诡异的数值精度丢失

634 阅读3分钟

问题描述

在一次开发列表编辑页面时发现一个诡异的问题,列表展示的数据和回显的数据不一样,第一反应是接口出错了,查看了一下ajax请求,好像确实是这样:

列表请求接口:

列表查询请求

数据回显请求接口:

数据回显请求
数据回显请求

很明显列表数据和回显数据不一致,于是就找服务端同学排查接口问题。服务端同学反馈从日志上看是没问题的,对比他们日志,发现日志打印的数据和界面展示的不一致,诡异的事情就这样发生了。更诡异的是直接在浏览器地址栏访问这个接口,返回的数据和日志的一样,和我界面展示的也不一致。

地址栏请求数据

看样子应该是我项目中请求对数据做了处理,回过头再来看请求的数据,对比发现 driverId 数值,初步定为是因为数值类型精度丢失。

let num = 1001379549335920640
// 1001379549335920600

精度丢失原因

js数值类型遵循IEEE 754规范,占用64位,

s eeeeeee eeee ffff ffffffff ffffffff  ffffffff ffffffff ffffffff ffffffff

从左起

  • 1位用来表示符号
  • 11位用来表示指数
  • 52表示尾数

这种表示方法直接导致了精度丢失,当数值无法用二进制表示时,就会采取0舍1入。

浮点数和大整数都会出现精度丢失。当大整数尾数大于 2^52 = 9007199254740992 时就可能精度丢失

9007199254740992     >> 10000000000000...000 // 共计 53 个 0
9007199254740992 + 1 >> 10000000000000...001 // 中间 52 个 0
9007199254740992 + 2 >> 10000000000000...010 // 中间 51 个 0
9007199254740992 + 1 // 丢失
// 9007199254740992 
9007199254740992 + 2 // 未丢失
// 9007199254740994
9007199254740992 + 3 // 丢失
// 9007199254740996
9007199254740992 + 4 // 未丢失
// 9007199254740996

解决方案

  • 如果数值本身不参与运算,可以换成字符类型
  • 如果数值在常规范围内,可以先把小数放大到整数,运算后在缩小原来整数倍
// 0.1 + 0.2
(0.1*10 + 0.2*10) / 10 == 0.3 // true
  • 如果对精度有严格要求,可以自己实现大数运算,或借助npm包(例如big.js