当开发者第一次遇到这个看似令人费解的结果时,JavaScript 经常被嘲笑:
0.1 0.2 == 0.30000000000000004
关于 JavaScript 处理数字的模因很普遍,常常导致许多人相信这种行为是该语言所独有的。
然而,这个怪癖不仅仅局限于 JavaScript。这是大多数编程语言处理浮点运算方式的结果。
例如,以下是来自 Java 和 Go 的代码片段,它们产生类似的结果:
计算机本身只能存储整数。他们不懂分数。 (他们会怎么做?计算机进行算术运算的唯一方法是打开或关闭一些灯。灯可以打开或关闭。它不能“半”亮!)他们需要某种表示浮点数的方法。由于这种表示方法并不完全准确,因此 0.1 0.2 通常不等于 0.3。
所有分母由数系基数的质因数组成的分数都可以清晰地表达,而任何其他分数都会有重复的小数。例如,在以 10 为基数的数字系统中,可以清楚地表示 1/2、1/4、1/5、1/10 等分数,因为每种情况下的分母均由 2 或 5(10 的质因数)组成然而,像 1/3、1/6、1/7 这样的分数都有循环小数。
同样,在二进制系统中,像 1/2、1/4、1/8 这样的分数都可以清晰地表达,而所有其他分数都有循环小数。当您对这些循环小数执行算术运算时,您最终会得到剩余的内容,当您将计算机的数字二进制表示形式转换为人类可读的以 10 为基数的表示形式时,这些剩余内容会继续存在。这就是导致大致正确结果的原因。
既然我们已经确定这个问题并非 JavaScript 所独有,那么让我们探讨一下浮点数是如何在幕后表示和处理的,以了解为什么会出现这种行为。
为了了解浮点数在底层是如何表示和处理的,我们首先必须了解IEEE 754浮点标准。
IEEE 754 标准是一种广泛使用的规范,用于在计算机系统中表示浮点数并对其执行算术运算。它的创建是为了保证在各种计算平台上使用浮点运算时的一致性。大多数编程语言和硬件实现(CPU、GPU 等)都遵守此标准。
这是数字在 IEEE 754 格式中的表示方式:
这里s是符号位(0表示正数,1表示负数),M是尾数(保存数字的位数)和E 是决定数字小数位数的指数。
您将无法找到任何可以在此格式中精确表示数字(如 0.1、0.2 或 0.3)的 M 和 E 整数值。我们只能选择给出最接近结果的 M 和 E 值。
这里有一个工具,您可以用来确定 IEEE 754 十进制数表示法:https://www.h-schmidt.net/FloatConverter/IEEE754.html
IEEE 754 0.25 表示法:
IEEE 754 分别表示 0.1 和 0.2:
请注意,0.25时转换误差为0,而0.1和0.2则为非零误差。
IEEE 754 定义了以下表示浮点数的格式:
单精度(32 位):1 位符号,8 位指数,23 位尾数
双精度(64 位):1 位符号,11 位指数,52 位尾数
为了简单起见,让我们考虑使用 32 位的单精度格式。
0.1的32位表示为:
0 01111011 10011001100110011001101
这里第一位代表符号(0在本例中表示正数),接下来的8位(01111011)代表指数,最后23位(10011001100110011001101)代表尾数。
这不是准确的表示。它代表 ≈ 0.100000001490116119384765625
同样,0.2的32位表示为:
0 01111100 10011001100110011001101
这也不是准确的表示。它代表 ≈ 0.20000000298023223876953125
添加后,结果是:
0 01111101 11001101010011001100110
十进制表示中的 ≈ 0.30000001192092896。
总之,看似令人困惑的结果 0.1 0.2 不产生 0.3 并不是 JavaScript 特有的异常现象,而是跨编程语言的浮点运算限制的结果。这种行为的根源在于数字的二进制表示形式,这在处理某些分数时本质上会导致精度错误。
免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。
Copyright© 2022 湘ICP备2022001581号-3