javascritp 双精度浮点数 精度问题

遇到的问题

今天遇到一个问题,场景如下:
后端返回一个 uuid 为 1763118905011392500 ,我带 1763118905011392500 去查数据时,接口报错提示: uuid 不存在,我问后端怎么回事,后端查了一下这个 uuid,发现数据库确实不存在值为 1763118905011392500 的 uuid。

谷歌了一下,发现是 javascript 使用双精度浮点数表示数字,最大安全整数是 2^53 - 1 ,即 9007199254740991

浏览器中可以接收的值的大小取决于浏览器的 javascript 引擎的实现。大多数浏览器都遵循 javascript 标准。

当浏览器接收到大于最大安全整数的值时,超出这个范围的整数值将会失去精度,可能会被舍入。

也就是说,后端给我的值大于 2^53-1 ,浏览器将丢失精度的值保存了下来,前端将丢失精度的值传递给后端去查询数据时,那必然找不到该值。

找到了原因后,接下来了解下 双精度浮点数 ,以及如何解决这个问题。

双精度浮点数

浮点数是什么?

在 JavaScript 中,浮点数是一种用于表示小数的数据类型。浮点数采用 IEEE 754 标准表示,通常被称为双精度浮点数,即使用 64 位(8 字节)来表示一个浮点数。这意味着无论数字的大小如何,它们在内存中占用的空间都是相同的,即 64 位或者 8 个字节。
这种格式可以表示小数(小数是指非整数的有理数,其中包含了小数点及其后面的数字部分)部分,而不仅仅是整数部分,因此可以用来存储小数值。

双精度浮点数是什么?

双精度浮点数是一种用于表示实数的数字表示方法,它在计算机科学中被广泛使用。它的名称中的“双精度”表示它的存储空间是单精度浮点数(32 位)的两倍,因此可以存储更大范围和更高精度的数字。
具体来说,双精度浮点数使用 64 位(8 字节)来存储一个数字,这 64 位被划分为三部分:

  1. 符号位(1 位):用于表示数字的正负。
  2. 指数部分(11 位):用于表示数字的指数部分,决定了数字的数量级。
  3. 尾数部分(52 位):用于表示数字的小数部分,决定了数字的精度。

在科学计数法中,一个数字通常表示为 M × 10^E 的形式,其中 M 是尾数(即小数部分),E 是指数(表示这个数需要移动的小数点位数)

64 位(8 字节)是什么意思?

64 位(8 字节)是指在计算机中用来表示数据的一种存储方式。它表示一个数据单元占用的存储空间大小。具体来说,64 位意味着这个数据单元可以存储 64 位二进制数字,即包含了 64 个二进制位。

1
2
3
4
5
6
7
Bit-比特 Byte-字节 KB-千字节 MB-兆字节 GB-吉字节 TB-太字节

1 Byte = 8 Bits
1 GM = 1024 MB
1 TB = 1024 GB

一个二进制位的大小称为一比特,也就是最小单位

解决方法

js 有没有什么办法可以表示大于 2^53 - 1 的数呢?有的,就是 BigInt 类型,它可以表示任意大的整数。

要表示 2^53,可以直接使用 BigInt 类型的字面量赋值,如下所示:

1
2
3
4
const bigIntNumber = 2n ** 53n;
console.log(bigIntNumber); // 输出:9007199254740992n

console.log(typeof bigIntNumber); // 'bigint'

这里的 2n 表示一个 BigInt 数字 2,** 表示幂运算符,53n 表示一个 BigInt 数字 53。

但在因为 Java 原生不支持 BitInt 类型,所以不能使用 BitInt 来处理。

所以解决方案有以下两种:

  1. 将 uuid 改为 2^53-1 下的值,也就是小于等于 15 位数
  2. 或者将 uuid 字符串化,如'1763118905011392500'

小结

js 标准中,使用双精度浮点数表示数字,最大安全整数为 2^53 - 1。js number 类型无法精准表示超出最大安全数的值,可用 BigInt 类型表示。如果后端给的数值超出最大安全值,可以采用”字符串化”方案,解决前后端交互时精度丢失的问题。

思考:
3 和 99 占用的存储空间大小一样吗?

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×