分析C ++函数中的数值错误


20

假设我有一个函数,将多个浮点值(单或双)作为输入,进行一些计算,然后生成输出浮点值(也为单或双)。我主要使用MSVC 2008,但还计划使用MinGW / GCC。我在用C ++编程。

以编程方式测量结果中有多少错误的典型方法是什么?假设我需要使用任意精度库:如果我不关心速度,那么最好的此类库是什么?

Answers:


17

如果您想为舍入误差找到一个很好的界限,则不一定需要一个全精度的库。您可以改用运行错误分析。

我找不到很好的在线参考,但是所有这些都在Nick Higham的书“数值算法的准确性和稳定性”的3.3节中进行了介绍。这个想法很简单:

  1. 重构代码,以便在每行上分配一个算术运算。
  2. 例如x,对于每个变量,创建一个变量x_err,该变量在x分配了常量后将初始化为零。
  3. 例如z = x * y,对于每个操作,请z_err使用浮点算法的标准模型以及由此产生的z和运行中的错误x_err和更新变量y_err
  4. 然后,函数的返回值还应_err附加一个相应的值。这是与总舍入误差相关的数据范围。

棘手的部分是步骤3。对于最简单的算术运算,您可以使用以下规则:

  • z = x + y -> z_err = u*abs(z) + x_err + y_err
  • z = x - y -> z_err = u*abs(z) + x_err + y_err
  • z = x * y -> z_err = u*abs(z) + x_err*abs(y) + y_err*abs(x)
  • z = x / y -> z_err = u*abs(z) + (x_err*abs(y) + y_err*abs(x))/y^2
  • z = sqrt(x) -> z_err = u*abs(z) + x_err/(2*abs(z))

u = eps/2单位取整在哪里。是的,对于规则+-是相同的。op(x)使用应用于的结果的泰勒级数展开式,可以轻松提取任何其他运算的规则op(x + x_err)。或者,您可以尝试谷歌搜索。或使用Nick Higham的书。

例如,考虑下面的Matlab / Octave代码,该代码使用Horner方案a在一点上计算系数中的多项式x

function s = horner ( a , x )
    s = a(end);
    for k=length(a)-1:-1:1
        s = a(k) + x*s;
    end

第一步,我们将两个操作拆分为s = a(k) + x*s

function s = horner ( a , x )
    s = a(end);
    for k=length(a)-1:-1:1
        z = x*s;
        s = a(k) + z;
    end

然后,我们介绍_err变量。需要注意的是输入ax被认为是准确的,但我们也可以同样也需要用户通过相应值a_errx_err

function [ s , s_err ] = horner ( a , x )
    s = a(end);
    s_err = 0;
    for k=length(a)-1:-1:1
        z = x*s;
        z_err = ...;
        s = a(k) + z;
        s_err = ...;
    end

最后,我们应用上述规则获取错误项:

function [ s , s_err ] = horner ( a , x )
    u = eps/2;
    s = a(end);
    s_err = 0;
    for k=length(a)-1:-1:1
        z = x*s;
        z_err = u*abs(z) + s_err*abs(x);
        s = a(k) + z;
        s_err = u*abs(s) + z_err;
    end

请注意,由于我们没有no a_errx_err,例如,假设它们为零,因此在错误表达式中将忽略各个术语。

等等!现在,我们有了一个Horner方案,它随结果返回一个与数据有关的错误估计值(注意:这是错误的上限)。

附带说明一下,由于您使用的是C ++,因此您可以考虑针对浮点值创建自己的类,该类包含该_err术语,并如上所述重载所有算术运算以更新这些值。对于大型代码,这可能会更容易,尽管计算效率较低。话虽如此,您也许可以在线找到这样的课程。快速的Google搜索为我提供了此链接

PS注意,这仅在严格遵守IEEE-754的机器上起作用,即所有算术运算都精确到。与定义间隔相比,此分析还提供了更严格,更实际的界限,因为根据定义,您不能在浮点数中表示数字,即,间隔只会舍入到数字本身。x 1 ± u ±ux(1±u)


1
+1,因为它很有趣。我喜欢Higham的作品。我担心的是,随着数字运算的数量变大,要求用户手动编写额外的代码(而不是像间隔算法那样的半自动代码)容易出错。
Geoff Oxberry 2012年

1
@GeoffOxberry:我完全同意复杂性问题。对于较大的代码,我强烈建议编写一个类/数据类型,以使操作在double上重载,从而只需正确地实现每个操作一次即可。对于Matlab / Octave似乎没有这样的东西,我感到非常惊讶。
2012年

我喜欢这种分析,但是由于误差项的计算也是在浮点数中进行的,由于浮点误差,这些误差项难道不是很精确吗?
–plasmcel

8

Victor Shoup的NTL是一个很好的可移植且开源的库,用于任意精度的浮点运算(以及许多其他功能),可以C ++源代码形式提供。

较低级别的是GNU多精度(GMP)Bignum库,它也是一个开源软件包。

NTL可以与GMP一起使用,因为它需要更快的性能,但是NTL提供了自己的基本例程,如果您“不关心速度”的话,肯定可以使用。GMP声称是“最快的bignum库”。GMP主要是用C编写的,但是具有C ++接口。

补充:尽管区间算术可以自动给出确切答案的上限和下限,但这并不能在“标准”精度计算中准确地测量误差,因为区间大小通常随每个操作而增加(无论是相对大小还是相对大小)绝对错误感)。

查找误差大小(对于舍入误差或离散化误差等)的典型方法是计算额外的精度值,并将其与“标准”精度值进行比较。仅需要适度的额外精度来确定误差大小本身,以达到合理的精度,因为在“标准”精度下,舍入误差本身比在额外精度计算中要大得多。

可以通过比较单精度和双精度计算来说明这一点。请注意,在C ++中,中间表达式始终以(至少)双精度计算,因此,如果我们要说明“纯”单精度计算的结果,则需要以单精度存储中间值。

C代码片段

    float fa,fb;
    double da,db,err;
    fa = 4.0;
    fb = 3.0;
    fa = fa/fb;
    fa -= 1.0;

    da = 4.0;
    db = 3.0;
    da = da/db;
    da -= 1.0;

    err = fa - da;
    printf("Single precision error wrt double precision value\n");
    printf("Error in getting 1/3rd is %e\n",err);
    return 0;

上面的输出(Cygwin / MinGW32 GCC工具链):

Single precision error wrt double precision value
Error in getting 1/3rd is 3.973643e-08

因此,该误差与将1/3舍入到单精度所期望的误差有关。一个人(我怀疑)不会在纠错中获得超过几个小数位,因为对误差的测量是针对幅度而不是准确性。


您的方法在数学上绝对是正确的。我认为这是严格的权衡;精于错误的人会指出间隔算术的严格性,但是我怀疑在许多应用程序中,以更高的精度进行计算就足够了,并且正如您所指出的那样,由此产生的错误估计可能会更严格。
Geoff Oxberry 2012年

这是我想象中会使用的方法。我可能会尝试这些不同的技术中的一些,以了解最适合我的应用程序的技术。代码示例更新非常感谢!
user_123abc

7

GMP(即GNU多精度库)是我所知道的最好的任意精度库。

我不知道任何编程方法可以测量任意浮点函数的结果中的错误。您可以尝试做的一件事是使用间隔算术来计算函数的间隔扩展。在C ++中,您将不得不使用某种库来计算区间扩展;例如,Boost Interval算法库就是这样一种。基本上,要测量该错误,您可以将函数舍入作为参数提供给参数,该函数的舍入宽度(大约为单位舍入值的2倍),以感兴趣的值为中心,然后您的输出将是一个间隔的集合,即这会给您一些保守的误差估计。这种方法的困难在于,以这种方式使用的区间算术可以高估大量错误,但是这种方法是我能想到的最“程序化”的方法。


啊,我刚刚注意到您的答案中提到了区间算术...赞成!
阿里

2
理查德•哈里斯(Richard Harris)在ACCU杂志的“ 超载 ”(Overload)上写了一系列关于浮点蓝调的优秀文章。他有关区间算术的文章在过载103pdf,p19-24)中。
Mark Booth 2012年

6

通过间隔分析可以实现严格而自动的误差估计。您使用间隔而不是数字。例如添加:

[a,b] + [c,d] = [min(a+c, a+d, b+c, b+d), max (a+c, a+d, b+c, b+d)] = [a+c, b+d]

舍入也可以严格处理,请参见舍入间隔算法

只要您的输入包含狭窄的间隔,估计就可以,而且计算起来也很便宜。不幸的是,该错误经常被高估,请参见依赖性问题

我不知道任何任意的精度区间算术库。

间隔算术是否可以满足您的需要取决于您的问题。


4

GNU MPFR库是具有精度高(尤其是正确的舍入的所有操作,这是不容易,因为它听起来)作为他们的主要关注点之一的任意精度浮点库。它在后台使用GNU MP。它有一个称为MPFI的扩展程序,它可以执行间隔算术,正如Geoff的答案所暗示的那样,它可能会派上用场以进行验证:继续提高工作精度,直到结果间隔落入较小的范围之内。

但是,这并不总是有效的。特别是,如果您要进行数值积分之类的工作(每一步都带有独立于舍入问题的“错误”),则不一定有效。在这种情况下,请尝试使用诸如COZY infinity之类的专用软件包,该软件包使用特定的算法来限制积分误差(并使用所谓的泰勒模型而不是区间)可以很好地做到这一点。


我同意; 数值积分绝对是天真的区间算法可能出错的情况。但是,即使泰勒模型也使用间隔算法。我对Makino和Berz的工作很熟悉,我相信他们在RE Moore的意义上使用泰勒模型,尽管他们也使用涉及“微分代数”的技巧。
Geoff Oxberry 2012年

@GeoffOxberry:是的-我认为这个微分代数可以用来限制积分步骤中的错误。
Erik P.

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.