C 对单个精度(浮点)值求和时的错误传播
我正在学习单精度,希望了解误差传播。根据法律,加法是一种危险的操作 所以我写了一个小的C程序来测试错误加起来有多快。我不完全确定这是否是一种有效的测试方法。如果是,我不确定如何解释结果,见下文C 对单个精度(浮点)值求和时的错误传播,c,floating-point-precision,C,Floating Point Precision,我正在学习单精度,希望了解误差传播。根据法律,加法是一种危险的操作 所以我写了一个小的C程序来测试错误加起来有多快。我不完全确定这是否是一种有效的测试方法。如果是,我不确定如何解释结果,见下文 #include <stdio.h> #include <math.h> #define TYPE float #define NUM_IT 168600 void increment (TYPE base, const TYPE increment, const unsign
#include <stdio.h>
#include <math.h>
#define TYPE float
#define NUM_IT 168600
void increment (TYPE base, const TYPE increment, const unsigned long num_iters) {
TYPE err;
unsigned long i;
const TYPE ref = base + increment * num_iters;
for (i=0; i < num_iters; i++ ) {
base += increment;
}
err = (base - ref)/ref;
printf("%lu\t%9f\t%9f\t%+1.9f\n", i, base, ref, err);
}
int
main()
{
int j;
printf("iters\tincVal\trefVal\trelErr\n");
for (j = 1; j < 20; j++ ) {
increment(1e-1, 1e-6, (unsigned long) (pow(2, (j-10))* NUM_IT));
}
return 0;
}
是
iters incVal refVal relErr
329 0.100328 0.100329 -0.000005347
658 0.100657 0.100658 -0.000010585
1317 0.101315 0.101317 -0.000021105
2634 0.102630 0.102634 -0.000041596
5268 0.105259 0.105268 -0.000081182
10537 0.110520 0.110537 -0.000154624
21075 0.121041 0.121075 -0.000282393
42150 0.142082 0.142150 -0.000480946
84300 0.184163 0.184300 -0.000741986
168600.268600.268600+0.000000222回答您的问题
1-IEEE浮点四舍五入到偶数尾数。这样做是为了防止误差累积总是以一种或另一种方式产生偏差;如果它总是向下四舍五入,或者向上四舍五入,那么您的错误会更大
2-168600本身没有什么特别之处。我还没有对它进行数学运算,但它很可能最终在二进制表示中生成一个更干净的值(即,一个理性/非重复值)。看看二进制而不是十进制的值,看看这个理论是否成立
3-限制因素可能是由于浮点尾数的长度为23位。一旦base
达到一定的大小,increment
与base
相比是如此之小,以至于计算base+increment
然后将尾数取整回23位完全消除了变化。也就是说,base
和base+increment
之间的区别在于舍入误差。首先,重要的是要知道,0.1
不能精确表示,在二进制中它有周期性重复的数字。该值应为0.000110011…
。比较1/3和1/7是如何用十进制数字表示的。值得使用增量0.25
重复您的测试,它可以精确地表示为0.01
我将用十进制来说明错误,这是我们人类习惯的。让我们使用十进制,假设我们可以有4位精度。这些就是这里发生的事情
- 划分:让我们计算1/11:
1/11等于0.0909…,可能四舍五入到0.09091。正如预期的那样,这是正确的4位有效数字(粗体)
- 震级差异:假设我们计算10+1/11
当把1/11加到10时,我们必须做更多的舍入,因为10.091是7个有效数字,我们只有4个。我们必须在点后将1/11四舍五入到两位数,计算出的和是10.09。这是低估了。请注意,如何仅保留1/11的一个有效数字。如果将许多小值加在一起,这将限制最终结果的精度
- 现在计算100+1/11。现在我们将1/11四舍五入到0.1,并将总和表示为100.1。现在我们有一点高估,而不是一点低估
我的猜测是,测试中的符号变化模式是系统性轻微低估与高估的结果,取决于
base
的大小
- 1000+1/11怎么样?现在我们不能在点之后有任何数字,因为在点之前已经有4个有效数字了。1/11现在四舍五入为0,总和仍然1000。你看到的就是那堵墙
- 在测试中没有看到的另一件重要事情是:如果这两个值有不同的符号,会发生什么。计算1.234–1.243:两个数字都有4个有效数字。结果为-0.009。现在,结果只有一个正确的有效数字,而不是四个
这里有一个类似问题的答案:。它有一些指向更多信息的链接。如果增量值在加法过程中保持不变,并且从零开始,则您所碰到的“墙”与增量值无关。它必须与iters
一起使用。2^23=800万,您正在进行8600万次添加。所以一旦累加器比增量大2^23,你就会撞到墙上
尝试以86323200次迭代运行代码,但增量为1或0.0000152587890625(或2的任意幂)。它应该具有与增量32相同的相对问题。谢谢您的回答。我可以大致理解你的第一点。二,。(我必须验证你的假设),但是3的解释。我不清楚。这并不是说increment*num_iters
的值不正确。我注意到总和的值(base
在上面的代码中)不再增加了。哦,我明白你现在说的了。问题是,一旦base
达到一定的大小,increment
与base
相比非常小,因此计算base+increment
然后将尾数取整回23位完全消除了变化。也就是说,base
和base+increment
之间的区别在于舍入误差。十进制类比非常有见地,非常感谢。特别是,对墙的解释很有意义。你是对的,使用0.0000152587890625
我看到了相同的图案。但是,只有在点击2^23-2^12
时,错误才会增加(2^23-2^13
仍然正常,没有错误)。有什么解释吗?尝试增量(1e-1,0.0000152587890625,(无符号长)(1*NUM_IT-pow(2,13))代码>和增量(1e-1,0.0000152587890625,(无符号长)(1*NUM_IT-pow(2,12))代码>和#定义NUM_IT 8388608
gcc -pedantic -Wall -Wextra -Werror -lm errorPropagation.c && ./a.out | tee float.dat | column -t
iters incVal refVal relErr
329 0.100328 0.100329 -0.000005347
658 0.100657 0.100658 -0.000010585
1317 0.101315 0.101317 -0.000021105
2634 0.102630 0.102634 -0.000041596
5268 0.105259 0.105268 -0.000081182
10537 0.110520 0.110537 -0.000154624
21075 0.121041 0.121075 -0.000282393
42150 0.142082 0.142150 -0.000480946
84300 0.184163 0.184300 -0.000741986
168600 0.268600 0.268600 +0.000000222 <-- *
337200 0.439439 0.437200 +0.005120996
674400 0.781117 0.774400 +0.008673230
1348800 1.437150 1.448800 -0.008041115
2697600 2.723466 2.797600 -0.026499098
5395200 5.296098 5.495200 -0.036231972
10790400 10.441361 10.890400 -0.041232508
21580800 25.463778 21.680799 +0.174485177
43161600 32.000000 43.261597 -0.260313928 <-- **
86323200 32.000000 86.423195 -0.629729033