英文:
Why do float literals and variables give different results in Go?
问题
在下面的简单计算中,c
和d
最终得到了不同的值(它们之间相差一个比特位)。为什么会这样呢?
a := 4000.0
b := 1e-9
c := a / b
d := 4000.0 / 1e-9
这是因为在计算机中,浮点数的表示存在精度限制。在这种情况下,a / b
和4000.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
是表示为float64
的1e-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 "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)
}
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,"".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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论