2
Math.floor(1.23456789 * 1e8) / 1e8)

returns:

1.23456788

Which is strange considering that:

Math.floor(1.23456789 * 1e9) / 1e9)

returns:

1.23456789

and also

Math.floor(1.23456799 * 1e8) / 1e8)

returns:

1.23456799

Any idea why this is happening and how to avoid it?

Todor Kolev
  • 1,432
  • 1
  • 16
  • 33

3 Answers3

3

As answered by Daniel Centore, double precision values are imprecise. Here is a list of the actual values (to 20 digits) for double precision numbers. Shown are the two closest encodings for 1.23456789; the first one is closer. When multiplying by 1e8, the multiply doesn't round up. When multiplying by 1e9, the multiply rounds up to an exact integer value.

1.23456789     => 1.2345678899999998901 hex 3ff3c0ca4283de1b
1.23456789     => 1.2345678900000001121 hex 3ff3c0ca4283de1c
1.23456789*1e8 => 123456788.99999998510 hex 419d6f3453ffffff
1.23456789*1e9 => 1234567890.0000000000 hex 41d26580b4800000
rcgldr
  • 27,407
  • 3
  • 36
  • 61
2

Floating point arithmetic is imprecise. The conversion to binary and back to decimal can cause problems (as binary cannot perfectly represent decimal fractions and visa-versa) on top of the fact that floating point has limited precision.

You can use BigDecimal to get perfect math, but it is much slower. This is only noticable if you will be doing many calculations.

Edit: Here's a BigDecimal tutorial.

Daniel Centore
  • 3,220
  • 1
  • 18
  • 39
1

The Java double type (almost) conforms to an international standard called IEEE-754, for floating point numbers. The numbers that can be expressed in this type all have one thing in common - their representations in binary terminate after at most 53 significant digits.

Most numbers with terminating decimal representations do not have terminating binary representations, which means there's no double that stores them exactly. When you write a double literal in Java, the value stored in the double will generally not be the number you wrote in the literal - instead it will be the nearest available double.

The literal 1.23456789 is no exception. It falls neatly between two numbers that can be stored as double, and the exact values of those two double numbers are 1.2345678899999998900938180668163113296031951904296875 and 1.23456789000000011213842299184761941432952880859375. The rule is that the closer of those two numbers is chosen, so the literal 1.23456789 is stored as 1.2345678899999998900938180668163113296031951904296875.

The literal 1E8 can be stored exactly as a double, so the multiplication in your first example is 1.2345678899999998900938180668163113296031951904296875 times 100000000, which of course is 123456788.99999998900938180668163113296031951904296875. This number can't be stored exactly as a double. The nearest double below it is 123456788.99999998509883880615234375 and the nearest double above it is 123456789 exactly. However, the double below it is closer, so the value of the Java expression 1.23456789 * 1E8 is actually 123456788.99999998509883880615234375. When you apply the Math.floor method to this number, the result is exactly 123456788.

The literal 1E9 can be stored exactly as a double, so the multiplication in your second example is 1.2345678899999998900938180668163113296031951904296875 times 1000000000, which of course is 1234567889.9999998900938180668163113296031951904296875. This number can't be stored exactly as a double. The nearest double below it is 1234567889.9999997615814208984375 and the nearest double above it is 1234567890 exactly. But this time, the double above it is closer, so the value of the Java expression 1.23456789 * 1E9 is exactly 1234567890, which is unchanged by the Math.floor method.

The second part of your question was how to avoid this. Well, if you want to do exact calculations involving numbers with terminating decimal representations, you must not store them in double variables. Instead, you can use the BigDecimal class, which lets you do things like this

BigDecimal a = new BigDecimal("1.23456789");
BigDecimal b = new BigDecimal("100000000");
BigDecimal product = a.multiply(b);

and the numbers are represented exactly.

Dawood ibn Kareem
  • 77,785
  • 15
  • 98
  • 110