`

JavaScript 中小数和大整数的精度丢失

阅读更多

先来看两个问题:

0.1 + 0.2 == 0.3; // false
9999999999999999 == 10000000000000000; // true

第一个问题是小数的精度问题,在业界不少博客里已有讨论。第二个问题,去年公司有个系统的数据库在做数据订正时,发现有部分数据重复的诡异现象。本文将从规范出发,对上面的问题做个小结。

 

最大整数

JavaScript 中的数字是用 IEEE 754 双精度 64 位浮点数 来存储的,其格式为:

s x m x 2^e

s 是符号位,表示正负。 m 是尾数,有 52 bits. e 是指数,有 11 bits. 在 ECMAScript 规范 里有给出 e 的范围为 [-1074, 971]. 这样,很容易推导出 JavaScript 能表示的最大整数为:

1 x (2^53 - 1) x 2^971 = 1.7976931348623157e+308

这个值正是 Number.MAX_VALUE

同理可推导出 Number.MIN_VALUE 的值为:

1 x 1 x 2^(-1074) = 5e-324

注意 MIN_VALUE 表示最接近 0 的正数,而不是最小的数。最小的数是 -Number.MAX_VALUE

小数的精度丢失

十进制 0.1 的二进制为 0.0 0011 0011 0011 … (循环 0011)
十进制 0.2 的二进制为 0.0011 0011 0011 … (循环 0011)

0.1 + 0.2 相加可表示为:
   e = -4; m = 1.10011001100...1100(52 位)
 + e = -3; m = 1.10011001100...1100(52 位)
---------------------------------------------
   e = -3; m = 0.11001100110...0110
 + e = -3; m = 1.10011001100...1100
---------------------------------------------
   e = -3; m = 10.01100110011...001
---------------------------------------------
 = 0.01001100110011...001
 = 0.30000000000000004(十进制)

根据上面的演算,还可以得出一个结论:当十进制小数的二进制表示的有限数字不超过 52 位时,在 JavaScript 里是可以精确存储的。比如:

0.05 + 0.005 == 0.055 // true

进一步的规律,比如:

0.05 + 0.2 == 0.25 // true
0.05 + 0.9 == 0.95 // false

需要考虑 IEEE 754 的 Rounding modes, 有兴趣的可进一步研究。

大整数的精度丢失

这个问题鲜有人提及。首先得弄清楚问题是什么:

1. JavaScript 能存储的最大整数是什么?

该问题前面已回答,是 Number.MAX_VALUE, 非常大的一个数。

2. JavaScript 能存储的且不丢失精度的最大整数是什么?

根据 s x m x 2^e, 符号位取正,52 位尾数全填充 1, 指数 e 取最大值 971, 显然,答案依旧是 Number.MAX_VALUE.

我们的问题究竟是什么呢?回到起始代码:

9999999999999999 == 10000000000000000; // true

很明显,16 个 9 还远远小于 308 个 10. 这个问题与 MAX_VALUE 没什么关系,还得归属到尾数 m 只有 52 位上来。

可以用代码来描述:

var x = 1; // 为了减少运算量,初始值可以设大一点,比如 Math.pow(2, 53) - 10
while(x != x + 1) x++;
// x = 9007199254740992 即 2^53

也就是说,当 x 小于等于 2^53 时,可以确保 x 的精度不会丢失。当 x 大于 2^53 时,x 的精度有可能会丢失。比如:

x 为 2^53 + 1 时,其二进制表示为:
10000000000...001 (中间共有 52 个 0)

用双精度浮点数存储时:
e = 1; m = 10000..00(共 52 个 0,其中 1 是 hidden bit)

显然,这和 2^53 的存储是一样的。

按照上面的思路可以推出,对于 2^53 + 2, 其二进制为 100000…0010(中间 51 个 0),也是可以精确存储的。

规律:当 x 大于 2^53 且二进制有效位数大于 53 位时,就会存在精度丢失。这和小数的精度丢失本质上是一样的。

hidden bit 可参考:A tutorial about Java double type.

小结

小数和大整数的精度丢失,并不仅仅在 JavaScript 中存在。严格来说,使用了IEEE 754 浮点数格式来存储浮点类型的任何编程语言(C/C++/C#/Java 等等)都存在精度丢失问题。在 C#、Java 中,提供了 Decimal、BigDecimal 封装类来进行相应的处理,才避开了精度丢失。

注:ECMAScript 规范中,已有 decimal proposal,但目前尚未被正式采纳。

最后考考大家:

Number.MAX_VALUE + 1 == Number.MAX_VALUE;
Number.MAX_VALUE + 2 == Number.MAX_VALUE;
...
Number.MAX_VALUE + x == Number.MAX_VALUE;
Number.MAX_VALUE + x + 1 == Infinity;
...
Number.MAX_VALUE + Number.MAX_VALUE == Infinity;

// 问题:
// 1. x 的值是什么?
// 2. Infinity - Number.MAX_VALUE == x + 1; 是 true 还是 false ?
参考资料
分享到:
评论

相关推荐

    浅谈JavaScript中小数和大整数的精度丢失_.docx

    综上所述,JavaScript中的小数和大整数精度丢失是由于IEEE 754双精度浮点数格式的限制。理解这个问题对于编写高精度计算或金融类应用至关重要,因为在这些场景下,即使微小的精度差异也可能导致严重后果。为确保精确...

    浅谈JavaScript中小数和大整数的精度丢失

    然而,由于双精度浮点数的结构限制,JavaScript中的小数(尤其是不能用有限二进制精确表示的小数,如0.1和0.2)和大整数(超过52位的二进制数)常常面临精度丢失的问题。 对于小数的精度丢失问题,由于尾数位数有限...

    javascript解决小数的加减乘除精度丢失的方案

    在JavaScript开发中,处理小数加减乘除运算时经常会遇到精度丢失的问题。由于JavaScript中的数字是以IEEE 754标准的双精度64位浮点数来表示的,这就意味着它可以精确地表示的整数范围大约在-***到***之间。当超出这...

    javascript解决小数的加减乘除精度丢失的方案.docx

    在JavaScript中处理小数运算时,经常会遇到一个令人头疼的问题——精度丢失。这主要是因为JavaScript中的数字采用IEEE 754标准下的双精度64位浮点数格式表示,而这种表示方法对于某些特定的十进制小数无法做到精确...

    javascript小数精度丢失的完美解决方法

    这就是所谓的JavaScript小数精度丢失问题。 针对这一问题,文章提出了几种解决方法,包括乘法、加法、减法运算中对精度进行控制的函数。 在进行乘法运算时,可以通过累加两个乘数的小数位数来重新计算乘积,并最终...

    javascript小数精度丢失的完美解决方法.docx

    ### JavaScript小数精度丢失的完美解决方法 #### 引言 在JavaScript中处理浮点数时,经常会遇到小数精度丢失的问题。这是由于JavaScript内部使用的是64位浮点数格式(即IEEE 754标准)来表示数值,而这种格式在...

    解决JavaScript数字精度丢失问题的方法

    本文分为三个部分 ...2. 大整数运算 16位和17位数竟然相等,没天理啊。 又如 var x = 9007199254740992 x + 1 == x // ? 看结果 三观又被颠覆了。 3. toFixed 不会四舍五入(Chrome) 线上曾经

    JavaScript小数点精确计算

    由于JavaScript内部使用的是IEEE 754标准来存储和处理浮点数,这可能导致在进行小数运算时出现精度丢失问题,尤其是在涉及大数字或者循环计算时。本篇将深入探讨这个问题,并提供一些解决方案。 首先,我们来理解...

    JS 能表示的最大数值及JS Number类型数字位数及IEEE754标准1

    总之,JavaScript中的Number类型遵循IEEE 754标准,存在浮点数精度问题,尤其是涉及到大整数和小数运算时。了解这些限制对于编写高精度的代码至关重要,特别是在金融计算和其他需要精确数值操作的场景中。在必要时,...

    详解JavaScript中精度失准问题及解决方法

    然而,对于更大但仍然可以表示的数字,如果涉及到精确计算,可能会遇到问题,因为超过了53位有效二进制数的限制,会导致精度丢失。为了定义一个可安全进行算术运算的数字范围,JavaScript提供了`Number.MAX_SAFE_...

    js-jsonBigint-js默认对number类型超出范围精度丢失

    js_jsonBigint_js默认对number类型超出范围精度丢失,本资源包含直接浏览器版本的jsonBigint js代码及示例,并对应不需要处理小数的情况进行源码修改说明,可供项目实际解决js长整数精度丢失问题

    JS精度计算1、转成整数计算再转换成小数,2、toFixed,3、math.js,4、bignumber.js,5、big.js

    它使用BigNumber类型来处理大数和高精度计算,从而避免了JavaScript浮点数精度问题。例如,`math.bignumber('1234567890.123456789')`可以创建一个大数对象,之后的计算都会保持高精度。 4. **bignumber.js库** `...

    javascript中的toFixed固定小数位数 简单实例分享

    JavaScript的浮点数运算有时会出现精度丢失的问题,通过`toFixed()`转换为字符串可以避免这种问题,确保显示的数值是准确的。 总之,`toFixed()`是JavaScript中处理数字格式化和精度控制的重要工具。通过合理使用,...

    浅谈java对象转json,数字精确出现丢失问题

    总的来说,Java对象转JSON过程中遇到的大数字精度丢失和科学记数法显示问题,主要是JavaScript引擎的限制所导致。通过将数字类型转换为字符串类型,可以有效地解决这个问题。在实际开发中,确保对大数字的处理方式有...

    JS小数运算出现多为小数问题的解决方法

    在进行JavaScript小数运算时,我们经常会遇到由于浮点数表示的限制而导致的精度问题。由于浮点数在计算机内部是以二进制形式表示的,部分十进制小数无法转换成一个精确的二进制小数,这导致了在运算过程中出现误差。...

    js加减乘除丢失精度问题解决方法

    JavaScript中的加减乘除运算时,尤其是涉及到小数点的运算,常常会出现精度丢失的问题,这个问题是由于JavaScript采用IEEE 754标准对浮点数进行二进制表示所引起的。在实际编程中,这种精度问题会出现在任何需要进行...

Global site tag (gtag.js) - Google Analytics