JS 大数溢出问题

2023-10-18 10:21:24 +08:00
 justdoit123
后端的大数到 JS 端 JSON.parse 之后,经常大数溢出。

这个问题之前一直是在后端看到一处,就 stringify 一处。现在实在是觉得烦了,想请教下各位有什么更好的做法?

查了些资料,这个溢出是在 JS 进程里 JSON.parse 的时候发生的,跟 JS 自身有关系,跟 JSON 没有关系。想着,在 JSON.parse 的时候,换成 BigInt 的 JSON.parse 。但是这其中也有两种策略:

1. 只要是整数,全部转成 BigInt ,不管实际会不会溢出。这样的好处是统一,坏处怕会有什么性能问题
2. 只有当一个整数会溢出的时候,才会转成 BigInt 。但是这样用的时候还需要做下判断,比较麻烦。

暂时想在内部后台系统里尝试下,浏览器兼容不成问题。有没有别的高招?

PS. 这个真是 JS 的天坑。
3800 次点击
所在节点    JavaScript
47 条回复
realJamespond
2023-10-18 10:23:26 +08:00
让后端返回字符串再处理?
neotheone2333
2023-10-18 10:27:06 +08:00
返字符串,内部再转 Bignumber.js 处理
justdoit123
2023-10-18 10:27:14 +08:00
@realJamespond 就是不想再在后端转字符串了呀!

除非从 DB Access 层就把所有 bigint 都转成 string ,顺便好奇问下大家也是这么做的吗?像 timestamp 这种,在 db 里用 bigint 存储,但是使用的时候是实实在在要当数字使用的,如果转成 string 用的时候还要转回去。
debuggerx
2023-10-18 10:27:40 +08:00
这个很多语言都有类似的问题,最简单的还是让后端把可能溢出的字段用字符串类型传过来,前端自己转
zjsxwc
2023-10-18 10:31:28 +08:00
如下,JSON 里整数是 64 位的,但到了 js v8 里却不支持 64 位整数,目前主流 cpu 都是 64 位,64 位整数最大值是~(1<<63) = (1<<63) -1 = 9223372036854775807:

获得 json 格式的字符串
```
$ php -r "var_dump(json_encode(9223372036854775807));"
string(19) "9223372036854775807"
$ php -r "var_dump(json_encode(9223372036854775808));"
string(21) "9.223372036854776e+18"
```

js 解析就丢失精度了,9223372036854775807 变成了 9223372036854776000 ,最后几位全变 0 了:
```
JSON.parse("9223372036854775807")
9223372036854776000
```
cmdOptionKana
2023-10-18 10:32:02 +08:00
唉,基础知识不能叫做“天坑”吧,浮点数处理本来就有很多注意事项,编程语言是设计给“专家”使用的,本来就不是面向 end user 的。

如果不是金融相关的,多数情况下都可以降低精度,如果确实需要很高精度,那也只好麻烦一点处理了。
icoomn
2023-10-18 10:32:28 +08:00
之前也遇到这个问题,SQL 语句 select count() 查出来的数据默认就是 bigint 类型, 我是在后端直接做类型转换,将 bigint 转为 int 然后再返给前端的。

前端解决的话可以看下这个 JS 库:json-bigint
ZAnko
2023-10-18 10:35:41 +08:00
我们也是后端处理成字符串返回的,如果一定要前端处理,可以试试利用第三方库在相应拦截中统一处理掉。
iOCZ
2023-10-18 10:36:14 +08:00
一些第三方库(如 json-bigint )之所以能正确的处理大数 parse ,且不造成精度丢失,其实现原理也是类似。在拿到接口的 JSON 数据时,并不直接 JSON.parse ,而是先将整块数据当作 text 字符串,将其中的大数以 string 类型进行存储和标记,再使用定制化的 JSON.parse 。

自己处理的话,不外乎类似如此,可以单独抽取一个方法包裹 JSON.parse:
```javascript
var text = '{ "name":"Bill Gates", "birth":"1955-10-28", "city":"Seattle"}';
var obj = JSON.parse(text, function (key, value) {
if (key == "birth") {
return new Date(value);
} else {
return value;
}});
```
justdoit123
2023-10-18 10:37:48 +08:00
转成 string 给前端,前端送回给后端的时候,后端得再转回 int ( python 后端),现在其实就是这么做的。就是时不时会遗漏掉,而且这种问题是要等溢出你才会发现。基于此,想寻找一个更好的方案。
28Sv0ngQfIE7Yloe
2023-10-18 10:47:11 +08:00
我是后端,bigint 都是 parseString 给到前端的。
Terry166
2023-10-18 10:48:25 +08:00
npm install --save bn.js
webbillion
2023-10-18 10:49:32 +08:00
之前的方案是前端涉及数字都当 string 处理,后端也返回 string ,至于后端怎么处理可以避免忘记,不清楚后端怎么处理的,不知道 python 有没有前端 decimal.js 这种库,涉及数字全部用单独的库,而不是 原生 int ,也许有用?
mxT52CRuqR6o5
2023-10-18 10:52:42 +08:00
后端框架不能直接指定大数类型序列化成 string 吗?只能一处处手动改?
thinkershare
2023-10-18 10:56:49 +08:00
这个和 JS 没有一毛钱关系,你要怪只能怪 ECMA 规范和 IEEE64 浮点数规范,还有 JSON 规范。
justdoit123
2023-10-18 10:57:29 +08:00
@mxT52CRuqR6o5 可以呀。

问题是,不是所有的 bigint 转成 string 都能相安无事。

比如,如果这个数字是用来做 ID 之类的,那它是 string 也无所谓,因为很少会对 ID 做什么加减乘除的运算。

但是这个 bigint 可能是表示毫秒、表示钱,这时候转成 string 就很不方便。而且后端又不是只为 js 服务,还有 ios 跟 android 。
RedBeanIce
2023-10-18 11:00:45 +08:00
@mxT52CRuqR6o5

可以,但是最好不要这样子。

一个后端+略微入门前端的人认为,一个语言不支持大数,是不太合理的。
clue
2023-10-18 11:01:00 +08:00
json-bigint +1

有现成的库了,不需要自己去处理,前后端都换用`json-bigint`就解决了
debuggerx
2023-10-18 11:01:06 +08:00
该说的楼上都说得差不多了,再加一个后端死活就不改,前端又不好用库的时候的一个骚操作吧:

let jstr = '{"asd": 9223372036854775807}';
console.info(JSON.parse(jstr));
console.info(JSON.parse(jstr.replace(/\"asd\":[ ]?(-?[\d|\.]+)/, (ma, p) => ma.replace(p, `"${p}"`))));
console.info(JSON.parse('{"asd": -12345.6789}'.replace(/\"asd\":[ ]?(-?[\d|\.]+)/, (ma, p) => ma.replace(p, `"${p}"`))));
就是把 json 字符串先根据 key 把数字正则替换成字符串🐶

为了骚而骚,别用,除非是为了给别人埋坑~
jazzg62
2023-10-18 11:01:54 +08:00
我是手动解析了后端请求,从网上找了 JSON.parse 的 polyfill ,改了实现,对超过精度的数字项改成字符串

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/983013

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX