Working with Numbers and Expressions
In Book II, Chapter 2, you discover the various primitive numeric types that are supported by Java. In this chapter, you build on that knowledge by doing basic operations with numbers. Much of this chapter focuses on the complex topic of expressions, which combine numbers with operators to perform calculations. But this chapter also covers techniques for formatting numbers when you display them and performing advanced calculations using the Math class. In addition, you find out why Java's math operations sometimes produce results you might not expect.
Working with Arithmetic Operators
An operator is a special symbol or keyword that's used to designate a mathematical operation or some other type of operation that can be performed on one or more values, called operands. In all, Java has about 40 different operators. This chapter focuses on the operators that do arithmetic. These arithmetic operators perform basic arithmetic operations, such as addition, subtraction, multiplication, and division. In all, there are 7 of them. Table 3-1 summarizes them.
Operator |
Description |
---|---|
+ |
Addition |
- |
Subtraction |
* |
Multiplication |
/ |
Division |
% |
Remainder |
++ |
Increment |
– |
Decrement |
The following section of code can help clarify how these operators work for int types:
int a = 21, b = 6; int c = a + b; // c is 27 int d = a - b; // d is 15 int e = a * b; // e is 126 int f = a / b; // f is 3 (21 / 6 is 3 remainder 3) int g = a % b; // g is 3 (20 / 6 is 3 remainder 3) a++; // a is now 22 b--; // b is now 5
Notice that for division, the result is truncated. Thus 21/6 returns 3, not 3.5. For more information about integer division, see the section "Dividing Integers" later in this chapter.
Here's how the operators work for double values:
double x = 5.5, y = 2.0; double m = x + y; // m is 7.5 double n = x - y; // n is 3.5 double o = x * y; // o is 11.0 double p = x / y; // p is 2.75 double q = x % y; // q is 1.5 x++; // x is now 6.5 y--; // y is now 1.0
Warning |
When you divide two int values, the result is an integer value, even if you assign it to a double variable. For example: int a = 21, b = 6; double answer = a / b; // answer = 3.0 |
Categorizing operators by the number of operands
A common way to categorize Java's operators is by the number of operands the operator works on. Categorizing the operators in this way, there are three types:
- Unary operators: Operators that work on just one operand. Examples of unary operators are negation (−x, which returns the negative of x) and increment (x++, which adds 1 to x).
A unary operator can be a prefix operator or a postfix operator. A prefix operator is written before the operand, like this:
operator operand
A postfix operator is written after the operand:
operand operator
- Binary operators: Operators that work on two operands. Examples of binary operators are addition (x + y), multiplication (invoiceTotal * taxRate), and comparison operators (x < leftEdge). In Java, all binary operators are infix operators, which means they appear between the operands, like this:
operand1 operator operand2
- Ternary operators: Operators that work on three operands. Java has only one ternary operator, called the conditional operator (?:). The conditional operator is also infix:
operand1 ? operand2: operand3
If that's not what you want, you can cast one of the operands to a double before performing the division, like this:
int a = 21, b = 6; double answer = (double)a / b; // answer = 3.5
The moral of the story is that if you want to divide int values and get an accurate double result, you must cast at least one of the int values to a double.
Tip |
Here are a few additional things to think about tonight as you lay awake pondering the wonder of Java's arithmetic operators:
|
Dividing Integers
When you divide one integer into another, the result is always another integer. Any remainder is simply discarded, and the answer is not rounded up. For example, 5/4 gives the result 1, and 3/4 gives the result 0. If you want to know that 5/4 is actually 1.25 or that 3/4 is actually 0.75, you need to use floats or doubles instead of integers.
If you need to know what the remainder is when you divide two integers, use the remainder operator (%). For example, suppose you have a certain number of marbles to give away and a certain number of children to give them to. The program in Listing 3-1 lets you enter the number of marbles and the number of children. Then it calculates the number of marbles to give to each child, and the number of marbles you have left over.
Here's a sample of the console output for this program, where the number of marbles entered is 93 and the number of children is 5:
Welcome to the marble divvy upper. Number of marbles: 93 Number of children: 5 Give each child 18 marbles. You will have 3 marbles left over.
Listing 3-1: A Program That Divvies Up Marbles
import java.util.Scanner; → 1 public class MarblesApp { static Scanner sc = new Scanner(System.in); → 5 public static void main(String[] args) { // declarations → 9 int numberOfMarbles; int numberOfChildren; int marblesPerChild; int marblesLeftOver; // get the input data → 15 System.out.println("Welcome to the marble divvy upper."); System.out.print("Number of marbles: "); numberOfMarbles = sc.nextInt(); System.out.print("Number of children: "); numberOfChildren = sc.nextInt(); // calculate the results marblesPerChild = numberOfMarbles / numberOfChildren; → 23 marblesLeftOver = numberOfMarbles % numberOfChildren; → 24 // print the results → 26 System.out.println("Give each child " + marblesPerChild + " marbles."); System.out.println("You will have " + marblesLeftOver + " marbles left over."); } }
The following paragraphs describe the key lines in this program:
→ 1 |
Imports the java.util.Scanner class so the program can use it to get input from the user. |
→ 5 |
Creates the Scanner object and assigns it to a class variable so it can be used in any method in the class. |
→ 9 |
The next four lines declare the local variables used by the program. |
→ 15 |
The next five lines get the input from the user. |
→ 23 |
Calculates the number of marbles to give to each child by using integer division, which discards the remainder. |
→ 24 |
Calculates the number of marbles left over. |
→ 26 |
The next two statements print the results. |
Tip |
It's probably obvious if you think about it, but you should realize that if you use integer division to divide a by b, then the result times b plus the remainder equals a. In other words: int a = 29; // any value will do int b = 3; // any value will do int c = a / b; int d = a % b; int e = (c * b) + d; // e will always equal a |
Combining Operators
You can combine operators to form complicated expressions. When you do, the order in which the operations are carried out is determined by the precedence of each operator in the expression. The order of precedence for the arithmetic operators is:
- Increment (++) and decrement (–) operators are evaluated first.
- Next, sign operators (+ or -) are applied.
- Then multiplication (*), division (/), and remainder (%) operators are evaluated.
- Finally, addition (+) and subtraction (-) operators are applied.
For example, in the expression a + b * c, multiplication has a higher precedence than addition. Thus b is multiplied by c first. Then the result of that multiplication is added to a.
If an expression includes two or more operators at the same order of precedence, the operators are evaluated left to right. Thus, in the expression a * b/c, a is first multiplied by b, and then the result is divided by c.
If you want, you can use parentheses to change the order in which operations are performed. Operations within parentheses are always performed before operations that aren't in parentheses. Thus, in the expression (a + b) * c, a is added to b first. Then the result is multiplied by c.
If an expression has two or more sets of parentheses, the operations in the innermost set are performed first. For example, in the expression (a * (b + c))/d, b is first added to c. Then the result is multiplied by a. And finally, that result is divided by d.
Tip |
Apart from the increment and decrement operators, these precedence rules and the use of parentheses are the same as they are for basic algebra. So if you were paying attention in the eighth grade, precedence should make sense. |
Warning |
With double or float values, changing the left to right order for operators with the same precedence doesn't affect the result. However, with integer types, it can make a huge difference if division is involved. For example, consider these statements: int a = 5, b = 6, c = 7; int d1 = a * b / c; // d1 is 4 int d2 = a * (b / c); // d2 is 0 |
This difference occurs because integer division always returns an integer result, which is a truncated version of the actual result. Thus, in the first expression, a is first multiplied by b, giving a result of 30. Then, this result is divided by c. Truncating the answer gives a result of 4. But in the second expression, b is first divided by c, which gives a truncated result of 0. Then, this result is multiplied by a, giving a final answer of 0.
Using the Unary Plus and Minus Operators
The plus and minus unary operators let you change the sign of an operand. Note that the actual operator used for these operations is the same as the binary addition and subtraction operators. The compiler figures out whether you mean to use the binary or the unary version of these operators by examining the expression.
Tip |
The unary minus operator doesn't necessarily make an operand have a negative value. Instead, it changes whatever sign the operand has to start with. Thus, if the operand starts with a positive value, the unary minus operator changes it to negative. But if the operand starts with a negative value, the unary minus operator makes it positive. The following examples illustrate this point: int a = 5; // a is 5 int b = -a; // b is -5 int c = -b; // c is +5 |
TECHNICAL STAUFF |
Interestingly enough, the unary plus operator doesn't actually do anything. For example: int a = -5; // a is -5 int b = +a; // b is -5 a = 5; // a is now 5 int c = +a; // c is 5 |
Notice that if a starts out positive, +a is also positive. But if a starts out negative, +a is still negative. Thus the unary + operator has no effect. I guess Java provides the unary plus operator out of a need for balance.
You can also use these operators with more complex expressions, like this:
int a = 3, b = 4, c = 5; int d = a * -(b + c); // d is -27
Here b is added to c, giving a result of 9. Then the unary minus is applied, giving a result of −9. Finally, −9 is multiplied by a giving a result of −27.
Using Increment and Decrement Operators
One of the most common operations in computer programming is adding or subtracting 1 from a variable. Adding 1 to a variable is called incrementing the variable. Subtracting 1 is called decrementing. The traditional way to increment a variable is like this:
a = a + 1;
Here the expression a + 1 is calculated, and the result is assigned to the variable a.
Java provides an easier way to do this type of calculation: the increment (++) and decrement (–) operators. These are unary operators that apply to a single variable. Thus, to increment the variable a, you can code just this:
a++;
Tip |
Note that an expression that uses an increment or decrement operator is a statement by itself. That's because the increment or decrement operator is also a type of assignment operator, as it changes the value of the variable it applies to. |
You can only use the increment and decrement operators on variables, not on numeric literals or other expressions. For example, Java doesn't allow the following expressions:
a = b * 5++; // can't increment the number 5 a = (b * 5)++; // can't increment the expression (b * 5)
Note that you can use an increment or decrement operator in an assignment statement. Here's an example:
int a = 5; int b = a--; // both a and b are set to 4
When the second statement is executed, the expression a– is evaluated first, so a is set to 4. Then the new value of a is assigned to b. Thus both a and b are set to 4.
TECHNICAL STAUFF |
The increment and decrement operators are unusual because they are unary operators that can be placed either before (prefix) or after (postfix) the variable they apply to. Whether you place the operator before or after the variable can have a major affect on how an expression is evaluated. If you place an increment or decrement operator before its variable, the operator is applied before the rest of the expression is evaluated. As a result, the incremented value of the variable is used in the expression. In contrast, if you place the operator after the variable, the operator is applied after the expression is evaluated. Thus the original value of the variable is used in the expression. |
Confused yet? A simple example can clear it up. First, consider these statements with an expression that uses a postfix increment:
int a = 5; int b = 3; int c = a * b++; // c is set to 15
When the expression in the third statement is evaluated, the original value of b-that is, 3-is used in the multiplication. Thus c is set to 15.
Then b is incremented to 4.
Now consider this version, with a prefix increment:
int a = 5; int b = 3; int c = a * ++b; // c is set to 20
This time, b is incremented before the multiplication is performed, so c is set to 20. Either way, b ends up set to 4.
Similarly, consider this example:
int a = 5; int b = --a; // b is set to 5, a is set to 4.
This example is similar to an earlier example, but this time the prefix increment operator is used. When the second statement is executed, the value of a is assigned to b. Then a is decremented. As a result, b is set to 5, and a is set to 4.
Warning |
Because the increment and decrement operators can be confusing when used with other operators in an expression, I suggest you use them alone. Whenever you're tempted to incorporate an increment or decrement operator into a larger expression, pull the increment or decrement out of the expression and make it a separate statement either before or after the expression. In other words, code this: b++; c = a * b; |
instead of this:
c = a * ++b;
In the first version, it's crystal-clear that b is incremented before the multiplication is done.
Using the Assignment Operator
The standard assignment operator (=) is used to assign the result of an expression to a variable. In its simplest form, you code it like this:
variable = expression;
Here's an example:
int a = (b * c) / 4;
You've already seen plenty of examples of assignment statements like this one, so I won't belabor this point any further. However, I do want to point out-just for the record-that you cannot code an arithmetic expression on the left side of an equal sign. Thus the following statement doesn't compile:
int a; a + 3 = (b * c);
Warning |
In the rest of this section, I point out some unusual ways in which you can use the assignment operator. I don't actually recommend that you use any of these techniques, as they are rarely necessary and almost always confusing. However, knowing about them can shed light on how Java expressions work and can sometimes help you find sneaky problems in your code. |
The key to understanding the rest of this section is realizing that in Java, assignments are expressions, not statements. In other words, a = 5 is an assignment expression, not an assignment statement. It becomes an assignment statement only when you add a semicolon to the end.
The result of an assignment expression is the value that's assigned to the variable. For example, the result of the expression a = 5 is 5. Likewise, the result of the expression a = (b + c) * d is the result of the expression (b + c) * d.
The implication is that you can use assignment expressions in the middle of other expressions. For example, the following is legal:
int a; int b; a = (b = 3) * 2; // a is 6, b is 3
As in any expression, the part of the expression inside the parentheses is evaluated first. Thus, b is assigned the value 3. Then the multiplication is performed, and the result (6) is assigned to the variable a.
Now consider a more complicated case:
int a; int b = 2; a = (b = 3) * b; // a is 9, b is 3
What's happening here is that the expression in the parentheses is evaluated first, which means that b is set to 3 before the multiplication is performed.
The parentheses are important in the previous example because without parentheses, the assignment operator is the last operator to be evaluated in Java's order of precedence. Consider one more example:
int a; int b = 2; a = b = 3 * b; // a is 6, b is 6
This time, the multiplication 3 * b is performed first, giving a result of 6. Then, this result is assigned to b. Finally, the result of that assignment expression (6) is assigned to a.
Incidentally, the following expression is also legal:
a = b = c = 3;
This expression assigns the value 3 to all three variables. Although this code seems pretty harmless, you're better off just writing three assignment statements. (You might guess that clumping the assignments together is more efficient than writing them on three lines, but you'd be wrong. These three assignments require the same number of bytecode instructions either way.)
Using Compound Assignment Operators
A compound assignment operator is an operator that performs a calculation and an assignment at the same time. All of Java's binary arithmetic operators (that is, the ones that work on two operands) have equivalent compound assignment operators. Table 3-2 lists them.
Operator |
Description |
---|---|
+= |
Addition and assignment |
-= |
Subtraction and assignment |
*= |
Multiplication and assignment |
/= |
Division and assignment |
%= |
Remainder and assignment |
For example, this statement
a += 10;
is equivalent to
a = a + 10;
and this statement
z *=2;
is equivalent to
z = z * 2;
Warning |
To avoid confusion, compound assignment expressions are best used by themselves, not in combination with other expressions. For example, consider these statements: int a = 2; int b = 3; a *= b + 1; |
Is a set to 7 or 8?
In other words, is the third statement equivalent to
a = a * b + 1; // This would give 7 as the result
or
a = a * (b + 1); // This would give 8 as the result
At first glance, you might expect the answer to be 7, because multiplication has a higher precedence than addition. But assignment has the lowest precedence of all, and the multiplication here is performed as part of the assignment. As a result, the addition is performed before the multiplication-and the answer is 8. (Gotcha!)
Using the Math Class
Java's built-in operators are useful, but they don't come anywhere near providing all the mathematical needs of most Java programmers. That's where the Math class comes in. It includes a bevy of built-in methods that perform a wide variety of mathematical calculations, from basic functions such as calculating an absolute value or a square root to trigonometry functions such as sin and cos, to practical functions such as rounding numbers or generating random numbers.
Tip |
I was going to make a joke here about having to take a Math class to fully appreciate the Math class, or how you'd better stay away from the Math class if you didn't do so well in Math class, or how if you're on the football team, maybe you can get someone to do the Math class for you. But it seemed too easy, so I decided not to. |
TECHNICAL STAUFF |
All the methods of the Math class are declared as static methods, which means you can use them by specifying the class name Math followed by a period and a method name. For example, here's a statement that calculates the square root of a number stored in a variable named y: double x = Math.sqrt(y); |
The Math class is contained in the java.lang package, which is automatically available to all Java programs. As a result, you don't have to provide an import statement to use the Math class.
The following sections describe the most useful methods of the Math class.
Constants of the Math class
The Math class defines two constants that are useful for many mathematical calculations. Table 3-3 lists these constants.
Constant |
What It Is |
Value |
---|---|---|
PI |
The constant Pi (π), the ratio of a circle's radius and diameter |
3.141592653589793 |
E |
The base of natural logarithms |
2.718281828459045 |
Note that these constants are only approximate values, because both p and e are irrational numbers.
The program shown in Listing 3-2 illustrates a typical use of the constant PI. Here, the user is asked to enter the radius of a circle. The program then calculates the area of the circle in line 13. (The parentheses aren't really required in the expression in this statement, but they help clarify that the expression is the Java equivalent to the formula for the area of a circle, pr2.)
Here's the console output for a typical execution of this program, in which the user entered 5 as the radius of the circle:
Welcome to the circle area calculator. Enter the radius of your circle: 5 The area is 78.53981633974483
Listing 3-2: The Circle Area Calculator
import java.util.Scanner; public class CircleAreaApp { static Scanner sc = new Scanner(System.in); public static void main(String[] args) { System.out.println( "Welcome to the circle area calculator."); System.out.print("Enter the radius of your circle: "); double r = sc.nextDouble(); double area = Math.PI * (r * r); →13 System.out.println("The area is " + area); } }
Mathematical functions
Table 3-4 lists the basic mathematical functions that are provided by the Math class. As you can see, you can use these functions to calculate such things as the absolute value of a number, the minimum and maximum of two values, square roots, powers, and logarithms.
Method |
Explanation |
---|---|
abs(argument) |
Returns the absolute value of the argument. The argument can be an int, long, float, or double. The return value is the same type as the argument. |
cbrt(argument) |
Returns the cube root of the argument. The argument and return value are doubles. |
exp(argument) |
Returns e raised to the power of the argument. The argument and the return value are doubles. |
hypot(arg1, arg2) |
Returns the hypotenuse of a right triangle calculated according to the Pythagorean theorem-the square root of x2+ y2. The argument and the return values are doubles. |
log(argument) |
Returns the natural logarithm (base e) of the argument. The argument and the return value are doubles. |
log10(argument) |
Returns the base 10 logarithm of the argument. The argument and the return value are doubles. |
max(arg1, arg2) |
Returns the larger of the two arguments. The arguments can be int, long, float, or double, but both must be of the same type. The return type is the same type as the arguments. |
min(arg1, arg2) |
Returns the smaller of the two arguments. The arguments can be int, long, float, or double, but both must be of the same type. The return type is the same type as the arguments. |
pow(arg1, arg2) |
Returns the value of the first argument raised to the power of the second argument. Both arguments and the return value are doubles. |
random() |
Returns a random number that's greater than or equal to 0.0 but less than 1.0. This method doesn't accept an argument, but the return value is a double. |
signum(argument) |
Returns a number that represents the sign of the argument: −1.0 if the argument is negative, 0.0 if the argument is zero, and 1.0 if the argument is positive. The argument can be a double or a float. The return value is the same type as the argument. |
sqrt(argument) |
Returns the square root of the argument. The argument and return value are doubles. |
The program shown in the upcoming Listing 3-3 demonstrates each of these methods except random. When run, it produces output similar to this:
abs(b) = 50 cbrt(x) = 2.924017738212866 exp(y) = 54.598150033144236 hypot(y, z)= 5.0 log(y) = 1.0986122886681096 log10(y) = 0.47712125471966244 max(a, b) = 100 min(a, b) = -50 pow(a, c) = 1000000.0 random() = 0.8536014557793756 signum(b) = -1.0 sqrt(x) = 1.7320508075688772
You can use this output to get an idea of the values returned by these Math class methods. For example, you can see that the expression Math.sqrt(y) returns a value of 5.0 when y is 25.0.
The following paragraphs point out a few interesting tidbits concerning these methods:
- You can use the abs and signnum methods to force the sign of one variable to match the sign of another, like this:
int a = 27; int b = -32; a = Math.abs(a) * Math.signum(b); // a is now -27;
- You can use the pow method to square a number, like this:
double x = 4.0; double y = Math.pow(x, 2); // a is now 16;
However, simply multiplying the number by itself is often just as easy and just as readable:
double x = 4.0; double y = x * x; // a is now 16;
-
TECHNICAL STAUFF In the classic movie The Wizard of Oz, when the Wizard finally grants the Scarecrow his brains, the Scarecrow suddenly becomes intelligent and quotes the Pythagorean Theorem, which is (coincidentally) used by the hypot method of the Math class. Unfortunately, he quotes it wrong. What the Scarecrow actually says in the movie is: "The sum of the square roots of any two sides of an isosceles triangle is equal to the square root of the remaining side." Silly Scarecrow. What he should have said, of course, is "The square of the hypotenuse of any right triangle is equal to the sum of the squares of the other two sides."
- Every time you run the program in Listing 3-3, you get a different result for the random method call. The random method is interesting enough that I describe it separately in the next section "Creating random numbers."
Listing 3-3: A Program That Uses the Mathematical Methods of the Math Class
public class MathFunctionsApp { public static void main(String[] args) { int a = 100; int b = -50; int c = 3; double x = 25.0; double y = 3.0; double z = 4.0; System.out.println("abs(b) = " + Math.abs(b)); System.out.println("cbrt(x) = " + Math.cbrt(x)); System.out.println("exp(y) = " + Math.exp(z)); System.out.println("hypot(y, z)= " + Math.hypot(y, z)); System.out.println("log(y) = " + Math.log(y)); System.out.println("log10(y) = " + Math.log10(y)); System.out.println("max(a, b) = " + Math.max(a, b)); System.out.println("min(a, b) = " + Math.min(a, b)); System.out.println("pow(a, c) = " + Math.pow(a, c)); System.out.println("random() = " + Math.random()); System.out.println("signum(b) = " + Math.signum(b)); System.out.println("sqrt(x) = " + Math.sqrt(y)); } }
Creating random numbers
Sooner or later, you're going to want to write programs that play simple games. Almost all games have some element of chance built in to them, so you need a way to create computer programs that don't work exactly the same every time you run them. The easiest way to do that is to use the random method of the Math class, which Table 3-4 lists along with the other basic mathematical functions of the Math class.
The random method returns a double whose value is greater than or equal to 0.0 but less than 1.0. Within this range, the value returned by the random method is different every time you call it, and is essentially random.
TECHNICAL STAUFF |
Strictly speaking, computers are not capable of generating truly random numbers. However, clever computer scientists over the years have developed ways to generate numbers that are random for all practical purposes. These numbers are called pseudorandom numbers because although they aren't completely random, they look random to most mortal human beings. |
TECHNICAL STAUFF |
Java has many different methods and classes for generating random numbers. For example, the java.util.Random class provides about ten specialized methods that generate random values. To generate a double with a value between 0.0 and 1.0, you can execute new Random().nextDouble(). In addition, the java.security.SecureRandom class provides random values for encrypting sensitive documents. And if size matters to you, the java.math.BigInteger class allows you to generate arbitrarily large random numbers (numbers with 1,000 digits, if that's what you need). |
The random method generates a random double value between 0.0 (inclusive, meaning it could be 0.0) and 1.0 (exclusive, meaning it can't be 1.0). However, most computer applications that need random values need random integers between some arbitrary low value (usually 1, but not always) and some arbitrary high value. For example, a program that plays dice needs random numbers between 1 and 6, while a program that deals cards needs random numbers between 1 and 52 (53 if jokers are used).
As a result, you need a Java expression that converts the double value returned by the random function into an int value within the range your program calls for. The following code shows how to do this, with the values set to 1 and 6 for a dice-playing game:
int low = 1; // the lowest value in the range int high = 6; // the highest value in the range int rnd = (int)(Math.random() * (high - low + 1)) + low;
This expression is a little complicated, so I show you how it's evaluated step by step:
- The random method to get a random double value. This value is greater than 0.0 but less than 5.0.
- The random value is multiplied by the high end of the range minus the low end, plus 1. In this example, the high end is 6, and the low end is 1, so you now have a random number that's greater than or equal to 0.0 but less than 6.0. (It could be 5.99999999999999, but it never is 6.0.)
- This value is then converted to an integer by the (int) cast. You now have an integer that's either 0, 1, 2, 3, 4, or 5. (Remember that when you cast a double to an int, any fractional part of the value is simply discarded. Because the number is less than 6.0, it never truncates to 6.0 when it is cast to an int.)
- The low value in the range is now added to the random number. Assuming the low is 1, the random number is now 1, 2, 3, 4, 5, or 6. That's just what you want: a random number between 1 and 6.
To give you an idea of how this random number calculation works, Listing 3-4 shows a program that places this calculation in a method called randomInt and then calls it to simulate 100 dice rolls. The randomInt method accepts two parameters representing the low and high ends of the range, and it returns a random integer within the range. In the main method of this program, the randomInt method is called 100 times, and each random number is printed by a call to System.out.print.
The console output for this program looks something like this:
Here are 100 random rolls of the dice: 4 1 1 6 1 2 6 6 6 6 5 5 5 4 5 4 4 1 3 6 1 3 1 4 4 3 3 3 5 6 5 6 6 3 5 2 2 6 3 3 4 1 2 2 4 2 2 4 1 4 3 6 5 5 4 4 2 4 1 3 5 2 1 3 3 5 4 1 6 3 1 6 5 2 6 6 3 5 4 5 2 5 4 5 3 1 4 2 5 2 1 4 4 4 6 6 4 6 3 3
However, every time you run this program, you see a different sequence of 100 numbers.
The program shown in Listing 3-4 uses several Java features you haven't seen yet.
Listing 3-4: Rolling the Dice
public class DiceApp { public static void main(String[] args) { int roll; String msg = "Here are 100 random rolls of the dice:"; System.out.println(msg); for (int i=0; i<100; i++) → 8 { roll = randomInt(1, 6); → 10 System.out.print(roll + " "); → 11 } System.out.println(); } public static int randomInt(int low, int high) → 16 { int result = (int)(Math.random() → 18 * (high - low + 1)) + low; return result; → 20 } }
The following paragraphs explain how the program works, but don't worry if you don't get all of the elements in this program. The main thing to see is the expression that converts the random double value returned by the Math.double method to an integer.
→ 8 |
The for statement causes the statements in its body (lines 10 and 11) to be executed 100 times. Don't worry about how this statement works for now; you find out about it in Book II, Chapter 5. |
→ 10 |
This statement calls the randomInt method, specifying 1 and 6 as the range for the random integer to generate. The resulting random number is assigned to the roll variable. |
→ 11 |
The System.out.print method is used to print the random number followed by a space. Because this statement calls the print method rather than the println method, the random numbers are printed on the same line rather than on separate lines. |
→ 16 |
The declaration for the randomInt method indicates that the method returns an int value and accepts two int arguments, one named low, the other named high. |
→ 18 |
This expression converts the random double value to an integer between low and high. |
→ 20 |
The return statement sends the random number back to the statement that called the randomInt method. |
Rounding functions
The Math class has four methods that round or truncate float or double values. Table 3-5 lists these methods. As you can see, each of these methods uses a different technique to calculate an integer value that's near the double or float value passed as an argument. Note that even though all four of these methods rounds a floating-point value to an integer value, only the round method actually returns an integer type (int or long, depending on whether the argument is a float or a double). The other methods return doubles that happen to be integer values.
Method |
Explanation |
---|---|
ceil(argument) |
Returns the smallest double value that is an integer and is greater than or equal to the value of the argument. |
floor(argument) |
Returns the largest double value that is an integer and is less than or equal to the value of the argument. |
rint(argument) |
Returns the double value that is an integer and is closest to the value of the argument. If two integer values are equally close, returns the one that is even. If the argument is already an integer, returns the argument value. |
round(argument) |
Returns the integer that is closest to the argument. If the argument is a double, returns a long. If the argument is a float, returns an int. |
Listing 3-5 shows a program that uses each of the four methods to round three different double values: 29.4, 93.5, and −19.3. Here's the output from this program:
round(x) = 29 round(y) = 94 round(z) = -19 ceil(x) = 30.0 ceil(y) = 94.0 ceil(z) = -19.0 floor(x) = 29.0 floor(y) = 93.0 floor(z) = -20.0 rint(x) = 29.0 rint(y) = 94.0 rint(z) = -19.0
Note that each of the four methods produces a different result for at least one of the values:
- All the methods except ceil return 29.0 (or 29) for the value 29.4. ceil returns 30.0, which is the smallest integer that's greater than 29.4.
- All the methods except floor return 94.0 (or 94) for the value 93.5. floor returns 93.0 because that's the largest integer that's less than 93.99. rint returns 94.0 because it's an even number, and 93.5 is midway between 93.0 and 94.0.
- All the methods except floor return −19.0 (or −19) for −19.3. floor returns −20 because −20 is the largest integer that's less than −19.3.
Listing 3-5: A Program That Uses the Rounding Methods of the Math Class
public class RoundingApp { public static void main(String[] args) { double x = 29.4; double y = 93.5; double z = -19.3; System.out.println("round(x) = " + Math.round(x)); System.out.println("round(y) = " + Math.round(y)); System.out.println("round(z) = " + Math.round(z)); System.out.println(); System.out.println("ceil(x) = " + Math.ceil(x)); System.out.println("ceil(y) = " + Math.ceil(y)); System.out.println("ceil(z) = " + Math.ceil(z)); System.out.println(); System.out.println("floor(x) = " + Math.floor(x)); System.out.println("floor(y) = " + Math.floor(y)); System.out.println("floor(z) = " + Math.floor(z)); System.out.println(); System.out.println("rint(x) = " + Math.rint(x)); System.out.println("rint(y) = " + Math.rint(y)); System.out.println("rint(z) = " + Math.rint(z)); } }
Formatting Numbers
Most of the programs you've seen so far have used the System.out. println or System.out.print method to print the values of variables that contain numbers. When you pass a numeric variable to one of these methods, the variable's value is converted to a string before it's printed. The exact format used to represent the value isn't very pretty. For example, large values are printed without any commas. And all the decimal digits for double or float values are printed, whether you want them to or not.
In many cases, you want to format your numbers before you print them. For example, you might want to add commas to large values and limit the number of decimal places printed. Or, if a number represents a monetary amount, you might want to add a dollar sign (or whatever currency symbol is appropriate for your locale). To do that, you can use the NumberFormat class. Table 3-6 lists the NumberFormat class methods.
Warning |
Like many aspects of Java, the procedure for using the NumberFormat class is a little awkward. It's designed to be efficient for applications that need to format a lot of numbers, but it's overkill for most applications. |
Method |
Explanation |
---|---|
getCurrencyInstance() |
A static method that returns a NumberFormat object that formats currency values. |
getPercentInstance() |
A static method that returns a NumberFormat object that formats percentages. |
getNumberInstance() |
A static method that returns a NumberFormat object that formats basic numbers. |
format(number) |
Returns a string that contains the formatted number. |
setMinimumFractionDigits(int) |
Sets the minimum number of digits to display to the right of the decimal point. |
setMaximumFractionDigits(int) |
Sets the maximum number of digits to display to the right of the decimal point. |
The procedure for using the NumberFormat class to format numbers takes a little getting used to. First, you must call one of the static getXxxInstance methods to create a NumberFormat object that can format numbers in a particular way. Then, if you want, you can call the setMinimumFractionDigits or setMaximumFractionDigits method to set the number of decimal digits to be displayed. Finally, you call that object's format method to actually format a number.
Note that the NumberFormat class is in the java.text package, so you must include the following import statement at the beginning of any class that uses NumberFormat:
import java.text.NumberFormat;
Here's an example that uses the NumberFormat class to format a double value as currency:
double salesTax = 2.425; NumberFormat cf = NumberFormat.getCurrencyInstance(); System.out.println(cf.format(salesTax));
When you run this code, the following line is printed to the console:
$2.43
Note that the currency format rounds the value from 2.425 to 2.43.
Here's an example that formats a number using the general number format, with exactly three decimal places:
double x = 19923.3288; NumberFormat nf = NumberFormat.getNumberInstance(); nf.setMinimumFractionDigits(3); nf.setMaximumFractionDigits(3); System.out.println(nf.format(x));
When you run this code, the following line is printed:
19,923.329
Here the number is formatted with a comma, and the value is rounded to three places.
Here's an example that uses the percentage format:
double grade = .92; NumberFormat pf = NumberFormat.getPercentInstance(); System.out.println(pf.format(grade));
When you run this code, the following line is printed:
92%
Tip |
If your program formats several numbers, consider creating the NumberFormat object as a class variable. That way, the NumberFormat object is created once when the program starts. Then, you can use the NumberFormat object from any method in the program's class. Here's a simple example that shows how this works: import java.text.NumberFormat; public class NumberFormatClassApp { static NumberFormat cf = NumberFormat.getCurrencyInstance(); public static void main(String[] args) { printMyAllowance(); printCostOfPaintBallGun(); } public static void printMyAllowance() { double myAllowance = 5.00; cf = NumberFormat.getCurrencyInstance(); System.out.println("My allowance: " + cf.format(myAllowance)); } public static void printCostOfPaintBallGun() { double costOfPaintBallGun = 69.95; cf = NumberFormat.getCurrencyInstance(); System.out.println("Cost of Paint Ball Gun: " + cf.format(costOfPaintBallGun)); } } |
Here the cf variable is created as a class variable. Then both the printMyAllowance and printCostOfPaintBallGun methods can use it.
Weird Things about Java Math
Believe it or not, computers-even the most powerful ones-have certain limitations when it comes to performing math calculations. These limitations are usually insignificant, but sometimes they sneak up and bite you. The following sections describe the things you need to watch out for when doing math in Java.
Integer overflow
Warning |
The basic problem with integer types is that they have a fixed size. As a result, the number has a size limit that can be stored in a short, int, or long variable. Although long variables can hold numbers that are huge, sooner or later you come across a number that's too big to fit in even a long variable. |
Okay, consider this (admittedly contrived) example:
int a = 1000000000; System.out.println(a); a += 1000000000; System.out.println(a); a += 1000000000; System.out.println(a); a += 1000000000; System.out.println(a);
Here you expect the value of a to get bigger after each addition. But here's the output that's displayed:
1000000000 2000000000 -1294967296 -294967296
The first addition seems to work, but after that, the number becomes negative! That's because the value has reached the size limit of the int data type. Unfortunately, Java doesn't tell you that this error has happened. It simply crams the int variable as full of bits as it can, discards whatever bits don't fit, and hopes you don't notice. Because of the way int stores negative values, large positive values suddenly become large negative values.
The moral of the story is that if you're working with large integers, you should use long rather than int because long can store much larger numbers than int. If your programs deal with numbers large enough to be a problem for long, consider using floating-point types instead. As you see in the next section, floating-point types can handle even larger values than long, and they let you know when you exceed their capacity.
Floating point weirdness
Warning |
Floating-point numbers have problems of their own. For starters, floating-point numbers are stored using the binary number system (base 2), but humans work with numbers in the decimal number system (base 10). Unfortunately, accurately converting numbers between these two systems is sometimes impossible. That's because in any number base, certain fractions can't be represented exactly. For example, base 10 has no way to exactly represent the fraction 1/3. You can approximate it as 0.3333333, but eventually you reach the limit of how many digits you can store, so you have to stop. In base 2, it happens that one of the fractions you can't accurately represent is the decimal value 1/10. In other words, a float or double variable can't accurately represent 0.1. |
Don't believe me? Try running this code:
float x = 0.1f; NumberFormat nf = NumberFormat.getNumberInstance(); nf.setMinimumFractionDigits(10); System.out.println(nf.format(x));
The resulting output is this:
0.1000000015
Although 0.1000000015 is close to 0.1, it isn't exact.
Warning |
In most cases, Java's floating-point math is close enough not to matter. The margin of error is extremely small. If you're using Java to measure the size of your house, you'd need an electron microscope to notice the error. However, if you're writing applications that deal with financial transactions, normal rounding can sometimes magnify the errors to make them significant. You might charge a penny too much or too little sales tax. And, in extreme cases, your invoices might actually have obvious addition errors. |
I'll have much more to say about floating-point numbers in Bonus Chapter 1 on this book's Web site. For now, just realize that you can't use float or double to represent money unless you don't care whether or not your books are in balance.
Warning |
Of course, integer types are stored in binary too. But integers aren't subject to the same errors that floating-point types are-because integers don't represent fractions at all. So you don't have to worry about this type of error for integer types. |
Dividing by zero
According to the basic rules of mathematics, you can't divide a number by zero. The reason is simple: Division is the inverse of multiplication-which means that if a * b = c, then it is also true that a = c/b. If you were to allow b to be zero, division would be meaningless because any number times zero is zero. Therefore both a and c would also have to be zero. In short, mathematicians solved this dilemma centuries ago by saying that division by zero is simply not allowed.
So what happens if you do attempt to divide a number by zero in a Java program? The answer depends on whether you're dividing integers or floating-point numbers. If you're dividing integers, the statement that attempts the division by zero chokes up what is called an exception, which is an impolite way of crashing the program. In Book II, Chapter 8, you find out how to intercept this exception to allow your program to continue. But in the meantime, any program you write that attempts an integer division by zero crashes.
If you try to divide a floating-point type by zero, the results are not so abrupt. Instead, Java assigns the floating-point result one of the special values listed in Table 3-7. The following paragraphs explain how these special values are determined:
- If you divide a number by zero and the sign of both numbers is the same, the result is positive infinity. For example, 40.0 divided by 0.0 is positive infinity, as is −34.0 divided by −0.0.
- If you divide a number by zero and the signs of the numbers are different, the result is negative infinity. For example, −40.0 divided by 0.0 is negative infinity, as is 34.0 divided by 0.0.
- If you divide zero by zero, the result is Not a Number, regardless of the signs.
REMEMBER |
Floating-point zeros can be positive or negative. Java considers positive and negative zeros to be equal numerically. |
If you attempt to print a floating-point value that has one of these special values, Java converts the value to an appropriate string. For example, suppose you execute the following statements:
double i = 50.0; double j = 0.0; double k = i / j; System.out.println(k);
The resulting console output is
Infinity
If i were −50.0, the console would display −Infinity. And if i were zero, the console would display NaN.
Constant |
Meaning |
---|---|
POSITIVE_INFINITY |
Positive infinity |
NEGATIVE_INFINITY |
Negative infinity |
NaN |
Not a number |
TECHNICAL STAUFF |
The following paragraphs describe some final bits of weirdness I want to sneak in before closing this chapter:
|