尾数较高的fp如何代表较小的数字?

时间:2018-09-03 08:46:26

标签: c++ floating-point

我爱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

你能照耀我吗?

编辑:抱歉,我在复制/粘贴二进制文件时出错。他们是正确的。

4 个答案:

答案 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,当加倍时,会导致向上舍入以十进制表示。