web-dev-qa-db-ja.com

Math.round(0.49999999999999994)が1を返すのはなぜですか?

次のプログラムでは、.5を除いて、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);
}

版画

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

私はJava 6 update 31を使っています。

551
Peter Lawrey

まとめ

Java 6(そしておそらく以前)では、round(x)floor(x+0.5)として実装されています。1 これは仕様バグです。まさにこの1つの病理学的ケースです。2 Java 7はもはやこの壊れた実装を強制していません。3

問題

0.5 + 0.49999999999999994は倍精度で厳密に1です。

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
}

これは、0.49999999999999994の指数が0.5より小さいため、それらが追加されると、その仮数がシフトされ、ULPが大きくなるためです。

解決策

Java 7以降、OpenJDKは(たとえば)次のように実装しています。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 (これを見つけるために@SimonNickersonにクレジット)

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

561

これは、Java 7で修正された既知のバグ( Javaバグ6430675:Math.roundは0x1.fffffffffffffp-2に対して驚くべき動作をします )のようです。

230
Simon Nickerson

JDK 6のソースコード:

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

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

値が0.49999999999999994dの場合、JDK 6ではfloorが呼び出されて1が返されますが、JDK 7では、if条件は、数値が0.5より小さい最大のdouble値であるかどうかを確認します。この場合のように、数値は0.5未満の最大のdouble値ではないため、elseブロックは0を返します。

0.49999999999999999dを試すと、1は返されますが、0は返されません。これは、0.5未満の最大のdouble値であるためです。

81
Chandra Sekhar

JDK 1.6 32ビットでも同じですが、Java 7 64ビットでは、0.4999999999999999994の0は四捨五入され、最後の行は印刷されません。これはVMの問題のようですが、浮動小数点を使用すると、さまざまな環境(CPU、32ビットまたは64ビットモード)で結果が多少異なることが予想されます。

そして、roundや反転行列などを使うとき、これらbitsは大きな違いを生む可能性があります。

x64出力:

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
26
Danubian Sailor

以下の答えは、 Oraclebug report 6430675 atの抜粋です。詳しい説明はレポートをご覧ください。

メソッド{Math、StrictMath.roundは操作上として定義されています。

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

二重引数の場合。この定義は通常期待どおりに機能しますが、0x1.fffffffffffffp-2(0.4999999999999999994)に対して0ではなく1という驚くべき結果が得られます。

値0.49999999999999994は、0.5未満の最大浮動小数点値です。 16進浮動小数点リテラルとしてのその値は0x1.fffffffffffffp-2で、これは(2 - 2 ^ 52)* 2 ^ -2に等しいです。 ==(0.5 - 2 ^ 54)したがって、合計の正確な値

(0.5 - 2^54) + 0.5

1 - 2 ^ 54です。これは、隣接する2つの浮動小数点数(1 - 2 ^ 53)と1の中間です。Javaで使用されるIEEE 754算術の四捨五入による最短偶数丸めモードでは、浮動小数点の結果が不正確な場合正確な結果を囲む表現可能な浮動小数点値を返す必要があります。両方の値が等しく近い場合は、最後のビットがゼロであるものが返されます。この場合、addからの正しい戻り値は1であり、1未満の最大値ではありません。

メソッドが定義どおりに動作している間、この入力に対する動作は非常に驚くべきものです。この指定は、「最も近い長さに丸めて四捨五入する」のように修正することもできます。これにより、この入力に対する動作を変更することができます。

11
shiv.mymail