By Doug Lowe

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. Here are the things you need to watch out for when doing math in Java.

Integer overflow

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

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. Floating-point types can handle even larger values than long, and they let you know when you exceed their capacity.

Floating-point weirdness

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.

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.

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.

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.

There is a way to intercept this exception to allow your program to continue which you don’t find out here. 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 the table below. 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.
Special Constants of the float and double Classes
Constant Meaning
POSITIVE_INFINITY Positive infinity
NEGATIVE_INFINITY Negative infinity
NaN Not a number

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 x = Math.sqrt(-50); // Not a number

double y = x;

if (x == y)

System.out.println(“x equals y”);

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.

The following paragraphs describe some final bits of weirdness:

  • 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");

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. NaNis 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: 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.