为什么在Go语言中,浮点字面量和变量会给出不同的结果?

huangapple go评论77阅读模式
英文:

Why do float literals and variables give different results in Go?

问题

在下面的简单计算中,cd最终得到了不同的值(它们之间相差一个比特位)。为什么会这样呢?

a := 4000.0
b := 1e-9
c := a / b
d := 4000.0 / 1e-9

这是因为在计算机中,浮点数的表示存在精度限制。在这种情况下,a / b4000.0 / 1e-9的计算结果可能会受到浮点数精度误差的影响,导致最终的结果略有不同。这是由于浮点数的二进制表示无法精确地表示某些小数,因此会引入舍入误差。

英文:

In the simple calculations below, c and d end up with different values (they're off by a single bit). Why is that?

a := 4000.0
b := 1e-9
c := a / b
d := 4000.0 / 1e-9

答案1

得分: 6

a被赋予了下一个最接近4000.0的值,表示为float64,即0x40AF400000000000,对应于精确的4000.0

b是表示为float641e-9的下一个最接近值,即0x3E112E0BE826D695,对应于1.00000000000000006228159145778E-9。在这里你失去了一些精度。

因此,在计算c之前,你已经有了一点不精确。然后,在实际计算c时,你会因为四舍五入而失去更多的精度。

对于d,只进行了一次“下一个最接近”的操作;将编译时计算的完全精确值4000.0 / 1e-9表示为float64

关于变量的一点说明:在C(和可能是C++)中,默认假设变量是非易失性的,即不共享的,因此优化通常会深入地跟踪常量表达式,用计算出的值替换所有内容。Go不对变量的作用域做任何假设,因此不保证替换常量表达式。这可能会在将来发生变化,但是今天,在这方面没有做太多工作,因此很可能c实际上是在运行时计算的,而不是像C语言那样在编译时计算。

编辑:我使用了@topskip提供的程序:

package main

import "fmt"

func main() {
    a := 4000.0
    b := 1e-9
    c := a / b
    d := 4000.0 / 1e-9
    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
    fmt.Println(d)
}

这是由go tool 6g -S生成的一部分汇编代码:

0x0021 00033 (meh.go:6)    MOVSD    $f64.40af400000000000+0(SB),X2
0x002a 00042 (meh.go:7)    MOVSD    $f64.3e112e0be826d695+0(SB),X3
0x0033 00051 (meh.go:8)    MOVAPD    X2,X0
0x0037 00055 (meh.go:8)    MOVSD    X3,"".b+64(SP)
0x003d 00061 (meh.go:8)    DIVSD    X3,X0

如你所见,c是在运行时根据我描述的两个常量计算出来的。

英文:

a is assigned the next-best value for 4000.0 represented as a float64, which is 0x40AF400000000000 and corresponds to exactly 4000.0.

b is the next-best value for 1e-9, represented as a float64, which is 0x3E112E0BE826D695 and corresponds to 1.00000000000000006228159145778E-9. You lost precision here.

So before computing c you already have a slight imprecision. Then when c is actually calculated you lose some more precision to rounding.

In the case of d, there is only one "next-besting" going on; when representing the compile-time calculated full-precision value of 4000.0 / 1e-9 as a float64.

An aside about variables: In C (and presumably C++), the default is to assume a variable is non-volatile i.e. not shared and hence optimizations often follow constant-expressions deeply, replacing everything with computed values. Go makes no assumptions about the scope of variables and hence no guarantees about replacing constant expressions. This may change in the future but today, not much is done in that department so it's quite possible that c is actually calculated at runtime, not at compile-time as someone used to C might think.

<hr>

Edit: I have taken the program provided by @topskip:

package main

import &quot;fmt&quot;

func main() {
	a := 4000.0
	b := 1e-9
	c := a / b
	d := 4000.0 / 1e-9
	fmt.Println(a)
	fmt.Println(b)
	fmt.Println(c)
	fmt.Println(d)
}

And this is part of the assembly generated by go tool 6g -S:

0x0021 00033 (meh.go:6)	MOVSD	$f64.40af400000000000+0(SB),X2
0x002a 00042 (meh.go:7)	MOVSD	$f64.3e112e0be826d695+0(SB),X3
0x0033 00051 (meh.go:8)	MOVAPD	X2,X0
0x0037 00055 (meh.go:8)	MOVSD	X3,&quot;&quot;.b+64(SP)
0x003d 00061 (meh.go:8)	DIVSD	X3,X0

As you can see; c is calculated at runtime based on the two constants I describe.

huangapple
  • 本文由 发表于 2014年10月8日 20:18:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/26256726.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定