为什么浮点运算在go中会产生不同的结果[已关闭]

问题描述 投票:0回答:2

这是 go 中的示例代码:

package main

import "fmt"

func mult32(a, b float32) float32 { return a*b }
func mult64(a, b float64) float64 { return a*b }


func main() {
    fmt.Println(3*4.3)                  // A1, 12.9
    fmt.Println(mult32(3, 4.3))         // B1, 12.900001
    fmt.Println(mult64(3, 4.3))         // C1, 12.899999999999999

    fmt.Println(12.9 - 3*4.3)           // A2, 1.8033161362862765e-130
    fmt.Println(12.9 - mult32(3, 4.3))  // B2, -9.536743e-07
    fmt.Println(12.9 - mult64(3, 4.3))  // C2, 1.7763568394002505e-15

    fmt.Println(12.9 - 3*4.3)                               // A4, 1.8033161362862765e-130
    fmt.Println(float32(12.9) - float32(3)*float32(4.3))    // B4, -9.536743e-07
    fmt.Println(float64(12.9) - float64(3)*float64(4.3))    // C4, 1.7763568394002505e-15

}

A1、B1 和 C1 行之间的结果差异是可以理解的。然而,从A2开始到C2的魔法来了。 B2 和 C2 的结果均与 A2 行的结果不匹配。对于 x2 行(x = A、B 或 C)也是如此 - 但 x2 和 x4 的输出是相同的。

为了确定,让我们以二进制形式打印结果。

    fmt.Printf("%b\n", 3*4.3)                   // A11, 7262054399134925p-49
    fmt.Printf("%b\n", mult32(3, 4.3))          // B11, 13526631p-20
    fmt.Printf("%b\n", mult64(3, 4.3))          // C11, 7262054399134924p-49

    fmt.Printf("%b\n", 12.9 - 3*4.3)            // A12, 4503599627370496p-483
    fmt.Printf("%b\n", 12.9 - mult32(3, 4.3))   // B12, -8388608p-43
    fmt.Printf("%b\n", 12.9 - mult64(3, 4.3))   // C12, 4503599627370496p-101

    fmt.Printf("%b\n", 12.9 - 3*4.3)                                // A14, 4503599627370496p-483
    fmt.Printf("%b\n", float32(12.9) - float32(3)*float32(4.3))     // B14, -8388608p-43
    fmt.Printf("%b\n", float64(12.9) - float64(3)*float64(4.3))     // C14, 4503599627370496p-101

上面代码中的一些事实(bin 形式中的一个):

  1. A11 行和 C11 行之间存在差异(最后一位数字 - 就在指数之前)。
  2. A12 线和 C12 线几乎相同(除了指数!!!),A14 线和 C14 线之间也可以观察到相同的情况。

问题来了:

  1. 如何执行裸(裸:))数字的计算? (每 Axx 行的计算)
  2. 它们是由编译器/其他什么东西执行的吗?
  3. 如果是,那么为什么它们不同?优化?
  4. 它们是在与 IEE-754 不同的系统中计算的吗?
  5. 如果是,为什么会这样?
  6. 实现更准确的精度是否证明这种方法是合理的?

代码已在 64 位 Linux 上的“go run”和“go build”(go1.0.3) 下进行了测试,也在该网站上进行了测试:http://tour.golang.org/

go floating-point precision ieee-754
2个回答
6
投票
  1. 常数

    • 数字常量表示任意精度的值并且不会溢出。
    • 表示至少 256 位的整数常量。
    • 表示浮点常量,包括复数常量的部分,尾数至少为 256 位,带符号指数至少为 32 位。
  2. 是的,由编译器用于编译时常量。

  3. 是的,它们是不同的:涉及更高的精度。参见 1。

  4. 是的,参见1。

  5. 最大限度地减少多项浮点常量表达式的浮点误差累积。

  6. 当然可以。实现较低精度可以成为一个目标吗?运行时浮点运算本质上是不完美的,这已经足够了,不需要从常量表达式中添加更多的不精确性。


1
投票

表示浮点常量,包括复数常量的部分,尾数至少为 256 位,带符号指数至少为 32 位。

请注意,Go 1.8(目前在 2016 年第四季度处于测试阶段,于 2017 年第一季度发布)更改了该定义:

语言规范现在仅要求实现支持浮点常量中的最多 16 位指数
这不会影响“

gc
”或
gccgo
编译器,它们仍然支持 32 位指数。

来自变更17711

spec
:要求常数中的最小指数为 16 位,而不是 32

16 位二进制指数允许常数范围大致涵盖从 7e-9865 到 7e9863 的范围,这对于任何实际和假设的常数算术来说都绰绰有余。

此外,直到最近

cmd/compile
都无法正确处理非常大的指数;也就是说,任何实际程序(除了探索极端情况的测试)受到影响的可能性都接近于零。

最后,限制最小支持范围显着降低了这一领域的实现复杂性,而对于新的或替代的符合规范的实现来说,实际上这几乎不重要,这些实现不或不能依赖于支持 32 位指数范围的预先存在的任意精度算术包.

这在技术上是一种语言更改,但由于上述原因,这不太可能影响任何实际程序,当然也不会影响使用 gc 或 gccgo 编译器编译的程序,因为它们目前支持高达 32 位指数。

参见 issue 13572 提到:

在 Go 1.4 中,编译器拒绝了大于 10000 的指数(因为知道代码不适用于更大的指数),而用户没有任何抱怨。

在 Go 的早期版本中,大指数被默默地错误处理,同样没有受到用户的任何抱怨。

© www.soinside.com 2019 - 2024. All rights reserved.