4

这是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. 实现更准确的精度证明这种方法是合理的吗?

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

4

2 回答 2

5
  1. 常数

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

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

  4. 是的,见 1。

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

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

于 2013-08-05T11:25:24.677 回答
0

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

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

语言规范现在只要求实现支持浮点常量中最多 16 位的指数。
这不会影响“<a href="https://beta.golang.org/cmd/compile/" rel="nofollow noreferrer"> gc”或gccgo编译器,它们仍然支持 32 位指数。

那来自变化17711

spec: 需要 16 位的最小指数,而不是 32

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

此外,直到最近cmd/compile还不能正确处理非常大的指数;即,任何实际程序(除了探索极端情况的测试)受到影响的机会接近于零。

最后,限制最小支持范围显着降低了实现复杂性,这对于不依赖或不能依赖支持 32 位指数范围的预先存在的任意精度算术包的新的或替代的规范兼容实现在现实中几乎不重要。

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

请参阅issue 13572提到:

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

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

于 2016-12-03T01:46:21.157 回答