Twlig / issuesBlog

MIT License
3 stars 0 forks source link

后端返回的64位大数,前端怎么处理精度丢失的问题 #72

Open Twlig opened 2 years ago

Twlig commented 2 years ago

双精度浮点数

JavaScript 采用双精度浮点数( IEEE 754 标准)来表示它的 Number 类型。一个数字占用 64 bits 存储空间(这里的每一位都只能存放 0 或 1):

image

最大安全整数

当表示整数时,js只使用52位尾数来表示,最大为2^53-1,即9007199254740991,超过这个数就会发生进位溢出等错误。

后端大数传输问题

而后端如果采用bigint 类型字段,在 MySQL 中,一个 bigint 存储占用 8 Bytes 的空间,即 64 bits。当取值为无符号整型时,能表示的范围是 0 到 2 的 64^-1,即 18446744073709551615

当取值在 (9007199254740991, 18446744073709551615] 之间时,后端程序通常能正确的执行 JSON 序列化操作,并通过 HTTP 接口返回给前端,而前端执行 JSON.parse 解码时,会因为语言本身的限制发生精度丢失,引发 bug。

大数转字符串类型

为了解决大数传递精度丢失的问题,常见的方案是“将大数转为字符串类型”。具体的做法如下:

一些第三方库(如 json-bigint)之所以能正确的处理大数 parse ,且不造成精度丢失,其实现原理也是类似。

类型语义丢失

上述处理其实会带来string和number类型混乱问题,无法得知当前传递的数据到底是以string类型,还是要以大数类型处理。

前端往后端 POST 数据时,有两种常见的编码形式 application/x-www-form-urlencodedapplication/json

当需要传递一个 number 类型的 id 给接口时,application/x-www-form-urlencoded 在 HTTP Request Body 中传输的是 id=1,而 application/json 的 Body 则是 {"id":1} 。之所以认为后者的语义更好,是因为后者能正确地反映出 id 的真实类型为 number。

而当这个 id 为 String 类型时,前者传输的依然是 id=1,后者则变为了 {"id":"1"}。对于后端程序来说,这层类型语义能让参数类型校验和计算更加准确和方便。

而如果前后端采用将“大数转为字符串”的方案,当 taskid 以 string 类型返回时,调用方将无法判断出它在业务和 DB 中到底是 char 字符类型存储的,还是 bigint 类型存储,导致类型语义丢失的情况发生。

参考文章:JSON Bigint大数精度丢失的背后