Chapter 3

Working with Numbers and Expressions

IN THIS CHAPTER

check Dealing with operators, such as +, –, *, and /

check Creating finely crafted expressions

check Incrementing and decrementing

check Accepting an assignment

check Using the Math class

check Formatting your numbers

check Seeing strange things that can happen with numbers

In Book 2, 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. This chapter also covers performing advanced calculations using the Math class and techniques for formatting numbers when you display them. In addition, you find out why Java's math operations sometimes produce results you may 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 operators. This chapter focuses on the operators that do arithmetic. These arithmetic operators — seven of them in all, summarized in Table 3-1 — perform basic arithmetic operations, such as addition, subtraction, multiplication, and division.

TABLE 3-1 Java’s Arithmetic Operators

Operator

Description

+

Addition

-

Subtraction

*

Multiplication

/

Division

%

Remainder (Modulus)

++

Increment

--

Decrement

The following section of code can help clarify how these operators work for int types:

int a = 32, b = 5;

int c = a + b; // c is 37

int d = a - b; // d is 27

int e = a * b; // e is 160

int f = a / b; // f is 5 (32 / 5 is 6 remainder 2)

int g = a % b; // g is 2 (32 / 5 is 6 remainder 2)

a++; // a is now 33

b--; // b is now 4

Notice that for division, the result is truncated. Thus 32 / 5 returns 6, not 6.4. 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

If that's not what you want, you can cast one of the operands to a double before performing the division. (Casting an operand means converting its value from one data type to another.) Here’s how:

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 lie awake pondering the wonder of Java's arithmetic operators:

  • In algebra, you can write a number right next to a variable to imply multiplication. In this case, 4x means “four times x.” Not so in Java. The following statement doesn’t compile:

    int x;

    y = 4x; // error, this line won't compile

  • The remainder operator (%) is also called a modulus operator. It returns the remainder when the first operand is divided by the second operand. The remainder operator is often used to determine whether one number is evenly divisible by another, in which case the result is zero. For more information, see the next section, “Dividing Integers.”
  • All operators, including the arithmetic variety, are treated as separators in Java. As a result, any use of white space in an expression is optional. Thus the following two statements are equivalent:

    a = ( (x + 4) * 7 ) / (y * x);

    a=((x+4)*7)/(y*x);

    Just remember that a little bit of white space never hurt anyone, and sometimes it helps make Java a little more readable.

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. 5 / 4 gives the result 1, for example, 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 (%). Suppose that 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.

Tip The remainder operator is also called the modulus operator.

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);→4

public static void main(String[] args)

{

// declarations→7

int numberOfMarbles;

int numberOfChildren;

int marblesPerChild;

int marblesLeftOver;→11

// get the input data→13

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();→18

// calculate the results

marblesPerChild = numberOfMarbles / numberOfChildren;→21

marblesLeftOver = numberOfMarbles % numberOfChildren;→22

// print the results→24

System.out.println("Give each child " +

marblesPerChild + " marbles.");

System.out.println("You will have " +

marblesLeftOver + " marbles left over.");

}

}→30

The following paragraphs describe the key lines in this program:

  • →1 Imports the java.util.Scanner class so that the program can use it to get input from the user.
  • →4 Creates the Scanner object and assigns it to a class variable so that it can be used in any method in the class.
  • →7–11 Declare the local variables used by the program.
  • →13-18 Get the input from the user.
  • →21 Calculates the number of marbles to give to each child by using integer division, which discards the remainder.
  • →22 Calculates the number of marbles left over.
  • →24–30 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, 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.

In the expression a + b * c, for example, 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 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. In the expression (a * (b + c)) / d, b is added to c. Then the result is multiplied by a. 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. With integer types, however, it can make a huge difference if division is involved. 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 unary plus and minus 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 stuff 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 plus 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 operator 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 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 unary operators apply to a single variable. Thus, to increment the variable a, you can code just this:

a++;

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.

Tip You can use the increment and decrement operators only on variables — not on numeric literals or other expressions. Java doesn’t allow the following expressions, for example:

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.

Remember 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 effect on how an expression is evaluated. If you place an increment or decrement operator before its variable, the operator is applied before the value of the variable is read. As a result, the incremented value of the variable is used in the expression. By contrast, if you place the operator after the variable, the operator is applied after the value has been read. Thus, the original value of the variable is used in its immediate context within the expression.

Confused yet? A simple example can clear things 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 b3 — 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 postfix decrement 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.

Remember Because the increment and decrement operators can be confusing when used with other operators in an expression, I suggest that 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

b++;

c = a * b;

instead of

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. 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 recommend that you actually use any of these techniques, as they’re rarely necessary and almost always confusing, but knowing about them can shed light on how Java expressions work and sometimes can 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. The result of the expression a = 5, for example, 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. The following example 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.

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, which Table 3-2 lists.

TABLE 3-2 Compound Assignment Operators

Operator

Description

+=

Addition and assignment

-=

Subtraction and assignment

*=

Multiplication and assignment

/=

Division and assignment

%=

Remainder and assignment

The statement

a += 10;

is equivalent to

a = a + 10;

Also, the statement

z *=2;

is equivalent to

z = z * 2;

Warning To prevent confusion, use compound assignment expressions by themselves, not in combination with other expressions. 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 (sine and cosine), to practical functions such as rounding numbers or generating random numbers.

I was going to make a joke here about how you’d have 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 these jokes seemed too easy, so I decided not to make them.

Technical stuff 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. 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.

Using constants of the Math class

The Math class defines two constants that are useful for many mathematical calculations. Table 3-3 lists these constants.

TABLE 3-3 Constants of the Math Class

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 π 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. Then the program calculates the area of the circle in line 11. (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, πr2.)

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);→11

System.out.println("The area is " + area);

}

}

Tip At the time I wrote this, the actual value of pi was known to a precision of more than 31 trillion digits. Unfortunately, the Math class's PI constant has a precision of just 15 digits. Fortunately, 15 digits is plenty of precision for most real-world applications. According to mathematician James Grime, you need just 39 digits of precision to calculate the circumference of the entire known universe to an accuracy of less than the size of a single hydrogen atom.

Working with 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.

TABLE 3-4 Commonly Used Mathematical Functions Provided by the Math Class

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 — √ 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 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. You can see, for example, 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;

    Simply multiplying the number by itself, however, is often just as easy and just as readable:

    double x = 4.0;

    double y = x * x; // a is now 16;

  • Technical stuff 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. (Of course, he quotes it wrong. What the Scarecrow actually says in the movie is this: “The sum of the square root of any two sides of an isosceles triangle is equal to the square root of the remaining side.” Silly Scarecrow. He didn't need to know this to be smart.)

  • 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 into 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 later in this section, 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 stuff Strictly speaking, computers are not capable of generating truly random numbers, but over the years, clever computer scientists 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 human beings.

Technical stuff Java has many methods and classes for generating random numbers. The java.util.Random class, for example, 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 that it could be 0.0) and 1.0 (exclusive, meaning that it can't be 1.0). Most computer applications that need random values, however, need random integers between some arbitrary low value (usually 1, but not always) and some arbitrary high value. A program that plays dice needs random numbers between 1 and 6, whereas a program that deals cards needs random numbers between 1 and 52 (53 if a joker is used).

As a result, you need a Java expression that converts the double value returned by the random function to 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:

  1. The Math.Random method is called to get a random double value. This value is greater than 0.0 but less than 1.0.
  2. 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.)
  3. This value is converted to an integer by the (int) cast. Now you have an integer that's 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.)
  4. The low value in the range is added to the random number. Assuming that 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

Every time you run this program, however, you see a different sequence of 100 numbers.

The program in Listing 3-4 uses several Java features that 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 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 2, 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 and 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 round 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.

Listing 3-5 shows a program that uses each of the four methods to round three 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

TABLE 3-5 Rounding Functions Provided by the Math Class

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, it returns the one that is even. If the argument is already an integer, it returns the argument value.

round(argument)

Returns the integer that is closest to the argument. If the argument is a double, it returns a long. If the argument is a float, it returns an int.

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.5. 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 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: Large values are printed without any commas, and all the decimal digits for double or float values are printed whether you want them to be or not.

In many cases, you want to format your numbers before you print them — to add commas to large values and limit the number of decimal places printed, for example. Or, if a number represents a monetary amount, you may 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.

TABLE 3-6 Methods of the NumberFormat Class

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

Remember 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.

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 by 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%

Recognizing 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, there is a limit to the size of the numbers that can be stored in variables of type byte, short, int, or long. 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 that you don’t notice. Because of the way int stores negative values, large positive values suddenly become large negative values. This effect is called wrap around.

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. One 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. If you’re writing applications that deal with financial transactions, however, normal rounding can sometimes magnify the errors to make them significant. You may charge a penny too much or too little sales tax. And in extreme cases, your invoices may actually have obvious addition errors.

Remember Integer types are stored in binary too, of course. 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.

Division 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, 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 2, Chapter 8, you find out how to intercept this exception to allow your program to continue. 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 to 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. 0.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. -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 (NaN), regardless of the signs.

TABLE 3-7 Special Constants of the float and double Classes

Constant

Meaning

POSITIVE_INFINITY

Positive infinity

NEGATIVE_INFINITY

Negative infinity

NaN

Not a number

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. Suppose that 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.

Technical stuff The following paragraphs describe some final bits of weirdness I want to sneak in before closing this chapter:

  • NaN is not equal to itself, which can have some strange consequences. For example:

    double x = Math.sqrt(-50); // Not a number

    double y = x;

    if (x == y)

    System.out.println("x equals y");

    Okay, I know that I jumped the gun here on the if statement, because I don't cover if statements until Book 2, Chapter 4. So just assume, for the sake of argument, that the if statement tests whether the variable x is equal to the variable y. Because this test immediately follows an assignment statement that assigns the value of x to y, you can safely assume that x equals y, right?

    Wrong. Because x is NaN, y also is NaN. NaN is never considered to be equal to any other value, including another NaN. Thus, the comparison in the if statement fails.

  • Another strange consequence: You can’t assume that a number minus itself is always zero. Consider this statement:

    double z = x - x; // not necessarily zero

    Shouldn’t this statement always set z to zero? Not if x is NaN. In that case, not a number minus not a number is still not a number.

  • One more weirdness and then I’ll stop: Any mathematical operation involving infinity results in either another infinity or NaN. Infinity + 5, for example, still equals infinity, so Buzz Lightyear’s call “To infinity and beyond!” just isn’t going to happen. But infinity minus infinity gives you … NaN.