6

我正在用 C/C++ 进行一些三角函数计算,并且遇到了舍入错误的问题。例如,在我的 Linux 系统上:

#include <stdio.h>
#include <math.h>

int main(int argc, char *argv[]) {
    printf("%e\n", sin(M_PI));
    return 0;
}

该程序给出以下输出:

1.224647e-16

当正确答案当然是 0 时。

使用三角函数时,我可以预期多少舍入误差?我怎样才能最好地处理这个错误?我熟悉 Bruce Dawson 的Comparing Floating Point Numbers中用于比较浮点数的 Units in Last Place 技术,但这似乎在这里不起作用,因为 0 和 1.22e-16 相隔相当多的 ULP。

4

9 回答 9

16

sin(pi) 的答案只有 0 - 你是否包括了 Pi 的所有数字?

-有没有其他人注意到这里明显缺乏讽刺/幽默感?

于 2009-10-06T19:29:42.417 回答
13

IEEE double 存储 52 位尾数,其中“隐式前导一位”形成 53 位数字。因此,结果底部位的错误约占数字比例的 1/2^53。您的输出与 1.0 的顺序相同,因此它恰好是 10^16 的一部分(因为 53*log(2)/log(10) == 15.9)。

所以是的。这大约是您可以预期的精度极限。我不确定您使用的 ULP 技术是什么,但我怀疑您使用错误。

于 2009-10-06T19:36:38.037 回答
10

π 的正弦值为 0.0。
的正弦M_PI值约为 1.224647e-16。

M_PI不是π。

当正确答案当然是 0 时,程序给出 ... 1.224647e-16。

Code 给出了 7 个地方的正确答案。


以下不打印π的正弦。它打印接近 π 的数字的正弦值。见下图。

π                            // 3.141592653589793 2384626433832795...
printf("%.21\n", M_PI);      // 3.141592653589793 115998
printf("%.21f\n", sin(M_PI));// 0.000000000000000 122465

注意:使用数学函数sine(x),曲线的斜率在x = π处为 -1.0 。π 和 的差值M_PI大约是sin(M_PI)-正如预期的那样。


我遇到了舍入错误的问题

使用 M_PI表示 π 时会出现舍入问题。 M_PIdouble最接近 π 的,但由于 π 是无理的,并且所有有限项double都是有理的,因此它们必须有所不同——即使相差很小。所以不是直接的舍入问题sin(), cos(), tan()sin(M_PI)simple 暴露了从使用不精确的 π 开始的问题。


sin(M_PI)如果代码使用不同的 FP 类型(如float),long double或者double使用 53 位精度以外的其他类型,则会出现此问题,该问题具有不同的非零结果。这不是一个精确问题,而是一个非理性/理性问题。

π 附近的 Sine(x)

于 2017-10-10T16:41:37.060 回答
6

@Josh Kelley - 好的,认真的回答。
通常,您永远不应该将任何涉及浮点数或双精度数的操作的结果相互比较。

唯一的例外是赋值。
浮动一个=10.0;
浮动 b=10.0;
然后 a==b

否则,您总是必须编写一些函数,例如 bool IsClose(float a,float b, float error) 以允许您检查两个数字是否在彼此的“错误”范围内。
记住还要检查标志/使用工厂 - 你可以有 -1.224647e-16

于 2009-10-06T19:42:40.113 回答
1

有两个错误来源。sin() 函数和 M_PI 的近似值。即使 sin() 函数是“完美的”,它也不会返回零,除非 M_PI 的值也是完美的——事实并非如此。

于 2009-10-06T22:05:22.067 回答
0

我宁愿认为这将取决于系统。我不认为标准对超越函数的准确性有什么要说的。不幸的是,我不记得看过任何关于函数精度的讨论,所以您可能必须自己弄清楚。

于 2009-10-06T19:36:32.437 回答
0

除非您的程序需要小数点后 16 位或更多的有效数字,否则您可能可以手动进行舍入。根据我编程游戏的经验,我们总是将小数四舍五入到一个可容忍的有效数字。例如:

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#define HALF 0.5
#define GREATER_EQUAL_HALF(X) (X) >= HALF

double const M_PI = 2 * acos(0.0);

double round(double val, unsigned  places = 1) 
{
    val = val * pow(10.0f, (float)places);
    long longval = (long)val;
    if ( GREATER_EQUAL_HALF(val - longval) ) {
       return ceil(val) / pow(10.0f, (float)places);
    } else {
      return floor(val) / pow(10.0f, (float)places);
    }
}

int main() 
{
    printf("\nValue %lf", round(sin(M_PI), 10));
    return 0;
}
于 2009-10-06T20:44:19.447 回答
-1

我在我的系统上得到完全相同的结果 - 我会说它足够接近

我会通过将格式字符串更改为 "%f\n" 来解决这个问题 :)

但是,这会给你一个“更好”的结果,或者至少在我的系统上它确实给了 -3.661369e-245

#include <stdio.h>
#include <math.h>

int main(int argc, char *argv[]) {
    printf("%e\n", (long double)sin(M_PI));
    return 0;
}
于 2009-10-06T19:46:51.870 回答
-2

可能实现的准确性太低

M_PI = 3.14159265358979323846 (M_PI is not π)

http://fresh2refresh.com/c/c-function/c-math-h-library-functions/

这是实施中的不准确,请参阅 Stephen C. Steel 在上面 Andy Ross 的回答和 chux 的回答下的评论。

于 2015-08-15T23:32:18.530 回答