579

In the following program you can see that each value slightly less than .5 is rounded down, except for 0.5.

for (int i = 10; i >= 0; i--) {
    long l = Double.doubleToLongBits(i + 0.5);
    double x;
    do {
        x = Double.longBitsToDouble(l);
        System.out.println(x + " rounded is " + Math.round(x));
        l--;
    } while (Math.round(x) > i);
}

prints

10.5 rounded is 11
10.499999999999998 rounded is 10
9.5 rounded is 10
9.499999999999998 rounded is 9
8.5 rounded is 9
8.499999999999998 rounded is 8
7.5 rounded is 8
7.499999999999999 rounded is 7
6.5 rounded is 7
6.499999999999999 rounded is 6
5.5 rounded is 6
5.499999999999999 rounded is 5
4.5 rounded is 5
4.499999999999999 rounded is 4
3.5 rounded is 4
3.4999999999999996 rounded is 3
2.5 rounded is 3
2.4999999999999996 rounded is 2
1.5 rounded is 2
1.4999999999999998 rounded is 1
0.5 rounded is 1
0.49999999999999994 rounded is 1
0.4999999999999999 rounded is 0

I am using Java 6 update 31.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Peter Lawrey
  • 498,481
  • 72
  • 700
  • 1,075
  • In my case the answer is 0 and not 1 and the last line it is not printing as it breaks the loop there. Here is my output ...... 8.499999999999998 rounded is 8 7.5 rounded is 8 7.499999999999999 rounded is 7 6.5 rounded is 7 6.499999999999999 rounded is 6 5.5 rounded is 6 5.499999999999999 rounded is 5 4.5 rounded is 5 4.499999999999999 rounded is 4 3.5 rounded is 4 3.4999999999999996 rounded is 3 2.5 rounded is 3 2.4999999999999996 rounded is 2 1.5 rounded is 2 1.4999999999999998 rounded is 1 0.5 rounded is 1 0.49999999999999994 rounded is 0 – Chandra Sekhar Mar 28 '12 at 07:34
  • 1
    On java 1.7.0 it works ok http://i.imgur.com/hZeqx.png – Caffeinated Mar 28 '12 at 07:44
  • 2
    @Adel: See my comment on [Oli's answer](http://stackoverflow.com/a/9903075/157247), looks like Java 6 implements this (and [documents that it does](http://docs.oracle.com/javase/6/docs/api/java/lang/Math.html#round(double))) in a way that can cause a further loss of precision by adding `0.5` to the number and then using `floor`; Java 7 [no longer documents it that way](http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#round(double)) (presumably/hopefully because they fixed it). – T.J. Crowder Mar 28 '12 at 07:53
  • 1
    It was a bug in a test program I wrote. ;) – Peter Lawrey Mar 28 '12 at 17:32
  • Ah good find ! I found another problem of floor(x+0.5) in old Squeak Smalltalk http://bugs.squeak.org/view.php?id=7134 – aka.nice Jul 11 '12 at 10:31
  • Because [Accuracy_problems](https://en.wikipedia.org/wiki/Floating_point#Accuracy_problems) – Somnath Muluk Aug 30 '16 at 13:13
  • 1
    Another example that shows floating point values cannot be taken at face value. – Michaël Roy Dec 08 '17 at 10:54
  • @MichaëlRoy esp when work on the edge of it's accuracy. A favourite example for me is `1.0 % 0.1` is hard to explain. – Peter Lawrey Dec 08 '17 at 11:27
  • 1
    After thinking about it. I don't see a problem. 0.49999999999999994 is larger than the smallest representable number less than 0.5, and the representation in **decimal human-readable form is itself an approximation** that is trying to fool us. – Michaël Roy Dec 08 '17 at 12:11
  • @MichaëlRoy `0.49999999999999994 < 0.5` as `double`s is true. As it is less than 0.5 it should round down. – Peter Lawrey Dec 08 '17 at 13:19
  • 1
    I do understand. and the error is half of the precision. Which is itself half of the precision after a single addition. This is akin to sampling noise, in a way. – Michaël Roy Dec 08 '17 at 13:44

5 Answers5

588

Summary

In Java 6 (and presumably earlier), round(x) is implemented as floor(x+0.5).1 This is a specification bug, for precisely this one pathological case.2 Java 7 no longer mandates this broken implementation.3

The problem

0.5+0.49999999999999994 is exactly 1 in double precision:

static void print(double d) {
    System.out.printf("%016x\n", Double.doubleToLongBits(d));
}

public static void main(String args[]) {
    double a = 0.5;
    double b = 0.49999999999999994;

    print(a);      // 3fe0000000000000
    print(b);      // 3fdfffffffffffff
    print(a+b);    // 3ff0000000000000
    print(1.0);    // 3ff0000000000000
}

This is because 0.49999999999999994 has a smaller exponent than 0.5, so when they're added, its mantissa is shifted, and the ULP gets bigger.

The solution

Since Java 7, OpenJDK (for example) implements it thus:4

public 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;
}

1. http://docs.oracle.com/javase/6/docs/api/java/lang/Math.html#round%28double%29

2. http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6430675 (credits to @SimonNickerson for finding this)

3. http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#round%28double%29

4. http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/java/lang/Math.java#Math.round%28double%29

Oliver Charlesworth
  • 252,669
  • 29
  • 530
  • 650
  • I don't see that definition of `round` in [the Javadoc for `Math.round`](http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#round(double)) or in the overview of the `Math` class. – T.J. Crowder Mar 28 '12 at 07:44
  • 3
    @ Oli: Oh now that's interesting, they took that bit out for Java 7 (the docs I linked to) -- maybe in order to avoid causing this sort of odd behavior by triggering a (further) loss of precision. – T.J. Crowder Mar 28 '12 at 07:46
  • @T.J.Crowder: Yes, it is interesting. Do you know if there's any kind of "release notes"/"improvements" doc for individual Java versions, so that we can verify this assumption? – Oliver Charlesworth Mar 28 '12 at 07:52
  • @ Oli: I expect there must be something buried in here: http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-429209.html Hard to see trees what with all that forest. – T.J. Crowder Mar 28 '12 at 08:53
  • Could you explain the last 2 lines for me please? "This is because 0.49999999999999994 ........" – Mohammad Fadin Apr 02 '12 at 22:26
  • 6
    @MohammadFadin: Take a look at e.g. http://en.wikipedia.org/wiki/Single_precision and http://en.wikipedia.org/wiki/Unit_in_the_last_place. – Oliver Charlesworth Apr 02 '12 at 22:27
  • It looks to me as though the OpenJDK 7 code that you show would still fail for (for example) `a = 5e15 + 1`: with this value, `a + 0.5d` is not exactly representable, so with round-ties-to-even the result of the sum is rounded up to `5e15 + 2` (and the floor, of course, would have no effect). What am I missing? (FWIW, Python 2.5 had this bug, as well as the bug the OP describes; `round(5e15 + 1)` gives `5000000000000002.0`. Both are fixed in Python 2.6 and later.) – Mark Dickinson Aug 11 '14 at 07:22
  • @MarkDickinson: Perhaps there are two bugs :) – Oliver Charlesworth Aug 11 '14 at 08:48
  • @MarkDickinson: Would adding 0x1.fffffffffffffp-2 and then adding in 0x0.0000000000001 p-2, rather adding 0.5 directly, be a good approach? Adding the former value would be equivalent to adding 0.5 except when the result would be either very near zero, or too large to add 0.5 precisely. – supercat Sep 30 '14 at 19:58
  • 2
    I can't help but think that this fix is only cosmetic, since zero is most visible. There are no doubt many other floating point values affected by this rounding error. – Michaël Roy Dec 10 '17 at 05:49
  • @MichaëlRoy Actually, no. It's that one specific number. No other number is smaller than n + 1/2 but close enough to n + 1/2 so that adding x + 1/2 gives n + 1 and not something smaller. – gnasher729 Jun 10 '20 at 04:45
  • @gnasher I disagree. Rounding is domain-dependent for this reason. You do not round numbers the same way for scientific applications and financial applications for this very reason. For example: using anything else than floor(x + .5f) for graphics or DSP will inevitably lead to disaster. That's probably also why round() is never used in scientific apps. – Michaël Roy Jun 11 '20 at 13:08
237

This appears to be a known bug (Java bug 6430675: Math.round has surprising behavior for 0x1.fffffffffffffp-2) which has been fixed in Java 7.

Simon Nickerson
  • 38,903
  • 18
  • 94
  • 124
87

Source code in JDK 6:

public static long round(double a) {
    return (long)Math.floor(a + 0.5d);
}

Source code in JDK 7:

public static long round(double a) {
    if (a != 0x1.fffffffffffffp-2) {
        // a is not the greatest double value less than 0.5
        return (long)Math.floor(a + 0.5d);
    } else {
        return 0;
    }
}

When the value is 0.49999999999999994d, in JDK 6, it will call floor and hence returns 1, but in JDK 7, the if condition is checking whether the number is the greatest double value less than 0.5 or not. As in this case the number is not the greatest double value less than 0.5, so the else block returns 0.

You can try 0.49999999999999999d, which will return 1, but not 0, because this is the greatest double value less than 0.5.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Chandra Sekhar
  • 16,511
  • 14
  • 71
  • 109
  • what happens with 1.499999999999999994 here then? returns 2 ? it should return 1, but this should get you the same error as earlier, but with a 1. ? – mmm Mar 28 '12 at 13:53
  • 7
    1.499999999999999994 cannot be represented in double-precision floating-point. 1.4999999999999998 is the smallest double less than 1.5. As you can see from the question, the `floor` method rounds it correctly. – OrangeDog Mar 29 '12 at 11:12
28

I've got the same on JDK 1.6 32-bit, but on Java 7 64-bit I've got 0 for 0.49999999999999994 which rounded is 0 and the last line is not printed. It seems to be a VM issue, however, using floating points, you should expect the results to differ a bit on various environments (CPU, 32- or 64-bit mode).

And, when using round or inverting matrices, etc., these bits can make a huge difference.

x64 output:

10.5 rounded is 11
10.499999999999998 rounded is 10
9.5 rounded is 10
9.499999999999998 rounded is 9
8.5 rounded is 9
8.499999999999998 rounded is 8
7.5 rounded is 8
7.499999999999999 rounded is 7
6.5 rounded is 7
6.499999999999999 rounded is 6
5.5 rounded is 6
5.499999999999999 rounded is 5
4.5 rounded is 5
4.499999999999999 rounded is 4
3.5 rounded is 4
3.4999999999999996 rounded is 3
2.5 rounded is 3
2.4999999999999996 rounded is 2
1.5 rounded is 2
1.4999999999999998 rounded is 1
0.5 rounded is 1
0.49999999999999994 rounded is 0
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Danubian Sailor
  • 21,505
  • 37
  • 137
  • 211
13

The answer hereafter is an excerpt of an Oracle bug report 6430675 at. Visit the report for the full explanation.

The methods {Math, StrictMath.round are operationally defined as

(long)Math.floor(a + 0.5d)

for double arguments. While this definition usually works as expected, it gives the surprising result of 1, rather than 0, for 0x1.fffffffffffffp-2 (0.49999999999999994).

The value 0.49999999999999994 is the greatest floating-point value less than 0.5. As a hexadecimal floating-point literal its value is 0x1.fffffffffffffp-2, which is equal to (2 - 2^52) * 2^-2. == (0.5 - 2^54). Therefore, the exact value of the sum

(0.5 - 2^54) + 0.5

is 1 - 2^54. This is halfway between the two adjacent floating-point numbers (1 - 2^53) and 1. In the IEEE 754 arithmetic round to nearest even rounding mode used by Java, when a floating-point results is inexact, the closer of the two representable floating-point values which bracket the exact result must be returned; if both values are equally close, the one which its last bit zero is returned. In this case the correct return value from the add is 1, not the greatest value less than 1.

While the method is operating as defined, the behavior on this input is very surprising; the specification could be amended to something more like "Round to the closest long, rounding ties up," which would allow the behavior on this input to be changed.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
shiv.mymail
  • 1,111
  • 3
  • 15
  • 22