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プログラムと同じ出力を持たないのはなぜですか?
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になります
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とは異なる方法で実行しました。