[英]How can a fp with higher mantissa represent a smaller number?
我爱FP
; 每次我想到它,我明白了解它:)
这是一个我不明白的例子。 我总结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
,其存储为0.8000000000000000444089209850062616169452667236328125
。
但结果震惊了我。 事实上在8和之后,结果是0.79999999999999993338661852249060757458209991455078125
,这是更小的。
另外,如果我检查两者的二进制输出,我可以看到总和比“原始”更“高”:
0.8 stored as binary 0 01111111110 1001100110011001100110011001100110011001100110011001 // smaller
sum stored as binary 0 01111111110 1001100110011001100110011001100110011001100110011010 // higher
但是0.79999999999999993338661852249060757458209991455078125
< 0.8000000000000000444089209850062616169452667236328125
。
你能照我吗?
编辑 :对不起,我复制/粘贴二进制文件时出错。 他们是对的。
在每次算术运算后都会发生IEEE浮点舍入 。 四舍五入可能会上升或下降。 如果在每次迭代时打印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
你假设四舍五入只能上升。 但是,由于“Round to nearest,ties to even”是IEEE 754中的默认舍入模式,因此在每次迭代时都会选择最接近的二进制可表示值,因此结果不必大于0.8
。
另一方面
std::cout << 0.1 * 8.0 << std::endl;
会产生预期的
0.8000000000000000444089209850062616169452667236328125
更新:如注释中提到的@Evg,可以使用std::fesetround
更改浮点舍入方向。
你的二进制表示是错误的。 正确的是:
sum = 0.79999999999999993 ... =
0b0011111111101001100110011001100110011001100110011001100110011001
numIteration / 10.0 = 0.80000000000000004... =
0b0011111111101001100110011001100110011001100110011001100110011010
通常,当您向较大的总和添加小增量时会出现问题。 没有足够的精度来存储完整的结果,并且失去了一些重要性。 通过循环的最后一次迭代,你已经开始遇到这种情况。
对于足够大的总和和小的增量,总和可能根本不会改变。
虽然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
中将最后一个数字5用于向上舍入(在十进制系统中),所以对于向上舍入, 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,并且当加倍时,导致向上舍入以十进制发生。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.