导航
导航
文章目录
  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
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 型的,只需要将数字转化成字符串就可以了。