-2

This may seem a very silly question, but I encountered something mysterious and seemingly beautiful today. I tried Googling around, and I couldn't dig anything up.

I am aware that 0.1 cannot be represented in binary. And YET, when I run the following code:

    double g = 1.0; 
    System.out.println(g);
    g = g/10;
    System.out.println(g);
    g = g*3;
    System.out.println(g);

This produces the output:

1.0
0.1
0.30000000000000004

The first output and the third output are expected, but what is going on with the second one? Why is it, well, correct? This should be impossible, and yet, there it is.

Ben I.
  • 1,065
  • 1
  • 13
  • 29
  • 2
    Can you please include the outputs you saw, so that we don't have to compile and re-run your code? – Nayuki Oct 16 '16 at 04:23
  • 2
    The output he's getting is probably: `1.0`, `0.1`, `0.30000000000000004` – Addison Oct 16 '16 at 04:26
  • 1
    Display enough digits right of the decimal and you will see the difference. Please read [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html). – Jim Garrison Oct 16 '16 at 05:33
  • Let me note for posterity that if you want to avoid these issues with floating-point, use the [`BigDecimal`](https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html) class. – Basil Bourque Oct 16 '16 at 07:17
  • @JimGarrison Why did you direct me towards that? It proved a lot of things about the IEEE floating point standard, but had little to do with my question, which ultimately hinged upon an unexpected behavior of toString(). – Ben I. Oct 16 '16 at 12:40

3 Answers3

1

Numeric Promotion Rules

  1. If two values have different data types, Java will automatically promote one of the val- ues to the larger of the two data types.
  2. If one of the values is integral and the other is floating-point, Java will automatically promote the integral value to the floating-point value’s data type.
  3. Smaller data types, namely byte, short, and char, are first promoted to int any time they’re used with a Java binary arithmetic operator, even if neither of the operands is int.
  4. After all promotion has occurred and the operands have the same data type, the result- ing value will have the same data type as its promoted operands.
gaston
  • 498
  • 7
  • 15
1

Why is it, well, correct?

As you noted, many decimal floating point numbers cannot be represented as binary floating point numbers and vice versa.

When you write a statement like this:

double g = 0.1;

the decimal value is converted to the nearest binary floating point value. And when you then print it like this

System.out.println(g);

the formatter produces the nearest decimal floating point value according to the following rules:

How many digits must be printed for the fractional part? There must be at least one digit to represent the fractional part, and beyond that as many, but only as many, more digits as are needed to uniquely distinguish the argument value from adjacent values of type double.

(Reference: Double.toString(double) javadoc )

That means that you will often get the exact decimal representation of the decimal number that you started with.

In simple terms, the error in converting from decimal to binary is the same as the error when converting from binary to decimal. The errors "cancel out".

Now, this doesn't always happen. Often the cumulative errors in the calculation are large enough the errors in the decimal (and binary) results will be apparent in the output.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • It turns out the Javadoc is wrong, and [Java may not always produce the shortest string that round-trips](http://www.exploringbinary.com/java-doesnt-print-the-shortest-strings-that-round-trip/) (this does not affect your answer though). – Rick Regan Oct 19 '16 at 11:13
0

Let us step through the computation line by line:

double g = 1.0;

g is the float64 number representing exactly 1.0.

g = g / 10;

The right operand is converted to double, so it is 10.0 exact.

The division operation is performed at infinite precision (conceptually), and then rounded to the closest float64 number as the result.

The exact answer is clearly 0.1. But the closest float64 number to 0.1 is exactly 7205759403792794 / 256.

Hence g = 0.10000000000000000555111512312578...(more digits). If you want to print the full-precision exact value, look at new BigDecimal(g).

g = g * 3;

Again, the right operand is converted to 3.0 exact. We multiply 0.1000000000000000055511151231257(...) by 3 to get 0.3000000000000000166533453693773(...).

The value of g now is exactly 5404319552844596 / 254.

Nayuki
  • 17,911
  • 6
  • 53
  • 80