避坑 | 记一次前端长整数精度丢失问题
孽起
@RestController
@RequestMapping("/test")
public class YupiTestController {
@GetMapping
public Long getNum() {
return 123456789123456789L;
}
}
但是前端请求这个接口后,在界面上展示的却是 123456789123456780,最后一位是0而不是9!
问题定位
后端同学利用curl工具测试自己的接口,得到的数据完全正确。
前端同学打开浏览器的开发者工具(F12)查看网络请求(注意要查看请求原生的返回值,而不是被浏览器二次处理过的格式化数据),发现后端返回的数据完全正确。
既然后端数据返回正确,那就是前端的锅没跑了。
可是前端明明拿到后端返回的json数据,解析成数字就直接展示了,为什么最后一位数字展示错误呢?
发现元凶
比对分析接口返回和前端展示的数据,发现只有数字超过16位时,才会出现最后几位数字不一致的问题。
难道是数字太大了,发生了精度丢失?
Java语言中的Long类型是64位,难道前端Js语言的Long类型小于64位?
等等,Js好像没有Long类型!
那就百度一下Js的数字类型,终于发现了问题的元凶。
Js的Number类型
在Js中,用Number来表示数字类型的值。Number类型总长度64位二进制bit,使用53位表示小数位,10 位表示指数位,1 位表示符号位。因此,Number整数的表示范围为 -2^53 ~ 2^53(不包含两端)。
可以在控制台打印Number的最大和最小值:
Number最大值
Number最小值
在其他语言,如Java中,Long类型占64位二进制bit,最大值为:9223372036854774807(2^63 - 1)长度约19位。
而在Js中,由于Number类型的值也包含了小数,最大值为:9007199254740993(2^53 - 1)长度约16位。
既然知道了出现问题的原因,解决问题就很简单了。
如何解决?
虽然前端也可以解决问题,比如通过正则表达式解析替换、或者修改json parser,但比较麻烦,更推荐在后端解决。
非常简单,将可能超出范围的数字类型(Long)变量转为字符串类型(String)即可!