考虑以下代码:
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
为什么会出现这些不准确的情况?我的解决方法:
function add(a, b, precision) {
var x = Math.pow(10, precision || 2);
return (Math.round(a * x) + Math.round(b * x)) / x;
}
精度指的是在加法运算中,你想要保留小数点后的位数。
nextafter()
。此外,您可以将浮点数作为整数进行比较,并获得正确的答案,除非它们都是负数(因为符号-大小与2的补码)。 - Peter Cordes这个著名的双精度问题相关的一些统计数据。
当使用0.1的步长(从0.1到100)将所有值 (a + b) 相加时,存在约15%的精度误差机会。请注意,该误差可能导致略微更大或更小的值。以下是一些示例:
0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)
从100到0.1,使用步长为0.1进行减法计算(a - b,其中a > b),我们有约34%的精度误差机会。 以下是一些例子:
0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)
*15%和34%确实很大,因此在需要高精度时始终使用BigDecimal。使用2个小数位(步长0.01),情况会更加恶化(18%和36%)。
decimal
模块和Java的BigDecimal
类,内部使用十进制表示数字(而不是二进制表示)。它们都有限的精度,因此仍然容易出错,但是它们解决了大多数二进制浮点算术的常见问题。decimal
模块基于IEEE标准854-1987。fractions
模块和Apache Common的BigFraction
类。它们都将有理数表示为(分子,分母)
对,并且它们可能比十进制浮点算术给出更准确的结果。你尝试过使用管道胶带解决方案了吗?
尝试确定错误发生的时机,并使用简短的if语句进行修复,虽然不太美观,但对于某些问题来说,这是唯一的解决方案,这也是其中之一。
if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
else { return n * 0.1 + 0.000000000000001 ;}
在一个C#科学模拟项目中,我遇到了同样的问题。我可以告诉你,如果你忽略蝴蝶效应,它会变成一条巨大的胖龙并咬你的屁股。
出现这些奇怪的数字是因为计算机在计算时使用二进制(基数为2)数制,而我们使用十进制(基数为10)。
大多数的小数无论是在二进制还是十进制中都无法精确表示。结果是一个四舍五入(但精确的)数字。
这个问题的很多重复提问都是关于浮点数舍入对特定数字的影响。实际上,通过查看感兴趣的计算的精确结果,比仅仅阅读相关内容更容易理解它的工作原理。一些编程语言提供了这样的功能,例如在Java中将 float
或 double
转换为 BigDecimal
。
由于这是一个与编程语言无关的问题,所以需要使用与编程语言无关的工具,例如十进制到浮点数转换器。
将此应用于视为双精度浮点数的问题中的数字:
0.1 转换为 0.1000000000000000055511151231257827021181583404541015625,
0.2 转换为 0.200000000000000011102230246251565404236316680908203125,
0.3 转换为 0.299999999999999988897769753748434595763683319091796875,以及
0.30000000000000004 转换为 0.3000000000000000444089209850062616169452667236328125。
手动添加前两个数字或使用诸如高精度计算器的小数计算器,可以得到实际输入的准确和为 0.3000000000000000166533453693773481063544750213623046875。
如果它被舍入到相当于0.3的值,那么舍入误差将为0.0000000000000000277555756156289135105907917022705078125。如果舍入到相当于0.30000000000000004的值,也会产生舍入误差0.0000000000000000277555756156289135105907917022705078125。采用银行家舍入法。
回到浮点数转换器,0.30000000000000004的原始十六进制为3fd3333333333334,它以偶数结束,因此这是正确的结果。
我能不能加一句; 人们总是假设这是计算机问题,但如果你用手数数(十进制),你就无法得到 (1/3+1/3=2/3)=true
,除非你有无限的0.333...加上0.333...,所以就像在二进制中的 (1/10+2/10)!==3/10
问题一样,你将其截断为0.333 + 0.333 = 0.666,可能会四舍五入为0.667,这也是技术上不准确的。
三进制计数,第三个不是问题——也许一些每只手15个手指的民族会问你的小数点数学是怎么回事...
数字计算机中可实现的浮点数学必然使用对实数的近似以及对其进行运算。 (标准版本需要50多页文档并有一个委员会处理其勘误和进一步完善。)
这种近似是不同类型的近似混合而成,每种近似方式都可以被忽略或仔细考虑其与精确度的特定偏差。 它还涉及在硬件和软件层面上的许多明确异常情况,大多数人在经过时假装没有注意到。
如果您需要无限精度(例如,使用数字π而不是它的许多较短的替代品之一),则应编写或使用符号数学程序。
但是,如果您认为有时浮点数学在价值和逻辑方面存在模糊性,并且错误可能会迅速累积,并且您可以编写要求并测试以允许该问题,则您的代码通常可以使用您的FPU。