登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go GJSON 大整数使用 Float 后为什么丢失精度

来源:17golang原创

时间:2026-10-13 16:07:40 243浏览 收藏

用 GJSON 读取订单号、用户 ID 或其他大整数时,直接调用 Float() 可能看到末尾数字变化。原因不是 GJSON 把 JSON 文本随机改写了,而是 Float() 返回的是 float64,大于 2^53 的整数无法全部用 IEEE-754 双精度浮点数精确表示。

官方地址:https://github.com/tidwall/gjson

如果字段是用于计算且能接受浮点语义,可以使用 Float();如果它是必须保真的编号,优先保留 Raw 或使用 String(),再按业务需要选择 Int() 或 Uint()。不要用浮点结果反推大整数原文。

先区分原始数字和浮点结果

GJSON 的 Result 同时保留了原始片段和数值字段。官方实现中,Raw 是原始 JSON 值的文本,Num 的类型是 float64;Float() 返回的正是这个浮点字段。于是同一个 JSON 数字会同时拥有“原文”和“数值近似”两条读取路径。

成员用途对大整数的含义
Raw保留 JSON 中的原始值文本适合继续做精确文本解析或记录
NumResult 内部的浮点数值受 float64 可表示精度限制
Float()返回浮点数与 Num 使用同一套精度边界
String()返回字符串表示整数原文保持数字字符时可保留原样
GJSON 原始 JSON 数字、Result Raw、Result Num、Float 和 2^53 精度边界的关系图
图1:GJSON 大整数从原始数字到 float64 的关系说明图,不是运行截图。

为什么 2^53 附近容易出现误差

float64 的有效整数精度边界通常用 2^53 表示。小于这个范围时,常见整数可以逐个表示;超过这个范围后,可表示的整数间隔会变大,两个相邻的十进制整数可能映射到同一个浮点值。

因此,下面这种现象并不矛盾:Result.Raw 仍然是原 JSON 中的数字文本,但 Result.Num 和 Float() 已经只能提供一个浮点近似值。精度损失发生在“把原文解析成浮点数”这一层,而不是发生在 JSON 字段定位上。

package main

import (
    "fmt"

    "github.com/tidwall/gjson"
)

func main() {
    jsonText := `{"order_id":9007199254740993}`
    result := gjson.Get(jsonText, "order_id")

    // Float 返回 float64;超过 2^53 的整数不能保证逐位精确。
    fmt.Printf("float=%v\n", result.Float())
    // Raw 保留 JSON 中的原始数字片段,适合继续做精确解析。
    fmt.Printf("raw=%s\n", result.Raw)
    // 整数形态的 String 可以作为不经过浮点格式化的文本读取入口。
    fmt.Printf("text=%s\n", result.String())
}

这里的重点不是某一台机器打印出什么具体末尾,而是读取 API 的语义不同:Float() 明确选择了浮点值,Raw 和整数形态的 String() 则能让后续逻辑继续接触数字文本。需要精确性时,应该把字段类型和读取方式一起设计。

用最小示例定位真正发生变化的路径

排查时可以把同一个 Result 的三种输出并列记录,不要只打印 Float():

func inspectNumber(jsonText string) {
    result := gjson.Get(jsonText, "order_id")

    // Type 用来确认 GJSON 识别到的是 Number,而不是字符串或 null。
    fmt.Println("type:", result.Type)
    // Raw 用来观察原始 JSON 数字,避免只看浮点格式化结果。
    fmt.Println("raw:", result.Raw)
    // Float 用来观察业务是否主动选择了 float64 语义。
    fmt.Println("float:", result.Float())
}

如果 Raw 中的十进制数字与 Float() 的输出不同,优先检查调用方是否把编号当成了测量值或比例值。订单号、雪花 ID、时间戳分位和外部系统主键通常不需要小数运算,使用浮点数只会增加转换风险。

按用途选择 Float、Int、Uint 与 Raw/String

GJSON 提供多个便捷方法,但它们不是同一个精度承诺。可以按字段用途做选择:

字段用途优先选择理由
展示或转发原始编号Raw 或 String()先保留数字字符,不主动经过浮点格式化
需要有符号整数运算Int()让 GJSON 按整数结果读取,并处理转换失败的边界
明确是非负计数Uint()表达无符号语义,避免把 ID 当作小数
金额比例、测量值或允许近似Float()字段本身需要浮点运算时再使用
GJSON 业务 ID 经过 Raw、Num 后选择 String、Int、Uint 或 Float 的关系图
图2:GJSON 大整数读取方式的选择关系说明图,不是运行截图。

如果业务要求比 int64 或 uint64 更大的整数范围,Raw 或 String() 只是保留文本,后续仍需交给合适的高精度整数库解析。这样可以把“JSON 定位”和“数值精度策略”分成两层,避免依赖 Float() 的近似结果。

大整数字段的选择清单

  1. 先确认字段是编号、计数,还是确实需要小数运算的测量值。
  2. 编号和主键优先观察 Raw,不要只记录 Float()。
  3. 需要整数运算时选择 Int() 或 Uint(),并处理转换边界。
  4. 需要保留原文时选择 String(),同时确认它确实是数字或字符串类型。
  5. 把大整数样例放进接口测试,比较原文与最终写回值,避免精度问题在下游才暴露。

常见问题

Float() 一定会把所有大整数改掉吗?

不一定。某些大整数恰好仍可表示,但不能把这种偶然结果当作通用保证。超过 2^53 后,应按字段需求选择精确文本或整数读取。

Raw 和 String() 有什么区别?

Raw 是 GJSON 结果里保存的原始 JSON 片段;String() 是便捷的字符串表示。对整数数字,GJSON 当前实现可以直接返回原始数字文本;如果字段类型复杂,仍要结合 Type 判断。

把大整数写成 JSON 字符串就不会丢精度吗?

字符串可以避开 JSON 数字到 float64 的转换,但它会改变接口字段类型。生产接口应先确认上下游约定,再决定使用字符串传输、整数解码还是保留原始数字文本。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>