js大数字精度丢失问题

5,042 阅读4分钟

起因

有一个数字104647149756747776。 一使用就会被js修改值,因为它超过了js的最大安全数。

image.png

可以看到,这个数值大于Number.MAX_SAFE_INTEGER两位。按照js的说法:

Number.MAX_SAFE_INTEGER 常量表示在 JavaScript 中最大的安全整数(maxinum safe integer)(253 - 1)。 这里安全存储的意思是指能够准确区分两个不相同的值,例如 Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2 将得到 true的结果,而这在数学上是错误的,参考Number.isSafeInteger()获取更多信息.

我的理解是大于这个数的数值,可能会被四舍五入,反正就是不保证它是正确的了。

需求

现在我需要把这样一个超长的数值通过ajax传递给后台,还需要保证精度。

先说结论 : 实现不了。期间我尝试使用bignumber.js 和 json-bigint两个库去处理数字,都能起到一定的作用,但是不能将数字原封不动的传递到后台。

bignumber

bignumber是一个封装库,它还是改变不了js number类型的最大值。也就是说它还是会被处理。不过bignumber可以帮你保留数字形式,不至于被转成科学计数法。


let a = new BigNumber(104647149756747776)
console.log(a.toFixed()) //  104647149756747780

let b = new BigNumber(111111111111111111111111111)
console.log(b.toFixed()) //  111111111111111100000000000

如果你的数值是小于Number.MAX_SAFE_INTEGER的,bignumber就很方便了,可以帮你处理小数和进行高精度的运算。

json-bigint

json-bigint是一个对json序列化,反序列化的库。它依赖了bignumber,同时还支持bigInt数据类型。

bigInt es10 增加的一种数字类型(大整数嘛),特点是大数字,缺点就是不能存小数,不能和number运算,不能被json库序列化,反序列化。

json-bigint就是用来对存在大整数的json进行序列化,反序列化的。我试了一下,可以正常处理104647149756747776这个长度的数字,不会丢精度。只是业务中咱也不能强制用户只能输入整数,然后我用的还是juery ajax。我在github上没找到通过js引入的方法,不过我用react项目尝试了一下,还是把案例放出来吧。

官方示例

``


var JSONbig = require('json-bigint');
var JSONstrict = require('json-bigint')({ strict: true });

var dupkeys = '{ "dupkey": "value 1", "dupkey": "value 2"}';
console.log('\n\nDuplicate Key test with both lenient and strict JSON parsing');
console.log('Input:', dupkeys);
var works = JSONbig.parse(dupkeys);
console.log('JSON.parse(dupkeys).dupkey: %s', works.dupkey);
var fails = 'will stay like this';
try {
  fails = JSONstrict.parse(dupkeys);
  console.log('ERROR!! Should never get here');
} catch (e) {
  console.log(
    'Succesfully catched expected exception on duplicate keys: %j',
    e
  );
}

// 这些事打印内容
Duplicate Key test with big number JSON
Input: { "dupkey": "value 1", "dupkey": "value 2"}
JSON.parse(dupkeys).dupkey: value 2
Succesfully catched expected exception on duplicate keys: {"name":"SyntaxError","message":"Duplicate key \"dupkey\"","at":33,"text":"{ \"dupkey\": \"value 1\", \"dupkey\": \"value 2\"}"}

我的示例


import React from "react";
import { Button } from "antd";
import jsonBig from 'json-bigint'

export default function Home(): JSX.Element {

  var json = '{ "value" : 104647149756747776, "v2": 123 }';
  var json2 = '{ "value" : 10464714975674742342342423776, "v2": 123 }';
  console.log('Input:', json);
  console.log('');

  console.log('node.js built-in JSON:');
  var r = JSON.parse(json);
  console.log('JSON.parse(input).value : ', r.value.toString());
  console.log('JSON.stringify(JSON.parse(input)):', JSON.stringify(r));

  console.log('\n\nbig number JSON:');
  var r1 = jsonBig.parse(json);
  var r2 = jsonBig.parse(json2);
  console.log('JSONbig.parse(input).value : ', r1.value.toString());
  console.log('JSONbig.stringify(JSONbig.parse(input)):', jsonBig.stringify(r1));


  console.log('r2  JSONbig.parse(input).value : ', r2.value.toString());
  console.log('r2 JSONbig.stringify(JSONbig.parse(input)):', jsonBig.stringify(r2));


  return <Button> Home页面</Button>;
}

image.png

可以看到对数字104647149756747776 的处理很正常,然后我随意输入的超长数字被转化为科学计数法了。

我的疑问

我对前端了解不太多,网上也没找到将科学计数法形式的数字转为number,还不丢失精度的。不知道能不能转回来。

其他解决办法

现在用超长数字的,大部分是雪花算法生成的id。然后普遍的解决办法就是前端换成字符串,后端再转换为数字。

》可以看看这个 牛气的JavaScript,让雪花算法成为空气