Why Math.round (0.499999999999999917) rounds to 1 on Java 6
Join the DZone community and get the full member experience.
Join For FreeRepresentation error
Floating point is a base 2 format, which means all number are represented as a sum of powers of 2. e.g. 6.25 is 2^2 + 2^1 + 2^-2. However, even simple numbers like 0.1 cannot be represented exactly. This becomes obvious when converting to BigDecimal as it will preserve the value actually represented without rounding.new BigDecimal(0.1)= 0.1000000000000000055511151231257827021181583404541015625 BigDecimal.valueOf(0.1)= 0.1Using the constructor obtains the value actually represented, using valueOf gives the same rounded value you would see if you printed the double When a number is parsed, it is rounded to the closest represented value. This means that there is a number slightly less than 0.5 which will be rounded to 0.5 because it is the closest represented value. The following does a brute force search for the smallest value which rounded becomes 1.0
public static final BigDecimal TWO = BigDecimal.valueOf(2); public static void main(String... args) { int digits = 80; BigDecimal low = BigDecimal.ZERO; BigDecimal high = BigDecimal.ONE; for (int i = 0; i <= 10 * digits / 3; i++) { BigDecimal mid = low.add(high).divide(TWO, digits, RoundingMode.HALF_UP); if (mid.equals(low) || mid.equals(high)) break; if (Math.round(Double.parseDouble(mid.toString())) > 0) high = mid; else low = mid; } System.out.println("Math.round(" + low + ") is " + Math.round(Double.parseDouble(low.toString()))); System.out.println("Math.round(" + high + ") is " + Math.round(Double.parseDouble(high.toString()))); }The Source Code On Java 7 you get the following result.
Math.round(0.49999999999999997224442438437108648940920829772949218749999999999999999999999999) is 0 Math.round(0.49999999999999997224442438437108648940920829772949218750000000000000000000000000) is 1What is surprising is that in Java 6 you get the follow.
Math.round(0.49999999999999991673327315311325946822762489318847656250000000000000000000000000) is 0 Math.round(0.49999999999999991673327315311325946822762489318847656250000000000000000000000001) is 1
Where do these numbers come from?
The Java 7 value is the mid point between 0.5 and the previous represent value. Above this mid point, the value is rounded to 0.5 when parsed. The Java 6 value is the mid point between value value before 0.5 and the value before that.Value 0.5 is 0.5 The previous value is 0.499999999999999944488848768742172978818416595458984375 ... and the previous is 0.49999999999999988897769753748434595763683319091796875 The mid point between 0.5 and 0.499999999999999944488848768742172978818416595458984375 is 0.4999999999999999722444243843710864894092082977294921875 ... and the mid point between 0.499999999999999944488848768742172978818416595458984375 and 0.49999999999999988897769753748434595763683319091796875 is 0.4999999999999999167332731531132594682276248931884765625
Why is the Java 6 value smaller
In the Java 6 Javadoc Math.round(double) is defined asThe problem with this definition is that 0.49999999999999994 + 0.5 has a rounding error which results in the value 1.0. In the Java 7 Javadoc Math.round(double) it simply states(long)Math.floor(a + 0.5d)
Returns the closest long to the argument, with ties rounding up.
So how does Java 7 fix this?
The source code for Java 7's Math.round looks likepublic static long round(double a) { if (a != 0x1.fffffffffffffp-2) // greatest double value less than 0.5 return (long)floor(a + 0.5d); else return 0; }The result for the largest value less than 0.5 is hard coded.
So what is 0x1.fffffffffffffp-2?
It is a hexi-decimal presentation of the floating point value. It is rarely used, but it is precise as all values can be represented without error (to a limit of 53 bits).Related Links
Bug ID: 6430675 Math.round has surprising behavior for 0x1.fffffffffffffp-2Why does Math.round(0.49999999999999994) return 1
Published at DZone with permission of Peter Lawrey, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments