写点什么

JavaScript 中的浮点数之谜:为什么 0.1 + 0.2 不等于 0.3?

作者:Lee Chen
  • 2023-09-28
    福建
  • 本文字数:2212 字

    阅读完需:约 7 分钟

在 JavaScript 中,为什么 0.1 + 0.2 不等于 0.3?

在 JavaScript 中,0.1 + 0.2 不等于 0.3 的原因与浮点数的表示和计算有关。JavaScript 使用 IEEE 754 双精度浮点数标准来表示数字,这是一种广泛用于计算机系统中的标准。但由于这种表示方式的限制,某些小数不能被精确地表示为二进制浮点数。


让我们深入了解为什么这会发生:


  1. 二进制表示:在计算机中,所有的数据都是以二进制形式存储的。而某些十进制小数在转换为二进制时会得到一个无限循环的小数。例如,十进制的 0.1 在二进制中的表示是一个无限循环的小数。

  2. 精度限制:由于双精度浮点数的存储空间是有限的(64 位),所以只能存储该小数的前面几位。这意味着存储的值是原始值的一个近似值。

  3. 计算误差:当你将两个这样的近似值相加时,误差可能会累积,导致结果与预期不符。


具体来说,0.10.2 在二进制中都不能被精确表示。当它们被加在一起时,结果是一个接近 0.3 的数,但不完全等于 0.3。因此,当你在 JavaScript 中测试 0.1 + 0.2 === 0.3 时,结果是 false


为了避免这种问题,当处理浮点数计算并需要精确的结果时,开发者通常会使用特定的库或采用某种策略(如将值乘以一个大的整数,进行整数运算,然后再除以该整数)来确保计算的准确性。

什么是双精度浮点数

双精度浮点数是一种计算机编程中用于表示实数(即小数和整数)的数据类型。它是基于 IEEE 754 标准中定义的 64 位双精度表示法。这种表示法允许数字在非常大的范围内变化,同时还能保持相对的精度。以下是双精度浮点数的一些关键特点:


  1. 位数:双精度浮点数使用 64 位来存储一个数字。

  2. 组成

  3. 符号位:1 位,用于表示数字的正负。0 表示正数,1 表示负数。

  4. 指数:11 位,表示 2 的指数。这 11 位允许的范围是从-1022 到 1023(使用偏移量或偏置 1023)。

  5. 尾数(或称为分数部分):52 位,表示数字的实际“有效数字”。

  6. 范围:双精度浮点数可以表示的范围大约是±5.0 × 10^−324 到 ±1.8 × 10^308。

  7. 精度:双精度浮点数可以精确到约 15 位十进制数字。

  8. 特殊值:IEEE 754 定义了一些特殊的值,如正无穷、负无穷和 NaN(非数字)。

  9. 存储方式:数字是以科学记数法的形式存储的,其中基数是 2(即,它是一个二进制的科学记数法)。


在 JavaScript 中,Number类型就是使用双精度浮点数表示的。这意味着,尽管 JavaScript 可以处理非常大或非常小的数字,但由于双精度浮点数的限制,它可能无法精确地表示非常大的整数或非常接近 0 的小数。


需要注意的是,由于双精度浮点数的表示方式,某些数字可能无法精确表示。这就是为什么在 JavaScript 中,0.1 + 0.2 不等于 0.3 的原因。这是浮点数精度问题的一个常见例子,不仅在 JavaScript 中存在,而且在许多其他编程语言中也存在。

如果将数字都转换为二进制,0.1 + 0.2 是如何计算的?

  1. 十进制转二进制

  2. 0.1 在二进制中表示为:0.0001100110011001100110011001100110011001100110011001101...(无限循环)

  3. 0.2 在二进制中表示为:0.001100110011001100110011001100110011001100110011001101...(无限循环)

  4. 注意,这两个数在二进制中都是无限循环的。

  5. 双精度浮点数的存储:在 JavaScript 中,数字是按照 IEEE 754 双精度浮点数格式存储的,这意味着它们有 64 位的空间。其中 1 位用于符号,11 位用于指数,剩下的 52 位用于尾数(实际有效数字)。由于存储空间是有限的,我们不能存储上述二进制小数的所有位。因此,我们只能存储它们的前 52 位(加上隐藏的第一位,总共 53 位的精度)。

  6. 计算:当我们将这两个近似值相加时,结果是两个二进制小数的和。但由于它们都是近似值,所以和也是一个近似值。

  7. 0.1 的近似二进制值(前 53 位):0.0001100110011001100110011001100110011001100110011001101

  8. 0.2 的近似二进制值(前 53 位):0.001100110011001100110011001100110011001100110011001101

  9. 将这两个值相加,我们得到:

  10. 0.01001100110011001100110011001100110011001100110011001101

  11. 这个值转换回十进制是 0.30000000000000004,而不是 0.3


因此,由于二进制表示的限制和双精度浮点数的有限精度,0.1 + 0.2 在 JavaScript 中的结果是 0.30000000000000004,而不是 0.3

0.1 是如何转换为二级制数的?

将十进制小数转换为二进制小数涉及到一种称为“乘以 2 取整法”的方法。以下是将 0.1 转换为二进制的步骤:

步骤:

  1. 乘以 2:将小数乘以 2。对于 0.1,我们有:[0.1 \times 2 = 0.2]

  2. 取整数部分:记录整数部分(在这种情况下是 0),并保留小数部分(0.2)以进行下一步的计算。

  3. 重复步骤 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)...(这个过程会一直重复)

  4. 记录整数部分:记录每一步的整数部分。在这个例子中,我们得到的二进制小数是 0.00011001100110011...

结果:

因此,0.1 在二进制中是一个无限循环的小数:0.00011001100110011...


这是因为 0.1 不能被精确地表示为一个有限的二进制小数。在二进制系统中,只有像 1/20.5),1/40.25)或 1/80.125)这样的分数可以被精确表示,因为它们的分母是 2 的幂。而 0.11/10,它不能被二进制系统精确表示,所以它在二进制中是一个无限循环的小数。

结论

由于双精度浮点数的存储方式和二进制表示的限制,JavaScript 中的某些浮点数运算可能会产生出乎意料的结果。为了避免这种情况,开发者在进行浮点数运算时应该特别小心,并考虑使用库或工具来帮助处理这种精度问题。

发布于: 刚刚阅读数: 3
用户头像

Lee Chen

关注

还未添加个人签名 2018-08-29 加入

还未添加个人简介

评论

发布
暂无评论
JavaScript中的浮点数之谜:为什么0.1 + 0.2 不等于 0.3?_JavaScript_Lee Chen_InfoQ写作社区