Golang浮点精度float32 vs float64

cha*_*e55 21 floating-point precision go

我写了一个程序来演示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)
}
Run Code Online (Sandbox Code Playgroud)

它打印:

After 54 iterations, a = 1.000000e+00
Run Code Online (Sandbox Code Playgroud)

这匹配用C编写的相同程序的行为(使用double类型)

但是,如果float32使用if ,程序会陷入无限循环!如果您修改C程序以使用float而不是a double,则打印

After 27 iterations, a = 1.600000e+00
Run Code Online (Sandbox Code Playgroud)

为什么Go程序在使用时没有与C程序相同的输出float32

ANi*_*sus 25

使用math.Float32bitsmath.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
Run Code Online (Sandbox Code Playgroud)

如果将这些二进制表示转换为十进制值并执行循环,则可以看到对于float32,初始值a将为:

0.20000000298023224
+ 0.10000000149011612
- 0.30000001192092896
= -7.4505806e-9
Run Code Online (Sandbox Code Playgroud)

负值,永远不能总和为1.

那么,为什么C表现不同?

如果你看一下二进制模式(并且稍微知道如何表示二进制值),你可以看到Go绕过最后一位,而我假设C只是裁剪它.

因此,从某种意义上说,虽然Go和C都不能在浮点数中精确地表示0.1,但Go使用最接近0.1的值:

Go:   00111101110011001100110011001101 => 0.10000000149011612
C(?): 00111101110011001100110011001100 => 0.09999999403953552
Run Code Online (Sandbox Code Playgroud)

编辑:

我发布了一个关于C如何处理浮点常量的问题,从答案来看,似乎允许C标准的任何实现.您尝试使用它的实现只是与Go不同.

  • 不需要`strconv.FormatUint(x,2)`,`fmt.Printf`具有"%b"格式.不需要`unsafe`,有`math.Float32bits`和`math.Float64bits`.更好的版本是:https://play.golang.org/p/ZqzdCZLfvC (4认同)

aka*_*ice 17

同意ANisus,go正在做正确的事.关于C,我不相信他的猜测.

C标准没有规定,但是libc的大多数实现都会将十进制表示转换为最接近的浮点数(至少符合IEEE-754 2008或ISO 10967),所以我认为这不是最可能的解释.

C程序行为可能有所不同有几个原因......特别是,某些中间计算可能会以过高的精度执行(双倍或长倍).

我能想到的最可能的事情是,如果你在C中写了0.1而不是0.1f.
在这种情况下,你可能会导致初始化的精度过高
(你总和浮动a + double 0.1 => float被转换为double ,然后将结果转换回浮点数)

如果我模仿这些操作

float32(float32(float32(0.2) + float64(0.1)) - float64(0.3))
Run Code Online (Sandbox Code Playgroud)

然后我在1.1920929e-8f附近找到了一些东西

经过27次迭代后,总和达到1.6f