8

Java には倍精度の落とし穴があることは知っていますが、近似結果が正しい場合とそうでない場合があるのはなぜですか。

次のようなコード:

for ( float value = 0.0f; value < 1.0f; value += 0.1f )  
    System.out.println( value );

このような結果:

0.0
0.1
0.2
0.3
...
0.70000005
0.8000001
0.9000001
4

1 に答える 1

23

あなたが述べているように、すべての数値を IEEE754 で正確に表現できるわけではありません。これらの数値を出力するために Java が使用する規則と併せて、表示される内容に影響を与えます。

背景として、IEEE754 の不正確さについて簡単に説明します。この特定のケースでは、0.1を正確に表すことができないため、使用される実際の数値は のようなものであることがよくあります0.100000001490116119384765625

なぜそうなるのかの分析については、こちらを参照してください。「不正確な」値を取得している理由は、そのエラー ( 0.000000001490116119384765625) が徐々に加算されるためです。


0.1または0.2(または同様の数字) が、実際の値自体ではなく、Javaの印刷コードに関係していることを常に示すとは限らない理由。

0.1は実際には予想よりも少し高いですが、それを出力するコードはすべての数字を示しているわけではありません。書式文字列を小数点以下 50 桁に設定すると、実際の値が表示されることがわかります。

Java が float を (明示的なフォーマットなしで) 出力することを決定する方法のルールについては、こちらで詳しく説明しています。桁数に関連するビットは次のとおりです。

小数部分を表すには少なくとも 1 桁が必要であり、それを超える桁数は、引数値を float 型の隣接する値から一意に区別するために必要な数だけです。

例として、これがどのように機能するかを示すコードを次に示します。

public class testprog {
    public static void main (String s[]) {
        float n; int i, x;
        for (i = 0, n = 0.0f; i < 10; i++, n += 0.1f) {
            System.out.print( String.format("%30.29f %08x ",
                n, Float.floatToRawIntBits(n)));
            System.out.println (n);
        }
    }
}

これの出力は次のとおりです。

0.00000000000000000000000000000 00000000 0.0
0.10000000149011611938476562500 3dcccccd 0.1
0.20000000298023223876953125000 3e4ccccd 0.2
0.30000001192092895507812500000 3e99999a 0.3
0.40000000596046447753906250000 3ecccccd 0.4
0.50000000000000000000000000000 3f000000 0.5
0.60000002384185791015625000000 3f19999a 0.6
0.70000004768371582031250000000 3f333334 0.70000005
0.80000007152557373046875000000 3f4cccce 0.8000001
0.90000009536743164062500000000 3f666668 0.9000001

最初の列は float の実際の値で、IEEE754 の制限による不正確さを含みます。

2 番目の列は、浮動小数点値の 32 ビット整数表現 (実際の整数値ではなく、メモリ内でどのように見えるか) であり、低レベルのビット表現で値をチェックするのに役立ちます。

最後の列は、書式設定なしで数値を印刷したときに表示されるものです。


さらにいくつかのコードを見ると、不正確な値を継続的に追加することの不正確さが間違った数値を与える方法と、周囲の値との違いが出力される内容を制御する方法の両方が示さます。

public class testprog {
    public static void outLines (float n) {
        int i, val = Float.floatToRawIntBits(n);
        for (i = -1; i < 2; i++) {
            n = Float.intBitsToFloat(val+i);
            System.out.print( String.format("%30.29f %.08f %08x ",
                n, n, Float.floatToRawIntBits(n)));
            System.out.println (n);
        }
        System.out.println();
    }
    public static void main (String s[]) {
        float n = 0.0f;
        for (int i = 0; i < 6; i++) n += 0.1f;
        outLines (n); n += 0.1f;
        outLines (n); n += 0.1f;
        outLines (n); n += 0.1f;
        outLines (0.7f);
    }
}

このコードは、 の継続的な追加を使用し0.10.6、その値と隣接する float の値を出力します。その出力は次のとおりです。

0.59999996423721310000000000000 0.59999996 3f199999 0.59999996
0.60000002384185790000000000000 0.60000002 3f19999a 0.6
0.60000008344650270000000000000 0.60000008 3f19999b 0.6000001

0.69999998807907100000000000000 0.69999999 3f333333 0.7
0.70000004768371580000000000000 0.70000005 3f333334 0.70000005
0.70000010728836060000000000000 0.70000011 3f333335 0.7000001

0.80000001192092900000000000000 0.80000001 3f4ccccd 0.8
0.80000007152557370000000000000 0.80000007 3f4cccce 0.8000001
0.80000013113021850000000000000 0.80000013 3f4ccccf 0.80000013

0.69999992847442630000000000000 0.69999993 3f333332 0.6999999
0.69999998807907100000000000000 0.69999999 3f333333 0.7
0.70000004768371580000000000000 0.70000005 3f333334 0.70000005

最初に確認することは、最後の列の各ブロックの中間行に、周囲の行と区別するのに十分な小数桁があることです (前述の Java 印刷仕様に従って)。

0.6たとえば、小数点以下が 3 桁しかない場合、と0.6000001(隣接するビット パターン0x3f19999aと)を区別できません0x3f19999b。したがって、必要なだけ印刷します。

2 番目に気付くのは0.7、2 番目のブロックの値が ではないこと 0.7です。むしろ、(前の行に) その数値にさらに近いビット パターンがあるという事実に0.70000005 もかかわらずです。

これは、 を追加することによって生じる誤差が徐々に蓄積されたことが原因0.1です。最後のブロックから、 を0.7連続的に追加するのではなく、直接使用した場合0.1に正しい値が得られることがわかります。

したがって、あなたの特定のケースでは、問題を引き起こしているのは後者の問題です。印刷されているという事実0.70000005は、Java が十分に近い近似値を取得していない (取得している) ためではなく0.7、最初に到達した方法が原因です。

上記のコードを変更して、以下を含めると:

outLines (0.1f);
outLines (0.2f);
outLines (0.3f);
outLines (0.4f);
outLines (0.5f);
outLines (0.6f);
outLines (0.7f);
outLines (0.8f);
outLines (0.9f);

そのグループ内のすべての数値を正しく出力できることがわかります。

于 2012-11-27T05:05:04.890 に答える