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

我正在学习单精度,希望了解误差传播。根据法律,加法是一种危险的操作

所以我写了一个小的C程序来测试错误加起来有多快。我不完全确定这是否是一种有效的测试方法。如果是,我不确定如何解释结果,见下文

#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