大型数字精度转换丢失问题的解决
背景
在重构以前的 Java 版本的系统时发现,当时的 Java 开发者在对登陆用户的密码进行存储时采用了 BigInteger 方式存储 hash 加密后的 16 进制数字,并在最后存储时转换为了 32进制的数字。
Java代码:
1 | private String sha(String s) { |
而前端在转换精度时发现,一旦数字大于 2 ** 53(2 的 53 次方)则会出现精度丢失问题,无法完全重构以前的 Java 代码。
原理
Java 的 BigInteger 方法可以存储超大型数字, BigInteger 可以进行无限位的存储与运算,但是实际上受你的计算机内存和计算能力影响。
而 JavaScript 中的所能表示的 最大整数 为 Math.pow(2, 53) === 9007199254740992
在前端中进制转换的原理为,将当前进制通过 parseInt 转为 10 进制, 再通过 toString 转换为需要的进制,而 parseInt 将字符串转换成十进制数字也不能超出Math.pow(2, 53)
俗称安全数字。
比如说,当前的 16 进制数字为: 06d1f54860ed59aa95c9984b07e6a547fa690a26
,常规转换为 32进制的方法为:
1 | /// 先转换为 10 进制 |
可以看到转换后的结果为:r8vai30tlcs00000000000000000000
, 而以前的 Java 代码转换后的正确值为: r8vai30tlcql5e9j15gfpl58vt6i2h6
,使用 js 出现了精度丢失问题。
实现方法
在 JavaScript 原生方法中,有一个 bigInt 的方法,表示可以存储超出安全数字的方法:
BigInt is a built-in object that provides a way to represent whole numbers larger than 253, which is the largest number JavaScript can reliably represent with the Number primitive.
它在某些方面类似于 Number ,但是也有几个关键的不同点:不能和 Math 对象中的方法一起使用;不能和任何 Number 实例混合运算。
虽然不能混合运算,但它支持 toString 方法,可以实现大型数字的精度转换
所以最后的解决方案为:
- 通过 npm 包
big-number
对 16 进制的数字采取原生方法转换为 10 进制(不用 parseInt) - 将返回值传入 bigInt
- 调用 toString 方法实现精度转换
详细代码:
1 | const BigNumber = require('big-number') |
达到了我们预期的效果。
done!