我可以计算双打引入的误差吗?

时间:2013-03-07 16:17:53

标签: c floating-point double ieee-754

假设我有一个像\sqrt{3}这样的无理数。由于它是不合理的,它没有十进制表示。因此,当您尝试使用IEEE 754 double表示它时,您将引入错误。

带有大量数字的十进制表示形式为:

1.7320508075688772935274463415058723669428052538103806280558069794519330169088
  00037081146186757248575675...

现在,当我计算\sqrt{3}时,我得到1.732051

#include <stdio.h> // printf
#include <math.h>   // needed for sqrt

int main() {
    double myVar = sqrt (3);
    printf("as double:\t%f\n", myVar);
}

根据Wolfram|Alpha,我的错误为1.11100... × 10^-7

我有什么方法可以自己计算错误吗?

(我不介意切换到C ++,Python或Java。如果没有简单的替代方案,我也可以使用Mathematica)

只是为了澄清:我不想要一个仅适用于sqrt {3}的解决方案。我想得到一个函数,给我任何数字的错误。如果那是不可能的,我至少想知道Wolfram | Alpha如何获得更多价值。

我的尝试

在写这个问题时,我发现了这个:

#include <stdio.h> // printf
#include <math.h>  // needed for sqrt
#include <float.h> // needed for higher precision

int main() {
    long double r = sqrtl(3.0L);
    printf("Precision: %d digits; %.*Lg\n",LDBL_DIG,LDBL_DIG,r);
}

根据Wolfram|Alpha,我可以将错误降至2.0 * 10^-18。所以我认为这可能足够接近,可以很好地估计错误。我写了这个:

#include <stdio.h> // printf
#include <math.h>  // needed for sqrt
#include <float.h>

int main() {
    double myVar = sqrt (3);
    long double r = sqrtl(3.0L);
    long double error = abs(r-myVar) / r;
    printf("Double:\t\t%f\n", myVar);
    printf("Precision:\t%d digits; %.*Lg\n",LDBL_DIG,LDBL_DIG,r);
    printf("Error:\t\t%.*Lg\n", LDBL_DIG, error);
}

但它输出:

Double:     1.732051
Precision:  18 digits; 1.73205080756887729
Error:      0

如何解决此错误?

6 个答案:

答案 0 :(得分:2)

每个程序员应该了解Goldberg的浮点运算是您正在寻找的明确指南。

https://ece.uwaterloo.ca/~dwharder/NumericalAnalysis/02Numerics/Double/paper.pdf

答案 1 :(得分:1)

当你使用printf而没有精确度时,

%f轮可以翻倍到6个位置。

e.g。

double x = 1.3;
long double y = 1.3L;
long double err = y - (double) x;
printf("Error %.20Lf\n", err);

我的输出:-0.00000000000000004445

如果结果为0,则您的long doubledouble相同。

答案 2 :(得分:0)

您在此Double: 1.732051

打印printf("Double:\t\t%f\n", myVar);时出错

double myVar的实际值是

1.732050807568877281 //18 digits

所以1.732050807568877281-1.732050807568877281为零

答案 3 :(得分:0)

根据C标准printf("%f", d)将默认为小数点后的6位数。这不是双精度的全部精度。

可能是您的架构上的双倍和长双倍碰巧相同。我的架构上有不同的大小,并且在示例代码中出现非零错误。

答案 4 :(得分:0)

在计算错误时,您希望fabsl而不是abs,至少在使用C时。(在C中,abs是整数。)通过此替换,我得到:

Double:     1.732051
Precision:  18 digits; 1.73205080756887729
Error:      5.79643049346087304e-17

(使用Apple clang 4.0在Mac OS X 10.8.3上计算。)

使用long double估算double中的错误是一种简单计算的合理方法,除了:

  • 如果您要计算更准确的long double结果,为什么还要打扰double
  • 计算序列中的错误行为很难描述,并且可能会增长到long double无法准确估计确切结果的程度。
  • 存在long double得到的结果不如double的反常情况。 (当有人构建一个教导学生上课的例子时,通常会遇到这种情况,但它们仍然存在。)

通常,在计算序列中没有简单有效的方法来计算浮点结果中的误差。如果有的话,它实际上是一种计算更准确结果的方法,我们将使用它而不是单独的浮点计算。

在特殊情况下,例如在开发数学库例程时,会仔细研究由特定代码序列产生的错误(并且根据需要重新设计代码以具有可接受的错误行为)。更常见的是,通过执行各种“实验”来估计误差,以查看有多少结果随着输入的变化而波动或通过研究系统的一般数学行为。

你还问过“我想得到一个函数,它给出了任何数字的错误。”嗯,这很容易,给定任何数字 x 和计算结果 x' ,错误正是 x' - x 。实际问题是您可能没有 x 的描述,可以用来轻松地评估该表达式。在您的示例中, x 是sqrt(3)。显然,错误是sqrt(3) - x x 正好是1.732050807568877193176604123436845839023590087890625。现在您需要做的就是评估sqrt(3)。换句话说,数值评估错误与数字评估原始数字一样困难。

是否有某类数字需要执行此分析?

另外,你真的想要计算错误或只是错误的良好界限吗?后者稍微容易些,但计算序列仍然很难。对于所有基本操作,IEEE 754要求生成的结果是最接近数学精确结果的结果(在使用舍入模式的适当方向上)。在舍入到最近模式中,这意味着每个结果最多为1/2 ULP(精度最低的单位),远离精确结果。对于诸如标准数学库(正弦,对数等等)中的操作,大多数库将在精确结果的几个ULP内产生结果。

答案 5 :(得分:0)

获得保证包含计算实际值的区间的一种方法是使用interval arithmetic。然后,将double结果与间隔进行比较可以告诉您double计算在多大程度上与实际计算的距离。

Frama-C的价值分析可以通过选项-all-rounding-modes为您完成。

double Frama_C_sqrt(double x);

double sqrt(double x)
{
  return Frama_C_sqrt(x);
}

double y;

int main(){
  y = sqrt(3.0);
}

使用以下方法分析程序:

frama-c -val t.c -float-normal -all-rounding-modes
[value] Values at end of function main:
      y ∈ [1.7320508075688772 .. 1.7320508075688774]

这意味着sqrt(3)的实际值,因此如果程序使用实数计算,则变量y中的值在double范围内{{1 }}

Frama-C的价值分析不支持[1.7320508075688772 .. 1.7320508075688774]类型,但如果我理解正确,您只使用long double作为参考来估算long double所犯的错误。该方法的缺点是double本身是不精确的。使用Frama-C值分析中实现的区间运算,计算的实际值保证在显示的范围内。