返回

探索Go Json.Unmarshal精度丢失背后的秘密

后端

缘起

前不久,我遇到一个看似简单的需求,但在上线后却发现了一个意料之外的Bug。需求如下:

  • 上游系统会调用我的服务来获取全量信息,但上游的数据包虽然是JSON格式,但其结构并不固定。
  • 我的服务使用Go语言开发。

当我使用Json.Unmarshal()函数处理上游发来的数据时,我遇到了一个意想不到的问题——精度丢失。在本文中,我将详细分析该问题产生的原因,并提供解决方案,以帮助您避免类似问题的发生。

JSON反序列化简介

在探讨精度丢失的问题之前,我们先简单回顾一下JSON反序列化在Go语言中的实现原理。Json.Unmarshal()函数是Go语言标准库中的一个函数,用于将JSON数据反序列化为Go语言结构体。其基本步骤如下:

  1. 解析JSON数据,将其解析为一个由令牌构成的流。
  2. 将令牌流解析为一个抽象语法树(Abstract Syntax Tree, AST)。
  3. 根据AST生成Go语言结构体的实例。

在生成结构体实例的过程中,Json.Unmarshal()函数会根据JSON数据中的值类型和Go语言结构体字段的类型进行类型转换。如果JSON数据中的值类型与结构体字段的类型不一致,则需要进行类型转换。而这正是导致精度丢失问题的根源。

浮点数精度丢失

浮点数精度丢失是由于浮点数在计算机中存储时存在精度限制造成的。浮点数本质上是二进制的,而计算机在存储二进制数据时只能存储有限的位数。因此,当浮点数的值非常大或非常小时,就会发生精度丢失。

在Go语言中,浮点数的类型主要有两种:float32float64float32类型表示32位浮点数,可以存储大约7位有效数字;float64类型表示64位浮点数,可以存储大约15位有效数字。

当我们使用Json.Unmarshal()函数反序列化JSON数据时,如果JSON数据中的浮点数值超过了Go语言结构体字段的浮点数类型的精度范围,就会发生精度丢失。例如,如果JSON数据中的浮点数值为1.23456789,而结构体字段的类型为float32,那么反序列化后该字段的值将变为1.234568,因为float32类型只能存储7位有效数字。

解决方案

避免精度丢失问题的最佳解决方案是确保JSON数据中的浮点数值在Go语言结构体字段的浮点数类型的精度范围内。如果JSON数据中的浮点数值可能超过精度范围,则需要在反序列化之前对其进行处理。

有几种方法可以处理精度丢失问题:

  • 使用big.Float类型: big.Float类型是Go语言标准库中的一种高精度浮点数类型,可以存储任意精度的浮点数。如果需要处理精度要求很高的浮点数,可以使用big.Float类型。
  • 使用字符串类型: 如果精度要求不高,也可以将浮点数值存储为字符串类型。在反序列化时,将字符串类型转换为浮点数类型即可。
  • 使用自定义反序列化函数: 如果需要对精度丢失问题进行更细粒度的控制,可以编写一个自定义的反序列化函数。在自定义的反序列化函数中,可以对JSON数据中的浮点数值进行预处理,以确保其在Go语言结构体字段的浮点数类型的精度范围内。

结语

精度丢失是浮点数在计算机中存储时固有的问题。在使用Json.Unmarshal()函数反序列化JSON数据时,需要特别注意精度丢失问题。可以通过使用big.Float类型、字符串类型或自定义反序列化函数来避免精度丢失问题。