JavaScript 中的浮点数之谜:为什么 0.1 + 0.2 不等于 0.3?
在 JavaScript 中,为什么 0.1 + 0.2 不等于 0.3?
在 JavaScript 中,0.1 + 0.2
不等于 0.3
的原因与浮点数的表示和计算有关。JavaScript 使用 IEEE 754 双精度浮点数标准来表示数字,这是一种广泛用于计算机系统中的标准。但由于这种表示方式的限制,某些小数不能被精确地表示为二进制浮点数。
让我们深入了解为什么这会发生:
二进制表示:在计算机中,所有的数据都是以二进制形式存储的。而某些十进制小数在转换为二进制时会得到一个无限循环的小数。例如,十进制的
0.1
在二进制中的表示是一个无限循环的小数。精度限制:由于双精度浮点数的存储空间是有限的(64 位),所以只能存储该小数的前面几位。这意味着存储的值是原始值的一个近似值。
计算误差:当你将两个这样的近似值相加时,误差可能会累积,导致结果与预期不符。
具体来说,0.1
和 0.2
在二进制中都不能被精确表示。当它们被加在一起时,结果是一个接近 0.3
的数,但不完全等于 0.3
。因此,当你在 JavaScript 中测试 0.1 + 0.2 === 0.3
时,结果是 false
。
为了避免这种问题,当处理浮点数计算并需要精确的结果时,开发者通常会使用特定的库或采用某种策略(如将值乘以一个大的整数,进行整数运算,然后再除以该整数)来确保计算的准确性。
什么是双精度浮点数
双精度浮点数是一种计算机编程中用于表示实数(即小数和整数)的数据类型。它是基于 IEEE 754 标准中定义的 64 位双精度表示法。这种表示法允许数字在非常大的范围内变化,同时还能保持相对的精度。以下是双精度浮点数的一些关键特点:
位数:双精度浮点数使用 64 位来存储一个数字。
组成:
符号位:1 位,用于表示数字的正负。0 表示正数,1 表示负数。
指数:11 位,表示 2 的指数。这 11 位允许的范围是从-1022 到 1023(使用偏移量或偏置 1023)。
尾数(或称为分数部分):52 位,表示数字的实际“有效数字”。
范围:双精度浮点数可以表示的范围大约是±5.0 × 10^−324 到 ±1.8 × 10^308。
精度:双精度浮点数可以精确到约 15 位十进制数字。
特殊值:IEEE 754 定义了一些特殊的值,如正无穷、负无穷和 NaN(非数字)。
存储方式:数字是以科学记数法的形式存储的,其中基数是 2(即,它是一个二进制的科学记数法)。
在 JavaScript 中,Number
类型就是使用双精度浮点数表示的。这意味着,尽管 JavaScript 可以处理非常大或非常小的数字,但由于双精度浮点数的限制,它可能无法精确地表示非常大的整数或非常接近 0 的小数。
需要注意的是,由于双精度浮点数的表示方式,某些数字可能无法精确表示。这就是为什么在 JavaScript 中,0.1 + 0.2
不等于 0.3
的原因。这是浮点数精度问题的一个常见例子,不仅在 JavaScript 中存在,而且在许多其他编程语言中也存在。
如果将数字都转换为二进制,0.1 + 0.2 是如何计算的?
十进制转二进制:
0.1
在二进制中表示为:0.0001100110011001100110011001100110011001100110011001101...
(无限循环)0.2
在二进制中表示为:0.001100110011001100110011001100110011001100110011001101...
(无限循环)注意,这两个数在二进制中都是无限循环的。
双精度浮点数的存储:在 JavaScript 中,数字是按照 IEEE 754 双精度浮点数格式存储的,这意味着它们有 64 位的空间。其中 1 位用于符号,11 位用于指数,剩下的 52 位用于尾数(实际有效数字)。由于存储空间是有限的,我们不能存储上述二进制小数的所有位。因此,我们只能存储它们的前 52 位(加上隐藏的第一位,总共 53 位的精度)。
计算:当我们将这两个近似值相加时,结果是两个二进制小数的和。但由于它们都是近似值,所以和也是一个近似值。
0.1
的近似二进制值(前 53 位):0.0001100110011001100110011001100110011001100110011001101
0.2
的近似二进制值(前 53 位):0.001100110011001100110011001100110011001100110011001101
将这两个值相加,我们得到:
0.01001100110011001100110011001100110011001100110011001101
这个值转换回十进制是
0.30000000000000004
,而不是0.3
。
因此,由于二进制表示的限制和双精度浮点数的有限精度,0.1 + 0.2
在 JavaScript 中的结果是 0.30000000000000004
,而不是 0.3
。
0.1 是如何转换为二级制数的?
将十进制小数转换为二进制小数涉及到一种称为“乘以 2 取整法”的方法。以下是将 0.1
转换为二进制的步骤:
步骤:
乘以 2:将小数乘以 2。对于
0.1
,我们有:[0.1 \times 2 = 0.2]取整数部分:记录整数部分(在这种情况下是
0
),并保留小数部分(0.2
)以进行下一步的计算。重复步骤 1 和 2:使用上一步留下的小数部分重复步骤 1 和 2:0.2 * 2 = 0.4 (整数部分
0
)0.4 * 2 = 0.8 (整数部分0
)0.8 * 2 = 1.6 (整数部分1
)0.6 * 2 = 1.2 (整数部分1
)0.2 * 2 = 0.4 (整数部分0
)...(这个过程会一直重复)记录整数部分:记录每一步的整数部分。在这个例子中,我们得到的二进制小数是
0.00011001100110011...
。
结果:
因此,0.1
在二进制中是一个无限循环的小数:0.00011001100110011...
。
这是因为 0.1
不能被精确地表示为一个有限的二进制小数。在二进制系统中,只有像 1/2
(0.5
),1/4
(0.25
)或 1/8
(0.125
)这样的分数可以被精确表示,因为它们的分母是 2 的幂。而 0.1
是 1/10
,它不能被二进制系统精确表示,所以它在二进制中是一个无限循环的小数。
结论
由于双精度浮点数的存储方式和二进制表示的限制,JavaScript 中的某些浮点数运算可能会产生出乎意料的结果。为了避免这种情况,开发者在进行浮点数运算时应该特别小心,并考虑使用库或工具来帮助处理这种精度问题。
版权声明: 本文为 InfoQ 作者【Lee Chen】的原创文章。
原文链接:【http://xie.infoq.cn/article/973bf3e428605efeed828544c】。文章转载请联系作者。
评论