javascript - 为什么 0.1 + 0.2 会在 JavaScript 中返回不可预测的浮点结果,而 0.2 + 0.3 不会?
问题描述
0.1 + 0.2
// => 0.30000000000000004
0.2 + 0.2
// => 0.4
0.3 + 0.2
// => 0.5
我知道它与浮点有关,但这里到底发生了什么?
根据@Eric Postpischil 的评论,这不是重复的:
那只涉及为什么“噪音”出现在一个加法中。这个问题问为什么“噪音”出现在一个加法中,而没有出现在另一个加法中。另一个问题没有回答。因此,这不是重复的。实际上,差异的原因不是浮点运算本身,而是由于 ECMAScript 2017 7.1.12.1 step 5
解决方案
在 JavaScript 中将 Number 值转换为字符串时,默认使用刚好足够的数字来唯一区分Number
value。1这意味着当一个数字显示为“0.1”时,并不意味着它恰好是 0.1,只是它比任何其他 Number 值更接近 0.1,所以只显示“0.1”告诉你它是这个唯一的数字值,即 0.1000000000000000055511151231257827021181583404541015625。我们可以用十六进制浮点表示法将其写为 0x1.999999999999ap-4。(p-4
将前面的十六进制数字乘以 2 的 -4 次方的方法,因此数学家会将其写为 1.999999999999 16 • 2 -4。)
0.1
以下是在源代码中编写、0.2
和时产生的值,0.3
它们被转换为 JavaScript 的数字格式:
- 0.1 → 0x1.999999999999ap-4 = 0.1000000000000000055511151231257827021181583404541015625。
- 0.2 → 0x1.999999999999ap-3 = 0.200000000000000011102230246251565404236316680908203125。
- .3 → 0x1.3333333333333p-2 = 0.299999999999999988897769753748434595763683319091796875。
当我们评估时0.1 + 0.2
,我们添加了 0x1.999999999999ap-4 和 0x1.999999999999ap-3。要手动执行此操作,我们可以首先通过将其有效数(小数部分)乘以 2 并从其指数中减去 1 来调整后者,从而产生 0x3.3333333333334p-4。(您必须以十六进制进行此算术运算。A 16 • 2 = 14 16,所以最后一位是 4,并且携带 1。然后 9 16 • 2 = 12 16,携带的 1 使其成为 13 16. 这会产生一个 3 位数字和一个 1 进位。)现在我们有 0x1.999999999999ap-4 和 0x3.3333333333334p-4,我们可以添加它们。这将产生 4.ccccccccccccep-4。这是精确的数学结果,但是对于数字格式来说它有太多位。我们在有效数字中只能有 53 位。4 (100 2 ) 中有 3 位,后 13 位中的每一个有 4 位,因此总共有 55 位。计算机必须删除 2 位并对结果进行四舍五入。最后一位数字 E 16是 1110 2,所以 10 位必须删除。这些位恰好是前一位的 ½,因此它是向上或向下舍入之间的关系。打破平局的规则是四舍五入,所以最后一位是偶数,所以我们四舍五入使 11 位变为 100。E 16变为 10 16,导致进位到下一位。结果是 4.cccccccccccd0p-4,等于 0.3000000000000000444089209850062616169452667236328125。
现在我们可以看到为什么打印.1 + .2
显示“0.30000000000000004”而不是“0.3”。对于数值 0.299999999999999988897769753748434595763683319091796875,JavaScript 显示“0.3”,因为该数值比任何其他数值更接近 0.3。它在小数点后第 17 位与 0.3 相差约 1.1 ,而我们得到的加法结果在第 17位与 0.3 相差约 4.4 。所以:
- 源代码
0.3
生成 0.299999999999999988897769753748434595763683319091796875 并打印为“0.3”。 - 源代码
0.1 + 0.2
生成 0.3000000000000000444089209850062616169452667236328125 并打印为“0.30000000000000004”。
现在考虑0.2 + 0.2
。结果是 0.40000000000000002220446049250313080847263336181640625。那是最接近 0.4 的数字,因此 JavaScript 将其打印为“0.4”。
最后,考虑0.3 + 0.2
。我们正在添加 0x1.999999999999ap-3 和 0x1.3333333333333p-2。我们再次调整第二个操作数,生成 0x2.6666666666666p-3。然后相加产生 0x4.0000000000000p-3,即 0x1p-1,即 ½ 或 0.5。所以它打印为“0.5”。
另一种看待它的方式:
- 源代码
0.1
和的值0.2
分别略高于 0.1 和 0.2,将它们相加会产生一个高于 0.3 的数字,并且误差会得到加强,因此总误差大到足以将结果推离 0.3,足以让 JavaScript 显示错误。 - 添加
0.2 + 0.2
时,错误再次加强。但是,这种情况下的总误差不足以将结果推离 0.4 太远,以至于 JavaScript 以不同的方式显示它。 - 源代码的值
0.3
略低于 0.3。当添加到0.2
略高于 0.2 的 时,误差被取消,正好为 0.5。
脚注
1此规则来自 ECMAScript 2017 语言规范第 7.1.12.1 条中的第 5 步。
推荐阅读
- python - TypeError:列表索引必须是整数,而不是列表问题中的str
- javascript - 作为前端 javascript 开发人员的登录身份验证问题,,,
- python - 我在黑客等级中将名字和姓氏的第一个字母大写,但出现语法错误
- sqlite - 如何在 Azure Web App 中手动安装 SQLite(windows 环境,PHP 语言)
- nosql - 如果分区键相同,DynamoDB 本地二级索引与全局二级索引之间有区别吗?
- google-app-engine - 使用“x-appengine-estimated-cpm-us-dollars”标头估算 Google App Engine API 脚本的成本
- laravel - 在控制器中获取自定义工匠命令的响应
- json - 如何在python中获得双引号的json正文?
- oracle - 我不小心在任务管理器中结束了任务 ORACLE RDBMS
- mysql - 修改列定义时,Mysql Data truncated for column error