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 位):用于表示数字的正负。
- 指数部分(11 位):用于表示数字的指数部分,决定了数字的数量级。
- 尾数部分(52 位):用于表示数字的小数部分,决定了数字的精度。
在科学计数法中,一个数字通常表示为 M × 10^E 的形式,其中 M 是尾数(即小数部分),E 是指数(表示这个数需要移动的小数点位数)
64 位(8 字节)是什么意思?
64 位(8 字节)是指在计算机中用来表示数据的一种存储方式。它表示一个数据单元占用的存储空间大小。具体来说,64 位意味着这个数据单元可以存储 64 位二进制数字,即包含了 64 个二进制位。
1 | Bit-比特 Byte-字节 KB-千字节 MB-兆字节 GB-吉字节 TB-太字节 |
解决方法
js 有没有什么办法可以表示大于 2^53 - 1 的数呢?有的,就是 BigInt 类型,它可以表示任意大的整数。
要表示 2^53,可以直接使用 BigInt 类型的字面量赋值,如下所示:
1 | const bigIntNumber = 2n ** 53n; |
这里的 2n 表示一个 BigInt 数字 2,** 表示幂运算符,53n 表示一个 BigInt 数字 53。
但在因为 Java 原生不支持 BitInt 类型,所以不能使用 BitInt 来处理。
所以解决方案有以下两种:
小结
js 标准中,使用双精度浮点数表示数字,最大安全整数为 2^53 - 1。js number 类型无法精准表示超出最大安全数的值,可用 BigInt 类型表示。如果后端给的数值超出最大安全值,可以采用”字符串化”方案,解决前后端交互时精度丢失的问题。
思考:
3 和 99 占用的存储空间大小一样吗?