2

假设我有一个无理数,例如\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);
}

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

#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

我该如何解决这个错误?

4

6 回答 6

2

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

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

于 2013-03-07T16:35:52.827 回答
1

printf当您在没有精度的情况下使用时,将双精度数舍入到 6 位%f

例如

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是相同的。

于 2013-03-07T16:26:56.173 回答
0

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

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

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

对于一些简单的计算,使用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 内产生结果。

于 2013-03-07T22:30:36.210 回答
0

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

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

于 2013-03-07T16:30:38.863 回答
0

Double: 1.732051你在这里打印有误printf("Double:\t\t%f\n", myVar);

double myVar 的实际值为

1.732050807568877281 //18 digits

所以 1.732050807568877281-1.732050807568877281 为零

于 2013-03-07T16:24:18.397 回答
0

获得保证包含计算实际值的区间的一种方法是使用区间算术。然后,将double结果与区间进行比较可以告诉您double计算与实际计算的最差情况。

Frama-C 的价值分析可以通过 option 为您做到这一点-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如果程序使用实数计算,变量中的值在doublebounds 内[1.7320508075688772 .. 1.7320508075688774]

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

于 2013-03-10T09:46:35.603 回答