为了账号安全,请及时绑定邮箱和手机立即绑定

浮点数学是否破碎?

浮点数学是否破碎?

GCT1015 2019-05-20 14:54:34
请考虑以下代码:0.1 + 0.2 == 0.3  ->  false0.1 + 0.2         ->  0.30000000000000004为什么会出现这些不准确之处?
查看完整描述

9 回答

?
侃侃无极

TA贡献2051条经验 获得超10个赞

当您将.1或1/10转换为基数2(二进制)时,您会在小数点后得到重复模式,就像尝试在基数10中表示1/3一样。该值不准确,因此您无法做到使用普通浮点方法精确计算数学。


查看完整回答
反对 回复 2019-05-20
?
jeck猫

TA贡献1909条经验 获得超7个赞

这里的大多数答案都以非常干燥的技术术语来解决这个问题 我想以正常人能够理解的方式来解决这个问题。

想象一下,你正在尝试切片比萨饼。你有一个机器人比萨刀,可以削减比萨正好一半。它可以将整个披萨减半,或者它可以将现有切片减半,但无论如何,减半总是精确的。

那个披萨刀具有非常精细的动作,如果你从整个披萨开始,然后将其减半,并且每次继续将最小的切片减半,则可以在切片太小之前对其进行减半53次,即使它的高精度能力也是如此。此时,您不能再将那个非常薄的切片减半,但必须按原样包含或排除它。

现在,你将如何将所有切片分成几乎加上十分之一(0.1)或五分之一(0.2)的披萨?真的想一想,试试吧。如果您手边有神话般的精密披萨刀,您甚至可以尝试使用真正的披萨。:-)


大多数有经验的程序员,当然知道真正的答案,这是没有办法拼凑出一个确切的使用这些切片比萨饼的十分之一或五分之一,无论你如何精细切片他们。你可以做一个非常好的近似,如果你用近似值0.2加上0.1的近似值,你会得到0.3的近似值,但它仍然只是一个近似值。

对于双精度数字(这是允许您将披萨减半53倍的精度),立即小于和大于0.1的数字是0.09999999999999999167332731531132594682276248931884765625和0.1000000000000000055511151231257827021181583404541015625。后者比前者更接近0.1,因此如果输入为0.1,则数字解析器将支持后者。

(这两个数字之间的差异是我们必须决定要包括的“最小切片”,它引入向上偏差,或排除,这会引入向下偏差。最小切片的技术术语是ulp。)

在0.2的情况下,数字都是相同的,只是按比例增加了2倍。再次,我们赞成略高于0.2的值。

请注意,在这两种情况下,0.1和0.2的近似值略有向上偏差。如果我们添加足够的这些偏差,它们会使数字越来越远离我们想要的数字,事实上,在0.1 + 0.2的情况下,偏差足够高,结果数字不再是最接近的数字到0.3。

特别是,0.1 + 0.2实际上是0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125,而最接近0.3的数字实际上是0.299999999999999988897769753748434595763683319091796875。


PS一些编程语言还提供披萨切割器,可以将切片分成精确的十分之一。虽然这种披萨切割器并不常见,但是如果您确实可以使用它,那么当重要的是能够获得切片的十分之一或五分之一时,您应该使用它。

(最初发布在Quora上。)


查看完整回答
反对 回复 2019-05-20
?
精慕HU

TA贡献1845条经验 获得超8个赞

浮点舍入错误。由于缺少素数因子5,0.1不能在base-2中准确地表示为基数为10.正如1/3采用无穷多位数来表示十进制,但在base-3中为“0.1”, 0.1在base-2中占用无数个数字,而不是在base-10中。计算机没有无限的内存。


查看完整回答
反对 回复 2019-05-20
?
守着一只汪

TA贡献1872条经验 获得超3个赞

除了其他正确答案之外,您可能还需要考虑缩放值以避免浮点运算问题。

例如:

var result = 1.0 + 2.0;     // result === 3.0 returns true

... 代替:

var result = 0.1 + 0.2;     // result === 0.3 returns false

表达式在JavaScript中0.1 + 0.2 === 0.3返回false,但幸运的是浮点中的整数运算是精确的,因此可以通过缩放来避免十进制表示错误。

作为一个实际的例子,为了避免精度至关重要的浮点问题,建议1处理货币作为整数代表分数:2550美分而不是25.50美元。


1 Douglas Crockford:JavaScript:好的部分:附录A - 可怕的部分(第105页)


查看完整回答
反对 回复 2019-05-20
?
慕雪6442864

TA贡献1812条经验 获得超5个赞

我的解决方法:

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;}

precision是指在添加期间小数点后要保留的位数。


查看完整回答
反对 回复 2019-05-20
  • 9 回答
  • 0 关注
  • 713 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信