我爱FP
;每当我想得到它时,我都知道对此一无所知:)
This是我不理解的示例。我将8次乘以相同的数字(0.1
),然后打印总和和“原始”的结果:
std::cout.precision(100);
int numIteration = 8;
double step = 0.1;
double sum = 0.0;
for(int i = 0; i < numIteration; i++) {
sum += step;
}
std::cout << "orig stored as " << numIteration / 10.0 << std::endl;
std::cout << " sum stored as " << sum << std::endl;
0.1
存储为0.1000000000000000055511151231257827021181583404541015625
,我希望8和之后将存储为大于等于0.8
的{{1}}。 / p>
但是结果震惊了我。实际上,在8和之后,结果是0.8000000000000000444089209850062616169452667236328125
,它较小。
另外,如果我检查两者的二进制输出,我会发现总和比“原始”值高:
0.79999999999999993338661852249060757458209991455078125
但是0.8 stored as binary 0 01111111110 1001100110011001100110011001100110011001100110011001 // smaller
sum stored as binary 0 01111111110 1001100110011001100110011001100110011001100110011010 // higher
<0.79999999999999993338661852249060757458209991455078125
。
你能照耀我吗?
编辑:抱歉,我在复制/粘贴二进制文件时出错。他们是正确的。
答案 0 :(得分:7)
使用IEEE floating-point rounding会在每次算术运算之后发生。并且舍入可以上升或下降。
如果在每次迭代中打印sum
的值,您应该会看到:
sum is 0.1000000000000000055511151231257827021181583404541015625
sum is 0.200000000000000011102230246251565404236316680908203125
sum is 0.3000000000000000444089209850062616169452667236328125
sum is 0.40000000000000002220446049250313080847263336181640625
sum is 0.5
sum is 0.59999999999999997779553950749686919152736663818359375
sum is 0.6999999999999999555910790149937383830547332763671875
sum is 0.79999999999999993338661852249060757458209991455078125
您假设四舍五入只能向上进行。但是,由于“四舍五入,与偶数保持联系” 是IEEE 754中的默认舍入模式,因此每次迭代都会选择最接近的二进制可表示值,因此结果不必大于{ {1}}。
另一方面
0.8
会产生预期的结果
std::cout << 0.1 * 8.0 << std::endl;
更新:正如评论中提到的@Evg一样,可以使用std::fesetround
更改浮点舍入方向。
答案 1 :(得分:2)
您的二进制表示法是错误的。正确的是:
sum = 0.79999999999999993 ... =
0b0011111111101001100110011001100110011001100110011001100110011001
numIteration / 10.0 = 0.80000000000000004... =
0b0011111111101001100110011001100110011001100110011001100110011010
答案 2 :(得分:1)
通常,将较小的增量添加到较大的金额时会出现问题。没有足够的精度来存储全部结果,并且失去了一些重要性。在循环的最后一次迭代中,您开始遇到这种情况。
对于足够大的和小的增量,总和可能完全不变。
答案 3 :(得分:1)
虽然AMA的答案是正确的,因为每次加法之后都会进行四舍五入,但是即使是单次操作(包括乘法),也会出现相同的意外情况:
#include <iostream>
int main()
{
const auto val1 = 0.3444444444444444
, val2 = 0.34444444444444442;
std::cout << (2*val1) << '\n'
<< (2*val2) << '\n';
}
(除非另有说明,否则我假设IEEE具有标准的舍入行为。)
第一行将显示0.6888888888888888(如果您相信我为您进行计数,它的输入为15x 4,输出为15x 8)毫不奇怪。我们假设第二行显示的是另外一位数字,希望是接近4,或者结果不变。
实际上,第二行显示的是0.688888888888888 9 。令人惊讶的是,最后一位上的4如何在下一位更高的位上向上四舍五入?这与我们的观点相反,即当双方都应用正比例因子时,会保持不平等。即因为2 <2.5,然后2 * 2 <2 * 2.5,然后4 <5。这意味着,由于在2*val2
中(以十进制表示)需要四舍五入的最后一位数字,因此val2
直观上必须至少为0.3444444444444444 25 向上舍入。
这里的问题是每个数字系统的输入和输出都有不同的舍入。实际上,由于乘法本身,二进制甚至都不进行舍入,但是在两个数字系统转换中都发生舍入。输入的二进制表示形式:
0.01011000001011011000001011011000001011011000001011001(val1
)
0.01011000001011011000001011011000001011011000001011011(val2
)
乘以2的乘积当然只是二进制形式的向左移1,其中包括浮点数(至少如果我们忽略溢出的可能性),所以输出为:
0.10110000010110110000010110110000010110110000010110010(2*val1
)
0.10110000010110110000010110110000010110110000010110110(2*val2
)
后者转换回0.688888888888888 88395 …(请注意,现在还有8个),正确地舍入为0.6888888888888888 9 。
在这种情况下,令人惊讶的行为的原始原因是val2
实际上变为:
0.3444444444444444 419772821675
还有一个附加的4代替了我们输入的尾随2,当加倍时,会导致向上舍入以十进制表示。