sin、cos、tan和舍入误差

42fyovps  于 2023-01-25  发布在  其他
关注(0)|答案(9)|浏览(186)

我正在用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。
在使用trig函数时,我可以预期有多大的舍入误差?我如何最好地处理这个误差?我熟悉布鲁斯Dawson的Comparing Floating Point Numbers中用于比较浮点数的Units in Last Place技术,但它似乎在这里不起作用,因为0和1.22e-16相差很多ULP。

axkjgtzd

axkjgtzd1#

对于sin(pi),答案只有0--你把Pi的所有数字都算进去了吗?

  • 有没有人注意到这里明显缺乏讽刺/幽默感?
gpnt7bae

gpnt7bae2#

一个IEEE双精度浮点数存储52位尾数,其中“隐式前导1”形成一个53位数。因此,结果最后一位的错误约占小数位数的1/2^53。您的输出与1.0的阶数相同。所以结果是10^16的一部分(因为53*log(2)/log(10)== 15.9)。
所以是的。这是你所能期望的精确度的极限。我不确定你使用的ULP技术是什么,但我怀疑你用错了。

sauutmhj

sauutmhj3#

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

M_PI不是π

当正确答案当然是0时,程序给出... 1.224647e-16。
代码给出了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_PI是最接近π的double常数,然而由于π是无理数,并且所有有限的double都是有理的,它们必须不同-即使是很小的差异。因此,这不是sin(), cos(), tan()的直接舍入问题。sin(M_PI)简单地暴露了使用M_PI开始的问题-不精确的π。
如果代码使用不同的FP类型,如floatlong doubledouble,并且精度不是53个二进制位,则会出现sin(M_PI)具有不同非零结果的问题。这不是精度问题,而是无理/有理问题。

z2acfund

z2acfund4#

@Josh Kelley --好的认真回答。
一般来说,永远不要将任何涉及浮点数或双精度数的运算结果进行相互比较。
唯一的例外是赋值。
浮动a=10.0;
浮动B=10.0;
则a==B
否则你必须写一些函数比如bool IsClose(float a,float b,float error)来检查两个数字是否在彼此的“error”范围内。
记住还要检查标志/使用晶圆厂-您可能有-1.224647e-16

8i9zcol2

8i9zcol25#

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

dgsult0t

dgsult0t6#

我认为这取决于系统。我不认为标准对超越函数的精确度有任何说明。不幸的是,我不记得看到过任何关于函数精确度的讨论,所以你可能要自己弄清楚。

neskvpey

neskvpey7#

除非你的程序需要有效数字到小数点后第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;
}
9o685dep

9o685dep8#

在我的系统上得到了完全相同的结果-我得说它足够接近了
我可以通过将格式字符串更改为“%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;
}
cngwdvgl

cngwdvgl9#

可能实现的准确性太低

M_PI = 3.14159265358979323846 (M_PI is not π)

http://fresh2refresh.com/c/c-function/c-math-h-library-functions/
这是一个不准确的实现,见斯蒂芬C.钢的意见下安迪罗斯'回答以上和chux的答案。

相关问题