考虑以下代码:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
为什么会出现这些错误?
考虑以下代码:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
为什么会出现这些错误?
当前回答
你试过胶带解决方案了吗?
尝试确定错误发生的时间,并用简短的if语句修复它们,这并不漂亮,但对于某些问题,这是唯一的解决方案,这就是其中之一。
if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
else { return n * 0.1 + 0.000000000000001 ;}
我在c#的一个科学模拟项目中也遇到过同样的问题,我可以告诉你,如果你忽视蝴蝶效应,它会变成一条大胖龙,咬你一口**
其他回答
这里的大多数答案都用非常枯燥的技术术语来解决这个问题。我想用正常人能够理解的方式来解决这个问题。
想象一下,你正试图把披萨切成薄片。你有一个机器人披萨切割机,可以将披萨切成两半。它可以将整个披萨减半,也可以将现有的披萨减半,但无论如何,减半总是准确的。
那台披萨切割机动作非常精细,如果你从一整块披萨开始,然后将其减半,然后继续每次将最小的披萨片减半,你可以在披萨片太小甚至无法实现高精度功能之前,将其减半53次。此时,您不能再将非常薄的切片减半,但必须按原样包含或排除它。
现在,你如何将所有的切片以这样一种方式分割,使其达到披萨的十分之一(0.1)或五分之一(0.2)?真的想一想,试着解决它。如果你手边有一个神话般的精密披萨切割机,你甚至可以尝试使用真正的披萨
当然,大多数有经验的程序员都知道真正的答案,那就是,无论你切得多细,都无法用这些切片拼凑出十分之一或五分之一的披萨。你可以做一个非常好的近似值,如果你把0.1的近似值和0.2的近似值相加,你会得到非常好的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.0200000000000000011102230246251565404236316680908203125=0.30000000000000000444089209850062616169452667236328125,而最接近0.3的数字实际上是0.29999999999988897769753748434595763683319091796875。
另外,一些编程语言还提供了披萨切割机,可以将披萨切成十分之一。虽然这种披萨切刀并不常见,但如果你有机会切到一个,那么你应该在切到十分之一或五分之一的披萨片非常重要的时候使用它。
(最初发布在Quora上。)
这个问题的许多重复问题都是关于浮点舍入对特定数字的影响。在实践中,通过查看感兴趣的计算的确切结果而不是仅仅阅读它,更容易了解它的工作原理。一些语言提供了实现这一点的方法,例如在Java中将浮点或双精度转换为BigDecimal。
由于这是一个语言不可知的问题,因此需要语言不可知工具,例如十进制到浮点转换器。
将其应用于问题中的数字,视为双精度:
0.1转换为0.1000000000000000055511151231257827021181583404541015625,
0.2转换为0.200000000000000011102230246251565404236316680908203125,
0.3转换为0.299999999999999988897769753748434595763683319091796875,以及
0.300000000000000004转换为0.30000000000000000444089209850062616169452667236328125。
手动或在十进制计算器(如Full Precision calculator)中添加前两个数字,显示实际输入的精确和为0.30000000000000000166533453693773481063544750213623046875。
如果四舍五入到等于0.3,则舍入误差将为0.000000000000000027755575615628913510591702705078125。四舍五入等于0.300000000000000004也会产生舍入误差0.000000000000000027755575615628913510591702705078125。打成平手的规则适用。
返回浮点转换器,0.300000000000000004的原始十六进制是3fd333333333334,以偶数结尾,因此是正确的结果。
我的解决方法:
function add(a, b, precision) {
var x = Math.pow(10, precision || 2);
return (Math.round(a * x) + Math.round(b * x)) / x;
}
精度是指在加法过程中要保留小数点后的位数。
从Python 3.5开始,您可以使用math.isclose()函数来测试近似相等性:
>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> 0.1 + 0.2 == 0.3
False
浮点数的陷阱是它们看起来像十进制,但它们是二进制的。
2的唯一素因子是2,而10的素因子为2和5。这样做的结果是,每一个可以完全写成二进制分数的数字也可以完全写成十进制分数,但只有一部分可以写成十进制分数的数字可以写成二进制分数。
浮点数本质上是一个有效位数有限的二进制分数。如果你超过这些有效数字,那么结果将被四舍五入。
当您在代码中键入文字或调用函数将浮点数解析为字符串时,它需要一个十进制数,并将该十进制数的二进制近似值存储在变量中。
当您打印浮点数或调用函数将浮点数转换为字符串时,它将打印浮点数的十进制近似值。可以将二进制数字精确地转换为十进制,但在转换为字符串*时,我所知道的任何语言都不会默认这样做。一些语言使用固定数量的有效数字,其他语言使用最短的字符串,该字符串将“往返”返回到相同的浮点值。
*Python在将浮点数转换为“decimal.decimal”时确实会进行精确的转换。这是我所知道的获得浮点数的精确十进制等效值的最简单方法。