0

How do I explain the below:

    double o = 2.3;
    int i = (int) (o * 100000.0);
    double d = i / 100000.0;
    System.out.println(d);

prints 2.29999

    double o = 2.3;
    double i = o * 100000.0;
    double d = i / 100000.0;
    System.out.println(d);

prints 2.3

When dividing an int by double doesn't java first cast the int as a double then do the division - if so the two code blocks should effectively print the same value?

There seems to be some IEEE 754 floating point precision specifications I'm missing and/or jvm optimizations that inlines

 double i = 2.3 * 100000.0;
 double d = i / 100000.0;

as double d = 2.3 * 100000.0 / 100000.0; effectively cancelling out the division and making this a no-op.

Thoughts?

fo_x86
  • 2,583
  • 1
  • 30
  • 41
  • This is known _binary operations level_ issue. That is why when you compare some `double` values you **must not** use ==, but use special `Compare` method with precision setting – Ilya Tereschuk Jan 08 '14 at 19:36
  • @ElliotTereschuk: There is no `==` operation in the question, and “binary operations level” is not a term generally used in floating-point. – Eric Postpischil Jan 08 '14 at 19:39
  • @EricPostpischil Simple check with == obviously returns `false` on 2.29999 == 2.3, this is very common error. This comment is rather a suggestion to use rounding and [Double.Compare](http://stackoverflow.com/questions/1726254/why-is-javas-double-comparedouble-double-implemented-the-way-it-is) – Ilya Tereschuk Jan 08 '14 at 19:44
  • @ElliotTereschuk: The question does not ask why a comparison returns an unexpected result; it asks why two results differ. Comments about comparison do not speak to this issue. – Eric Postpischil Jan 08 '14 at 20:04

3 Answers3

10

2.3 is not exactly representable in IEEE-754 64-bit binary floating-point. What happens in your first code sequence is:

  • The source text 2.3 is converted to the nearest representable value, 2.29999999999999982236431605997495353221893310546875.
  • The exact mathematical result of multiplying that by 100000 is not exactly representable either, so it is rounded to 229999.99999999997089616954326629638671875.
  • When this is converted to an int, the conversion truncates, producing 229999.
  • Dividing that by 100000 rounds again, producing 2.29999000000000020094148567295633256435394287109375.
  • When the above is printed, Java displays it as “2.29999”.
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • Why don't I have this issue when I replace 2.3 with 0.1 (which can't be represented exactly using a double). – fo_x86 Jan 08 '14 at 19:58
  • @fo_x86: Sometimes the nearest representable value is lower than the original value, sometimes it is higher. In the case of 2.3, it is lower. In the case of .1, it is higher, 0.1000000000000000055511151231257827021181583404541015625. Then multiplying by 100,000 happens to produce exactly 10,000, due to rounding. Converting that to an `int` produces 10,000. Dividing by 100,000 produces 0.1000000000000000055511151231257827021181583404541015625, which is displayed as “.1”. – Eric Postpischil Jan 08 '14 at 20:00
  • 1
    It may be helpful to know that Java’s default display of floating-point values shows only enough digits to distinguish the value from any neighboring value. Thus, it produces “.1” for 0.1000000000000000055511151231257827021181583404541015625, because converting “.1” back to floating-point produces the original value. If the value being printed were slightly greater or lesser, Java would use more digits to distinguish it. Thus, by default, Java “lies” a little bit when showing floating-point values. – Eric Postpischil Jan 08 '14 at 20:02
  • The easiest way I've found so far to get an exact print out of a double `d` is `System.out.println(new BigDecimal(d))`. – Patricia Shanahan Jan 08 '14 at 20:25
  • I'm surprised that no one has linked to Goldberg's paper, "What Every Computer Scientist Should Know About Floating-Point Arithmetic". https://ece.uwaterloo.ca/~dwharder/NumericalAnalysis/02Numerics/Double/paper.pdf [pdf] – shoover Jan 08 '14 at 21:01
4

There seems to be some IEEE 754 floating point precision specifications I'm missing

When you use (int) it truncates the fractional part no matter how close it is to the next whole value. It is not reversible as information is lost, so it should be no surprise that you don't get the same result.

What you could do instead is the following

double o = 2.3;
long l = Math.round(o * 100000.0);
double d = l / 100000.0;
System.out.println(d);

jvm optimizations that inlines ... making this a no-op

The JVM might optimise it, but it won't change the result. If an optimisation changes the outcome it is a bug in the optimiser.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
1
double i = o * 100000.0;

The value of i after this is 229999.99999999997 (you can see this yourself by adding a simple print statement; it's due to floating-point inaccuracies).

int i = (int) (o * 100000.0);

The value of i after this is 229999 (i.e. the value above with the fractional part truncated due to the int cast).

Therefore, in the line

double d = i / 100000.0;

you are using two numerically different i values in your two snippets (i.e. they differ by about 1), hence the different outputs.

You are correct in saying that when dividing an int by a double, the int is first converted to a double (this is done through the bytecode instruction i2d).

Community
  • 1
  • 1
arshajii
  • 127,459
  • 24
  • 238
  • 287