golang,go,博客,开源,编程

在 Golang 中使用 float64 可能导致精度丢失的情况

Updated on with 0 views and 0 comments

在 Golang 中使用 float64 类型时,精度丢失主要发生在以下场景中。

根本原因是 IEEE 754 双精度浮点数标准的固有特性:


一、无法精确表示的十进制小数
原理:某些十进制小数(如 0.10.2)无法用有限的二进制浮点数精确表示,导致存储时被截断或四舍五入。
示例:

var a float64 = 0.1
var b float64 = 0.2
fmt.Println(a + b) // 输出 0.30000000000000004 而非 0.3

• 原因:0.1 的二进制表示为无限循环小数 0.0001100110011...,而 float64 的尾数位仅 52 位,超出部分会被截断。


二、大整数或数值范围差异大的运算
原理:当数值超过 2^53(约 9e15)时,连续整数无法被准确表示,且大数和小数相加时可能发生“大数吃小数”现象。
示例:

fmt.Println(float64(9007199254740993)) // 输出 9.007199254740992e15(精度丢失)
fmt.Println(1e16 + 1.0 == 1e16)        // 输出 true(小数被忽略)

• 关键点:float64 的尾数位仅能精确表示最多 16 位十进制整数。


三、JSON 序列化时的默认行为
原理:Golang 的 json.Marshal 默认以最简形式输出浮点数,导致末尾零丢失(如 19.90 变为 19.9)。
示例:

type Product struct {
Price float64 json:"price"
}
p := Product{Price: 19.90}
jsonData, _ := json.Marshal(p)
fmt.Println(string(jsonData)) // 输出 {"price":19.9}

• 解决方案:通过自定义类型重写 MarshalJSON 方法强制保留两位小数。


四、连续浮点运算的误差累积
原理:多次运算后微小误差会逐步放大。
示例:

var sum float64
for i := 0; i < 10; i++ {
sum += 0.1
}
fmt.Println(sum) // 输出 0.9999999999999999(而非 1.0)

五、强制类型转换
原理:将高精度数值(如 int64decimal 类型)强制转换为 float64 时可能丢失精度。
示例:

x := int64(9223372036854775807)
fmt.Println(float64(x)) // 输出 9.223372036854776e18(精度丢失)

那么,如何避免精度丢失?

  1. 使用高精度库:如 github.com/shopspring/decimal 处理金融或科学计算。
  2. 整数替代法:将金额转换为分单位(如用 int 表示分)。
  3. 自定义 JSON 序列化:强制保留小数点后两位。
  4. 避免直接比较浮点数:改用误差范围(如 math.Abs(a-b) < 1e-9)。

标题:在 Golang 中使用 float64 可能导致精度丢失的情况
作者:mooncakeee
地址:http://blog.dd95828.com/articles/2025/05/09/1746752607458.html
联系:scotttu@163.com