感谢各位的关注与建议。下午实践了下:
整个过程还算平滑。遇到一些需要手动处理的问题:
moment.unix(ts)
方法。在集成的地方,把这个方法替换成支持bigint的实现。因为只是后台系统,暂时没遇到什么性能问题,以后遇到了再来讨论想办法。这样改造后,溢出问题就不会污染到服务端实现,JS端也不用有特别的判断处理。
1
realJamespond 2023-10-18 10:23:26 +08:00
让后端返回字符串再处理?
|
2
neotheone2333 2023-10-18 10:27:06 +08:00
返字符串,内部再转 Bignumber.js 处理
|
3
justdoit123 OP @realJamespond 就是不想再在后端转字符串了呀!
除非从 DB Access 层就把所有 bigint 都转成 string ,顺便好奇问下大家也是这么做的吗?像 timestamp 这种,在 db 里用 bigint 存储,但是使用的时候是实实在在要当数字使用的,如果转成 string 用的时候还要转回去。 |
4
debuggerx 2023-10-18 10:27:40 +08:00
这个很多语言都有类似的问题,最简单的还是让后端把可能溢出的字段用字符串类型传过来,前端自己转
|
5
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 ``` |
6
cmdOptionKana 2023-10-18 10:32:02 +08:00
唉,基础知识不能叫做“天坑”吧,浮点数处理本来就有很多注意事项,编程语言是设计给“专家”使用的,本来就不是面向 end user 的。
如果不是金融相关的,多数情况下都可以降低精度,如果确实需要很高精度,那也只好麻烦一点处理了。 |
7
icoomn 2023-10-18 10:32:28 +08:00
之前也遇到这个问题,SQL 语句 select count() 查出来的数据默认就是 bigint 类型, 我是在后端直接做类型转换,将 bigint 转为 int 然后再返给前端的。
前端解决的话可以看下这个 JS 库:json-bigint |
8
ZAnko 2023-10-18 10:35:41 +08:00
我们也是后端处理成字符串返回的,如果一定要前端处理,可以试试利用第三方库在相应拦截中统一处理掉。
|
9
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; }}); ``` |
10
justdoit123 OP 转成 string 给前端,前端送回给后端的时候,后端得再转回 int ( python 后端),现在其实就是这么做的。就是时不时会遗漏掉,而且这种问题是要等溢出你才会发现。基于此,想寻找一个更好的方案。
|
11
28Sv0ngQfIE7Yloe 2023-10-18 10:47:11 +08:00
我是后端,bigint 都是 parseString 给到前端的。
|
12
Terry166 2023-10-18 10:48:25 +08:00 via iPhone
npm install --save bn.js
|
13
webbillion 2023-10-18 10:49:32 +08:00
之前的方案是前端涉及数字都当 string 处理,后端也返回 string ,至于后端怎么处理可以避免忘记,不清楚后端怎么处理的,不知道 python 有没有前端 decimal.js 这种库,涉及数字全部用单独的库,而不是 原生 int ,也许有用?
|
14
mxT52CRuqR6o5 2023-10-18 10:52:42 +08:00
后端框架不能直接指定大数类型序列化成 string 吗?只能一处处手动改?
|
15
thinkershare 2023-10-18 10:56:49 +08:00
这个和 JS 没有一毛钱关系,你要怪只能怪 ECMA 规范和 IEEE64 浮点数规范,还有 JSON 规范。
|
16
justdoit123 OP @mxT52CRuqR6o5 可以呀。
问题是,不是所有的 bigint 转成 string 都能相安无事。 比如,如果这个数字是用来做 ID 之类的,那它是 string 也无所谓,因为很少会对 ID 做什么加减乘除的运算。 但是这个 bigint 可能是表示毫秒、表示钱,这时候转成 string 就很不方便。而且后端又不是只为 js 服务,还有 ios 跟 android 。 |
17
RedBeanIce 2023-10-18 11:00:45 +08:00
|
18
clue 2023-10-18 11:01:00 +08:00
json-bigint +1
有现成的库了,不需要自己去处理,前后端都换用`json-bigint`就解决了 |
19
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 把数字正则替换成字符串🐶 为了骚而骚,别用,除非是为了给别人埋坑~ |
20
jazzg62 2023-10-18 11:01:54 +08:00
我是手动解析了后端请求,从网上找了 JSON.parse 的 polyfill ,改了实现,对超过精度的数字项改成字符串
|
21
coala 2023-10-18 11:09:48 +08:00
Long 类型是吧, 其实我觉得很多项目 Long 当主键听没必要的。
全局 Long 转 JSON 为 String 类型。 |
22
justdoit123 OP @coala 同意。不过遗留项目,已成定局。
|
23
cheng6563 2023-10-18 11:14:17 +08:00
你这样理解,js 里面数值只有 double 类型,double 自然是放不下 bigint 的数据的
|
24
justdoit123 OP @jazzg62 请问下,如果这个数字要回传给后端你们怎么处理?也是让后端在 server 拿到后转成数字吗?
|
25
cheng6563 2023-10-18 11:18:28 +08:00
后端如果用得只是标准的 JSON 转换库的话,用 Long 类型自然就会出问题。
|
26
justdoit123 OP js 这个“缺陷”的原因我知道。
话说,js 未来是否能支持真正的 int 类型,面量就写成 类似 `97i32` 或者 `97i`之类的? |
27
Martens 2023-10-18 11:26:18 +08:00
如果你后端用的 golang 的话,可以在相应的结构体 json tag 中添加 ,string 来实现序列化 json 时将 int64 转为字符串。
```go type T struct { ID int64 `json:id,string` } ``` |
28
jazzg62 2023-10-18 11:29:32 +08:00
@justdoit123 传字符串给后端,后端可以转换的
|
29
iMouseWu 2023-10-18 11:37:40 +08:00
@justdoit123 在 VO 层做一下转 String ,其实成本也还好。
|
30
fiveStarLaoliang 2023-10-18 11:59:28 +08:00
我都是序列化时把所有的数字转为字符串, 然后前端自己处理,这样就不会出现精度传着传着丢了的情况了
|
31
wusheng0 2023-10-18 12:03:24 +08:00
前端 axios 的话,可以自定义 parser ,然后用上面说的 json-bigint 。
可以全局,也可以单个函数,用到的地方解析一下。 ```typescript /** * 定义 parser */ export function bigIntTransformer(data: string) { const jsonBig = jsonBigint({ storeAsString: true }); try { return jsonBig.parse(data); } catch { return JSON.parse(data); } } /** * 接口使用 */ export function createAccountApi(reqVo: AccountReqVo) { return http.post(genApiUrl("/add"), reqVo, { transformRequest: bigIntTransformer, }); } ``` |
32
zhy0216 2023-10-18 12:22:00 +08:00 via Android
得用第三方 json parser 库
|
33
humbass 2023-10-18 12:48:49 +08:00
为避免此类问题,无论前后端,我们在计算后都以字符串的形式传递,包括前后端之间的交互
|
34
darkengine 2023-10-18 12:55:29 +08:00
先做出来再考虑性能问题吧
|
35
libook 2023-10-18 14:25:27 +08:00
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
JS 里用的 Number 是由 ECMAScript 规范定义好的,使用双精度浮点型,而双精度浮点型是 IEEE 754-2019 定义的,有精度边界。 用数字之前先看一下是不是超过了 MAX_SAFE_INTEGER 就行了(相应的还有 MIN_SAFE_INTEGER ),ES 和 JS 里面已经提供了这个常量可以用来对比。 前端用 double 类型,后端也用 double 类型才算是合适;相应的后端如果用 int64 ,前端也得用 bigint 。使用其他语言也是一样的问题,就好比用 C 写的客户端使用 double 类型与用 int64 的后端通信。归根结底是数据类型一致可以直接避免所有问题。 唯一的问题是 JSON 支持的数据类型有限,比如不支持 bigint ,所以就需要前后端换成其他兼容的类型(比如字符串)来使用 JSON 传输,或者干脆不用 JSON 换其他交换格式。 JSON.parse()支持传入 reviver 函数来对 k/v 进行处理 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#description 当然也可以找一些现成的支持 bigint 类型的 json 序列化和反序列化库。 |
36
hesetiema 2023-10-18 14:41:44 +08:00
JS 最大安全整数 9007199254740991 ,9 千万亿,一般金额/毫秒什么的,应该是够用的。接口传字符串就行,提交时后端再转回整数应该也不麻烦吧,9 楼说得很明白。
|
37
mxT52CRuqR6o5 2023-10-18 14:48:04 +08:00
JSON 最初设计的就是 JS 的子集,真要说谁的问题也是其他语言的问题,强行把超精度的数塞到 JSON 里了,甚至我见过后端框架虽然可以大数序列化不报错,但在序列化过程就已经产生精度问题了,前端拿到的就已经是错误的值
|
38
RedNax 2023-10-18 15:15:23 +08:00
@justdoit123
> 话说,js 未来是否能支持真正的 int 类型,面量就写成 类似 `97i32` 或者 `97i`之类的? 不会,因为已经被 typedArray/wasm/asm.js 支持了,不可能再增加一种基本类型。 |
39
Huelse 2023-10-18 15:18:50 +08:00
前端应该没有这种大数的计算操作吧?那就应该转为字符串,只是显示就好
这个所谓的天坑也是 js 的性能优势之一 |
40
mysunshinedreams 2023-10-18 16:10:25 +08:00
这个基本是初级工程师大概率会面对的问题,我们 C 段交互一般大概率是 string 了,要不然多端共用一套接口的话,指不定什么时候就出现各种千奇百怪的问题了
|
41
nianyu 2023-10-18 16:13:27 +08:00
@cmdOptionKana 大兄弟秀优越也讲个基本法,当个谜语人,又讲不出什么东西。 淘宝订单都是先转字符串在处理的,这都是业界常识,跟基础知识有半毛钱关系?
|
42
palytoxin 2023-10-18 17:19:06 +08:00
不要折腾 int,就用 string
|
43
IvanLi127 2023-10-18 17:26:56 +08:00
遇到大数不是第一时间用字符串或者自定义的格式传么?这要是天坑的话……那以后的路得多难走。
|
44
BurNIng1988 2023-10-18 18:02:22 +08:00
你如果在 v8 环境执行这个代码,就只能乖乖转字符串,如果后端不要字符串就走非 node 的 bff 再转一次
|
45
qeqv 2023-10-18 18:38:23 +08:00
以前我是后端自己实现了一个 stringify 解决这种不同 JSON 解析器出现的奇怪问题的 - -
|
46
justdoit123 OP 另外,纠正一下,溢出的是 JS Runtime ,不是 JSON 。在你把一个 int64 转成 JSON 格式的时候,它并没有“失真”,可以在其它支持 int64 的语言里试试。
之前看 Twitter 的前端代码的时候,偶然发现他们有 `id_str` 这样的字段,今天翻了下文档,果然是为了处理大数溢出问题。https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/tweet |
47
YuJianrong 2023-10-19 12:28:58 +08:00 via iPad
@justdoit123 这其实是历史问题。
当 Douglas Crockford 发明 JSON 的时候,其实只是为了 Javascript 方便用,就直接借用了 Javascript 定义 Object 的方式来制定 JSON 标准(所以 JSON 全称是 JavaScript Object Notation )。但他懒到根本没有按 Javascript 的 number 来定义 JSON 的 number 类型,JSON 的 number 在标准上并没有任何限制。在你的场景下你觉得 int64 的 JSON 数字在 JS 读不出正确精度是 bug ,但同样别的 app 也可以写一个 BigDecimal 转出来超过 int64 的大数,你的 app 同样不能正确读出来,也是 bug 。 所以为了互操作性,不管在什么环境下 JSON 遇到数字的时候都应该当作 double 来处理,这样最不容易产生问题。 ref: https://en.wikipedia.org/wiki/JSON -> interoperability |