导航
导航
文章目录
  1. 原因
  2. 解决方法

js处理long型丢失精度问题

最近项目后端为Prong开发了一个基于snowflake算法的Java分布式ID组件,将实体主键从原来的String类型的UUID修改成了Long型的分布式ID。修改后发现前端显示的ID和数据库中的ID不一致。例如数据库中存储的是:812782555915911412,显示出来却成了812782555915911400,后面2位变成了0,精度丢失了:

1
2
console.log(812782555915911412);
812782555915911400

原因

这是因为JavaScript中数字的精度是有限的,Java的Long类型的数字超出了JavaScript的处理范围。JavaScript内部只有一种数字类型Number,所有数字都是采用IEEE 754 标准定义的双精度64位格式存储,即使整数也是如此。这就是说,JavaScript 语言的底层根本没有整数,所有数字都是小数(64位浮点数)。其结构如图:

https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/long/long.png

各位的含义如下:

  • 1位(s) 用来表示符号位,0表示正数,1表示负数
  • 11位(e) 用来表示指数部分
  • 52位(f) 表示小数部分(即有效数字)

双精度浮点数(double)并不是能够精确表示范围内的所有数, 虽然双精度浮点型的范围看上去很大: 。 可以表示的最大整数可以很大,但能够精确表示,使用算数运算的并没有这么大。因为小数部分最大是 52 位,因此 JavaScript 中能精准表示的最大整数是 ,十进制即 9007199254740991

1
2
3
console.log(Math.pow(2, 53) - 1);
console.log(1L<<53);
9007199254740991

JavaScript 有所谓的最大和最小安全值:

1
2
3
4
console.log(Number.MAX_SAFE_INTEGER);
console.log(Number.MIN_SAFE_INTEGER);
9007199254740991
-9007199254740991

安全 意思是说能够 one-by-one 表示的整数,也就是说在范围内,双精度数表示和整数是一对一的,在这个范围以内,所有的整数都有唯一的浮点数表示,这叫做安全整数。

而超过这个范围,会有两个或更多整数的双精度表示是相同的;即超过这个范围,有的整数是无法精确表示的,只能大约(round)到与它相近的浮点数(说到底就是科学计数法)表示,这种情况下叫做不安全整数,例如:

1
2
3
4
5
6
7
8
9
console.log(Number.MAX_SAFE_INTEGER + 1); // 结果:9007199254740992,精度未丢失

console.log(Number.MAX_SAFE_INTEGER + 2); // 结果:9007199254740992,精度丢失

console.log(Number.MAX_SAFE_INTEGER + 3); // 结果:9007199254740994,精度未丢失

console.log(Number.MAX_SAFE_INTEGER + 4); // 结果:9007199254740996,精度丢失

console.log(Number.MAX_SAFE_INTEGER + 5); // 结果:9007199254740996,精度未丢失

JavaLong 类型的有效位数是63位(扣除一位符号位),其最大值为,十进制为9223372036854775807。

1
2
3
4
5
6
7
public static void main(String[] args) {
System.out.println(Long.MAX_VALUE);
System.out.println((1L<<63) -1);
}

9223372036854775807
9223372036854775807

所以只要java传给JavaScript的 Long 类型的值超过9007199254740991,就有可能产生精度丢失,从而导致数据和逻辑出错。

和其他编程语言(如 C 和 Java)不同,JavaScript 不区分整数值和浮点数值,所有数字在 JavaScript 中均用浮点数值表示,所以在进行数字运算的时候要特别注意精度缺失问题。容易造成混淆的是,某些运算只有整数才能完成,此时 JavaScript 会自动把64位浮点数,转成32位整数,然后再进行运算,由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心。

进一步阅读:JavaScript 教程 - 数据类型 - 数值

解决方法

解决办法就是让Javascript把数字当成字符串进行处理。对Javascript来说如果不进行运算,数字和字符串处理起来没有什么区别。当然如果需要进行运算,只能采用其他方法,例如JavaScript的一些开源库 bignum、bigint等支持长整型的处理。Java进行JSON处理的时候是能够正确处理long型的,只需要将数字转化成字符串就可以了。

支持一下
扫一扫,支持hzzly
  • 微信扫一扫
  • 支付宝扫一扫