我写了一个程序来演示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
代替,程序会陷入无限循环!如果您修改C程序以使用float
而不是double
,则会打印
After 27 iterations, a = 1.600000e+00
使用float32
时,为什么Go程序与C程序的输出相同?
答案 0 :(得分:23)
使用math.Float32bits
和math.Float64bits
,您可以看到Go如何将不同的十进制值表示为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
如果convert these二进制表示为十进制值并执行循环,则可以看到对于float32,a
的初始值将为:
0.20000000298023224
+ 0.10000000149011612
- 0.30000001192092896
= -7.4505806e-9
负值,永远不能总和为1.
那么,为什么C表现不同?
如果你看一下二进制模式(并且稍微知道如何表示二进制值),你可以看到Go绕过最后一位,而我假设C只是裁剪它。
因此,从某种意义上说,虽然Go和C都不能在浮点数中精确地表示0.1,但Go使用的值最接近0.1:
Go: 00111101110011001100110011001101 => 0.10000000149011612
C(?): 00111101110011001100110011001100 => 0.09999999403953552
修改强>
我发布了a question about how C handles float constants,从答案中可以看出,C标准的任何实现都可以执行。您尝试使用它的实现方式与Go不同。
答案 1 :(得分:15)
同意ANisus,go正在做正确的事。关于C,我不相信他的猜测。
C标准没有规定,但是大多数libc实现都会将十进制表示转换为最接近的浮点数(至少符合IEEE-754 2008或ISO 10967),所以我不认为这是最多的可能的解释。
C程序行为可能有所不同有几个原因......特别是,某些中间计算可能会以过高的精度执行(双倍或长倍)。
我能想到的最可能的事情是,你是否曾在C中写过0.1而不是0.1f 在这种情况下,您可能会导致初始化精度过高 (你总和浮点数a + double 0.1 =&gt;浮点数转换为double,然后结果转换回浮点数)
如果我模仿这些操作
float32(float32(float32(0.2) + float64(0.1)) - float64(0.3))
然后我在1.1920929e-8f附近发现了一些东西
经过27次迭代后,总和达到1.6f