【问题标题】:Golang floating point precision float32 vs float64Golang 浮点精度 float32 与 float64
【发布时间】:2014-04-15 18:07:20
【问题描述】:

我写了一个程序来演示 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

为什么Go程序在使用float32时输出与C程序不同?

【问题讨论】:

  • 我没有看到问题... 0.2 + 0.1 = 0.3, 0.3 - 0.3 = 0.0, 循环 0.0 + 0.0 永远不会超过 1.0 我很困惑的是你是怎么得到的它用float64跳出循环?
  • 浮点数并不完全准确。特别是数字 0.1 和 0.3 不能准确表示。这会导致a 在进入循环之前有一个非零(尽管非常小)的值。维基百科有解释。 en.wikipedia.org/wiki/Guard_digit
  • 我开始玩这个操场play.golang.org/p/Im6OFfTFPY,我有点明白你的意思,但它看起来在Go中float32s 精确表示,而float64s不是
  • 如果你用go tool 6g -S main.go检查代码的ASM你会看到原因。 float32 的计算如下:2.00000002980232230e-01 + 1.00000001490116120e-01 - 3.00000011920928950e-01 这是一个负值,永远不会总和为 1。为什么 Go 这样做,我不知道。
  • 在另一个游乐场 (play.golang.org/p/FZxCQTS9yG) 玩了一会儿,发现当您将 float64 打印到小数点后 20 位时,您会得到比 0.30...04 更多的数字,您会得到 @ 987654335@,其余的被切断。我猜想使用 float32 会截断更多,并舍入到偶数 0.3。这可以解释算术,但现在它只是一个理论。

标签: go floating-point precision


【解决方案1】:

使用 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

如果您将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 不同。

【讨论】:

  • 不需要strconv.FormatUint(x, 2)fmt.Printf 具有“%b”格式。不需要unsafe,有math.Float32bitsmath.Float64bits。更好的版本是:play.golang.org/p/ZqzdCZLfvC
【解决方案2】:

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

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

C 程序行为可能不同的原因有多种...尤其是,某些中间计算可能会以超高的精度(双精度或长双精度)执行。

我能想到的最可能的事情是,如果你曾经在 C 中写过 0.1 而不是 0.1f。
在这种情况下,您可能会导致初始化精度过高
(你 sum float a+double 0.1 => float 转换为 double,然后 result 转换回 float)

如果我模拟这些操作

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

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

27 次迭代后,总和为 1.6f

【讨论】:

  • 我更改了 C 程序以使用 f 声明所有常量,现在它也停止了。我最初从 wikipedia (en.wikipedia.org/wiki/Guard_digit) 获得了代码,所以我也会去更新该代码。
猜你喜欢
  • 2021-07-12
  • 1970-01-01
  • 2019-07-28
  • 1970-01-01
  • 1970-01-01
  • 2017-01-31
  • 1970-01-01
  • 1970-01-01
  • 2023-02-07
相关资源
最近更新 更多