最近在做收银台业务,成功下单后,后端需要返回给前端订单号,前端根据订单号去跳转收银台,然后付款。但是碰到个奇怪的问题,一致订单异常。
一开始,我觉得订单异常,肯定是后端某个环节有问题,我前端就负责拿订单号去跳转收银台,能出什么问题?
我指着下单接口,“你看,你接口给我的订单号是 201808221019001800
”,又指着收银台接口,“你看,我传给收银台的订单号是 201808221019001800
”,这订单异常,肯定是后端有问题啊!
后端调试了半天,找不到原因,“我这都查过了,我写的代码都是对的啊!不过为什么你的订单号是 201808221019001800,我后台查不到这个订单啊!而且你的 uid 是 10007777,订单尾号应该是 1777 才对,我订单号取的逻辑是 ‘年月日时分秒’ 再加 uid 的第一位和后三位!是不是你那边请求问题啊”。
我心里呵呵一笑,我就请求个接口,还能请求出问题?我前端就负责调接口,你接口返回给我什么,我就展示什么,我还能改你返回的数据?我又没拦截!
我去后台查了一下订单,我特么发现后台记录的那条订单,真是 201808221019001777
。但到底为啥给我的就是 201808221019001800
呢?虽然不知道什么原因,但我不管,一定是后端处理错了!
后端说,那好,我在返回之前打个日志,我看看我给你的到底是什么,你调调看。
试了一下,他打出的是 201808221019001777
,我拿到的是 201808221019001800
。呐呢?
后端说那肯定是你调用的问题吧,我用 postman
试了试,我调出来的也是 201808221019001777
。这越发让我疑惑。
于是我也用 postman
调用,特么真的是 201808221019001777
,我又在 chrome
控制台调用,特么竟然又是 201808221019001800
,什么鬼哦?还有这种操作?
突然鬼使神差地,我在 chrome
控制台输入了 201808221019001777
,按下 return
键,特么返回了 201808221019001800
。
尼玛!原来是浏览器处理了!
接口给的 201808221019001777
是个 Number
类型,超过了浏览器(js)能处理的最大安全整数!
解决方案就是接口转化为 String
类型返回。
最大安全整数、最小安全整数
js 最大安全整数是 Number.MAX_SAFE_INTEGER
,最小安全整数是 Number.MIN_SAFE_INTEGER
,
1 | Number.MAX_SAFE_INTEGER // 9007199254740991 |
2 的 53 次方
js 安全整数的范围是 (Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER) 即 -2^53~2^53 (不包含边界) 。
安全整数,意思是说能够 one-by-one 表示的整数,也就是说在(-2^53, 2^53)范围内,双精度数表示和整数是一对一的,反过来说,在这个范围以内,所有的整数都有唯一的浮点数表示,这叫做安全整数。超过这个范围,会有两个或更多整数的双精度表示是相同的;反过来说,超过这个范围,有的整数是无法精确表示的,只能round到与它相近的浮点数(说到底就是科学计数法)表示,这种情况下叫做不安全整数。
1 | Math.pow(2, 53) // 9007199254740992 |
当运算数与运算结果都处于安全整数的范围内时,才能保证 js 运算结果正确。
请求接口中返回一个整数,例如订单号,是个不安全整数,就会导致前端处理异常!
1 | 201808221019001777 // 被处理成 201808221019001800 |
js 安全整数的范围为啥是 (Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER) 即 -2^53~2^53 (不包含边界) ?
js 里数字类型只有一种,Number 类型,是双精度浮点型,都是 64-bit (1bit 的符号位,11bits 的指数部分,以及 52bits 的小数部分) 的双精度浮点数(double)!
js 里的整型 int 是 双精度浮点型 double 的一个子集,而不是一个独立的数据类型。
由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心!
引申问题:
1 | typeof 1 // number |