web-dev-qa-db-ja.com

Golang浮動小数点の精度float32とfloat64

Goで浮動小数点エラーを示すプログラムを作成しました。

func main() {
    a := float64(0.2) 
    a += 0.1
    a -= 0.3
    var i int
    for i = 0; a < 1.0; i++ {
        a += a
    }
    fmt.Printf("After %d iterations, a = %e\n", i, a)
}

以下を印刷します。

After 54 iterations, a = 1.000000e+00

これは、Cで記述された同じプログラムの動作と一致します(doubleタイプを使用)

ただし、float32が代わりに使用されると、プログラムは無限ループに陥ります。 floatの代わりにdoubleを使用するようにCプログラムを変更すると、印刷されます

After 27 iterations, a = 1.600000e+00

float32を使用すると、GoプログラムがCプログラムと同じ出力を持たないのはなぜですか?

20
charliehorse55

ANisusに同意し、正しいことを行ってください。 Cについては、彼の推測に納得していません。

C規格では規定されていませんが、libcのほとんどの実装は10進表現を最も近い浮動小数点数に変換します(少なくともIEEE-754 2008またはISO 10967に準拠するため)。

Cプログラムの動作が異なる理由はいくつかあります...特に、いくつかの中間計算が過剰な精度(doubleまたはlong double)で実行される可能性があります。

私が考えることができる最も可能性のあることは、これまでにCで0.1fの代わりに0.1を書いたということです。
この場合、初期化の精度が過剰になる可能性があります
(float a + double 0.1の合計=> floatはdoubleに変換され、結果は再びfloatに変換されます)

これらの操作をエミュレートする場合

float32(float32(float32(0.2) + float64(0.1)) - float64(0.3))

それから私は1.1920929e-8fの近くに何かを見つけます

27回の反復後、これは合計1.6fになります

17
aka.nice

math.Float32bits および math.Float64bits 、Goがさまざまな10進数値をIEEE 754バイナリ値としてどのように表すかを確認できます。

遊び場: https://play.golang.org/p/ZqzdCZLfvC

結果:

float32(0.1): 00111101110011001100110011001101
float32(0.2): 00111110010011001100110011001101
float32(0.3): 00111110100110011001100110011010
float64(0.1): 0011111110111001100110011001100110011001100110011001100110011010
float64(0.2): 0011111111001001100110011001100110011001100110011001100110011010
float64(0.3): 0011111111010011001100110011001100110011001100110011001100110011

これらを変換する バイナリ表現を10進値に変換してループを実行すると、float32の場合、aの初期値は次のようになります。

0.20000000298023224
+ 0.10000000149011612
- 0.30000001192092896
= -7.4505806e-9

合計が1になることは決してない負の値。

それでは、なぜCは異なる振る舞いをするのでしょうか?

バイナリパターンを見ると(そして、バイナリ値の表現方法について少し知っていると)、Goが最後のビットを丸めているのを見ることができます。

したがって、ある意味では、GoもCもfloatで0.1を正確に表すことはできませんが、Goは0.1に最も近い値を使用します。

Go:   00111101110011001100110011001101 => 0.10000000149011612
C(?): 00111101110011001100110011001100 => 0.09999999403953552

編集:

Cが浮動小数点定数をどのように処理するかについての質問 を投稿しましたが、その答えから、C標準の実装はいずれも許可されているようです。試した実装は、Goとは異なる方法で実行しました。

24
ANisus