现象
func main() {
x := uint64(1)
for i := 0; i < 53; i++ {
x = x * 2
}
fmt.Println("2^53 =", x)
xStr := strconv.FormatUint(x, 10)
fmt.Println("len(2^53) =", len(xStr))
xAdded1 := x + 1
fmt.Println("2^53 + 1 =", xAdded1) // 9007199254740993
fmt.Println("float64(2^53 + 1) =", float64(xAdded1)) // 9.007199254740992e+15,出现了精度问题
xAdded2 := x + 2
fmt.Println("2^53 + 2 =", xAdded2) // 9007199254740994
fmt.Println("float64(2^53 + 2) =", float64(xAdded2)) // 9.007199254740994e+15,没有出现精度问题
fmt.Println("math.MaxInt64 =", int64(math.MaxInt64))
fmt.Println("float64(math.MaxInt64) =", float64(math.MaxInt64)) // 精度问题
fmt.Println("math.MaxUint64 =", uint64(math.MaxUint64))
}
运行结果:
2^53 = 9007199254740992
len(2^53) = 16
2^53 + 1 = 9007199254740993
float64(2^53 + 1) = 9.007199254740992e+15
2^53 + 2 = 9007199254740994
float64(2^53 + 2) = 9.007199254740994e+15
math.MaxInt64 = 9223372036854775807
float64(math.MaxInt64) = 9.223372036854776e+18
math.MaxUint64 = 18446744073709551615
分析:
可以看到float64
无法精确存储2^53 + 1
,但能精确存储2^53 + 2
,为什么?
首先,float64
的尾数位有52位,尾数的最大长度只能是52+1=53
位,尾数长度超过53就无法精确存储,会存在精度问题。
无法精确存储2^53+1
:
2^53+1
的二进制是10000000....0001(中间有52个0)
,根据IEEE标准则是(-1)^0 * 1.000000000...01(中间有52个0) * 2^53
,尾数长度是54,超过了53,因此float64
无法存储第54位,只能舍去最后的1,所以存在精度问题。
能精确存储2^53+2
:
2^53+2
的二进制是1000000...010(中间51个0)
,根据IEEE标准则是(-1)^0 * 1.00000000...01(中间是51个0)* 2^53
,尾数长度是53,float64
的尾数位可以精确存储,因此没有精度问题。