假设我有一个函数,将多个浮点值(单或双)作为输入,进行一些计算,然后生成输出浮点值(也为单或双)。我主要使用MSVC 2008,但还计划使用MinGW / GCC。我在用C ++编程。
以编程方式测量结果中有多少错误的典型方法是什么?假设我需要使用任意精度库:如果我不关心速度,那么最好的此类库是什么?
假设我有一个函数,将多个浮点值(单或双)作为输入,进行一些计算,然后生成输出浮点值(也为单或双)。我主要使用MSVC 2008,但还计划使用MinGW / GCC。我在用C ++编程。
以编程方式测量结果中有多少错误的典型方法是什么?假设我需要使用任意精度库:如果我不关心速度,那么最好的此类库是什么?
Answers:
如果您想为舍入误差找到一个很好的界限,则不一定需要一个全精度的库。您可以改用运行错误分析。
我找不到很好的在线参考,但是所有这些都在Nick Higham的书“数值算法的准确性和稳定性”的3.3节中进行了介绍。这个想法很简单:
x
,对于每个变量,创建一个变量x_err
,该变量在x
分配了常量后将初始化为零。z = x * y
,对于每个操作,请z_err
使用浮点算法的标准模型以及由此产生的z
和运行中的错误x_err
和更新变量y_err
。_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
变量。需要注意的是输入a
和x
被认为是准确的,但我们也可以同样也需要用户通过相应值a_err
和x_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_err
或x_err
,例如,假设它们为零,因此在错误表达式中将忽略各个术语。
等等!现在,我们有了一个Horner方案,它随结果返回一个与数据有关的错误估计值(注意:这是错误的上限)。
附带说明一下,由于您使用的是C ++,因此您可以考虑针对浮点值创建自己的类,该类包含该_err
术语,并如上所述重载所有算术运算以更新这些值。对于大型代码,这可能会更容易,尽管计算效率较低。话虽如此,您也许可以在线找到这样的课程。快速的Google搜索为我提供了此链接。
PS注意,这仅在严格遵守IEEE-754的机器上起作用,即所有算术运算都精确到。与定义间隔相比,此分析还提供了更严格,更实际的界限,因为根据定义,您不能在浮点数中表示数字,即,间隔只会舍入到数字本身。x (1 ± u )
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舍入到单精度所期望的误差有关。一个人(我怀疑)不会在纠错中获得超过几个小数位,因为对误差的测量是针对幅度而不是准确性。
GMP(即GNU多精度库)是我所知道的最好的任意精度库。
我不知道任何编程方法可以测量任意浮点函数的结果中的错误。您可以尝试做的一件事是使用间隔算术来计算函数的间隔扩展。在C ++中,您将不得不使用某种库来计算区间扩展;例如,Boost Interval算法库就是这样一种库。基本上,要测量该错误,您可以将函数舍入作为参数提供给参数,该函数的舍入宽度(大约为单位舍入值的2倍),以感兴趣的值为中心,然后您的输出将是一个间隔的集合,即这会给您一些保守的误差估计。这种方法的困难在于,以这种方式使用的区间算术可以高估大量错误,但是这种方法是我能想到的最“程序化”的方法。
在GNU MPFR库是具有精度高(尤其是正确的舍入的所有操作,这是不容易,因为它听起来)作为他们的主要关注点之一的任意精度浮点库。它在后台使用GNU MP。它有一个称为MPFI的扩展程序,它可以执行间隔算术,正如Geoff的答案所暗示的那样,它可能会派上用场以进行验证:继续提高工作精度,直到结果间隔落入较小的范围之内。
但是,这并不总是有效的。特别是,如果您要进行数值积分之类的工作(每一步都带有独立于舍入问题的“错误”),则不一定有效。在这种情况下,请尝试使用诸如COZY infinity之类的专用软件包,该软件包使用特定的算法来限制积分误差(并使用所谓的泰勒模型而不是区间)可以很好地做到这一点。
有人告诉我,如果您使用Visual Studio,则MPIR是一个很好的库: