考虑以下代码:
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;
}
精度是指在加法过程中要保留小数点后的位数。
其他回答
你试过胶带解决方案了吗?
尝试确定错误发生的时间,并用简短的if语句修复它们,这并不漂亮,但对于某些问题,这是唯一的解决方案,这就是其中之一。
if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
else { return n * 0.1 + 0.000000000000001 ;}
我在c#的一个科学模拟项目中也遇到过同样的问题,我可以告诉你,如果你忽视蝴蝶效应,它会变成一条大胖龙,咬你一口**
这些奇怪的数字之所以出现,是因为计算机使用二进制(以2为基数)数字系统进行计算,而我们使用十进制(以10为基数)。
大多数分数不能用二进制或十进制或两者精确表示。结果-四舍五入(但精确)的数字结果。
其实很简单。当你有一个基数为10的系统(像我们的系统)时,它只能表示使用基数素因子的分数。10的主要因子是2和5。因此,1/2、1/4、1/5、1/8和1/10都可以清晰地表达,因为分母都使用10的素因子。相比之下,1/3、1/6和1/7都是重复小数,因为它们的分母使用3或7的素因子。在二进制(或基数2)中,唯一的素因子是2。所以你只能清楚地表达分数,它只包含2作为素因子。在二进制中,1/2、1/4、1/8都可以清晰地表示为小数。而1/5或1/10将是重复小数。因此,0.1和0.2(1/10和1/5)虽然在以10为基数的系统中是干净的小数,但在计算机运行的以2为基数的体系中是重复的小数。当你对这些重复的小数进行数学运算时,当你将计算机的以2(二进制)为基数的数字转换为更易于人类阅读的以10为基础的数字时,你最终会留下剩余部分。
从…起https://0.30000000000000004.com/
不,不破,但大多数小数必须近似
总结
浮点运算是精确的,不幸的是,它与我们通常的以10为基数的数字表示法不太匹配,所以我们经常给它的输入与我们写的略有不同。
即使是像0.01、0.02、0.03、0.04…0.24这样的简单数字也不能精确地表示为二进制分数。如果你数到0.01、.02、.03…,直到你数到0.25,你才能得到以2为底的第一个分数。如果你尝试使用FP,那么你的0.01会稍微有点偏差,所以要将其中的25个相加到一个精确的0.25,就需要一长串的因果关系,包括保护位和舍入。很难预测,所以我们举手说“FP不准确”,但事实并非如此。
我们不断地给FP硬件一些在基数10中看似简单但在基数2中却是重复的分数。
这是怎么发生的?
当我们用十进制书写时,每个分数(特别是每个终止的小数)都是形式的有理数
a/(2n x 5m)
在二进制中,我们只得到2n项,即:
a/2n
所以在十进制中,我们不能表示1/3。因为基数10包括2作为素因子,所以我们可以写成二进制分数的每个数字也可以写成基数10的分数。然而,我们写为10进制分数的任何东西都很难用二进制表示。在0.01、0.02、0.03…0.99的范围内,只有三个数字可以用我们的FP格式表示:0.25、0.50和0.75,因为它们是1/4、1/2和3/4,所有的数字都只使用2n项。
在base10中,我们不能表示1/3。但在二进制中,我们不能做1/10或1/3。
因此,虽然每一个二进制分数都可以用十进制来表示,但反过来却不正确。事实上,大多数小数在二进制中重复。
处理它
开发人员通常被要求进行<epsilon比较,更好的建议可能是舍入为整数值(在C库中:round()和round f(),即保持FP格式),然后进行比较。舍入到特定的小数部分长度可以解决大多数输出问题。
此外,在实数运算问题(FP是在早期昂贵的计算机上为之发明的问题)上,宇宙的物理常数和所有其他测量值只为相对较少的有效数字所知,因此整个问题空间无论如何都是“不精确的”。FP“精度”在这种应用中不是问题。
当人们尝试使用FP进行计数时,整个问题就真的出现了。它确实可以做到这一点,但前提是你坚持使用整数值,这会破坏使用它的意义。这就是为什么我们拥有所有这些小数软件库的原因。
我喜欢克里斯的披萨回答,因为它描述了实际问题,而不仅仅是关于“不准确”的通常手写。如果FP只是“不准确”,我们可以修复它,而且几十年前就已经做到了。我们没有这样做的原因是因为FP格式紧凑快速,是处理大量数字的最佳方式。此外,这也是太空时代和军备竞赛以及早期使用小型内存系统解决速度非常慢的计算机的大问题的尝试所留下的遗产。(有时,单个磁芯用于1位存储,但这是另一回事。)
结论
如果您只是在银行数豆子,那么首先使用十进制字符串表示的软件解决方案工作得非常好。但你不能这样做量子色动力学或空气动力学。
这个问题的许多重复问题都是关于浮点舍入对特定数字的影响。在实践中,通过查看感兴趣的计算的确切结果而不是仅仅阅读它,更容易了解它的工作原理。一些语言提供了实现这一点的方法,例如在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,以偶数结尾,因此是正确的结果。