【golang】关于float64的精度问题

1,223 阅读1分钟

现象

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的尾数位可以精确存储,因此没有精度问题。

参考

# 计算机的浮点数表示