【问题标题】:What is a more accurate algorithm I can use to calculate the sine of a number?我可以使用什么更准确的算法来计算数字的正弦?
【发布时间】:2019-09-21 03:52:03
【问题描述】:

我有这段代码计算正弦的猜测并将其与标准 C 库(在我的例子中是 glibc)的结果进行比较:

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

double double_sin(double a)
{
    a -= (a*a*a)/6;

    return a;
}

int main(void)
{
    double clib_sin = sin(.13),
             my_sin = double_sin(.13);
    printf("%.16f\n%.16f\n%.16f\n", clib_sin, my_sin, clib_sin-my_sin);
    return 0;
}

double_sin 的准确度很差(大约 5-6 位)。这是我的输出:

0.1296341426196949
0.1296338333333333
0.0000003092863615

如您所见,.12963 之后,结果不同。

一些注意事项:

  • 我认为泰勒级数不适用于这种特定情况,更高准确度所需的阶乘无法存储在 unsigned long long 中。

  • 查找表不是一种选择,它们占用太多空间,并且通常不提供任何有关如何计算结果的信息。

  • 如果您使用幻数,请解释它们(尽管我更喜欢不使用它们)。

  • 我更喜欢一种算法易于理解并且能够用作参考,而不是那些不是的算法。

  • 结果不必完全准确。最低要求是 IEEE 754、C 和/或 POSIX。

  • 我使用的是IEEE-754 double格式,可以信赖。

  • 支持的范围至少需要从-2*M_PI2*M_PI。如果包括范围缩小,那就太好了。

我可以使用什么更准确的算法来计算数字的正弦?

我有一个类似于 Newton-Raphson 的想法,但用于计算正弦值。 但是,我在上面找不到任何东西,正在排除这种可能性。

【问题讨论】:

  • 在最好的情况下,您可以从浮点数中获得 6-9 位的准确度。更多你需要使用双重。即使那样,由于 ieee 754 浮点数学的性质,您的数字也永远不会与 sin() 函数的版本完全相同。
  • (a) unsigned long long 与什么有什么关系?代码中没有unsigned long long。 (b) 你需要支持什么领域? (c) 你需要什么精度?
  • x 的正弦完全由 x 的余数除以 2pi 决定,比 2**3 小一点。您需要大约 100 位余数来处理它恰好非常小的情况。最大的有限double 约为 2**1024。因此,支持double 的整个域需要以一种或另一种方式将大约 1100 位的 2pi 构建到实现中。这通常通过在参数缩减期间使用的表来完成。这与您不使用表格的规定相冲突。在没有大量准备好的数据的情况下支持整个域是不可行的。
  • (100 位估计来自内存。找到最坏的情况很复杂,需要一些数论。)
  • IIRC,double 的参数减少期间余数中前导零位的最大数量为 62,因此肯定需要超过 100 位余数。实现一个忠实的四舍五入的实现还需要用比double 更多的位来表示余数。所以余数计算的 120 位可能是更接近的估计。

标签: c math floating-point trigonometry ieee-754


【解决方案1】:

您实际上可以非常接近泰勒级数。诀窍不是每次迭代都计算全阶乘。

泰勒级数如下所示:

sin(x) = x^1/1! - x^3/3! + x^5/5! - x^7/7!

查看各项,您可以通过将分子乘以 x^2、将分母乘以阶乘中接下来的两个数字并切换符号来计算下一项。然后在添加下一项不会改变结果时停止。

所以你可以这样编码:

double double_sin(double x)
{
    double result = 0;
    double factor = x;
    int i;

    for (i=2; result+factor!=result; i+=2) {
        result += factor;
        factor *= -(x*x)/(i*(i+1));
    }
    return result;
}

我的输出:

0.1296341426196949
0.1296341426196949
-0.0000000000000000

编辑:

如果以相反的方向添加术语,则可以进一步提高准确性,但这意味着计算固定数量的术语:

#define FACTORS 30

double double_sin(double x)
{
    double result = 0;
    double factor = x;
    int i, j;
    double factors[FACTORS];

    for (i=2, j=0; j<FACTORS; i+=2, j++) {
        factors[j] = factor;
        factor *= -(x*x)/(i*(i+1));
    }
    for (j=FACTORS-1;j>=0;j--) {
        result += factors[j];
    }
    return result;
}

如果 x 超出 0 到 2*PI 的范围,则此实现会失去准确性。这可以通过在函数开始时调用x = fmod(x, 2*M_PI); 来规范化值来解决。

【讨论】:

  • @JL2210 它们仅在最后一位上相差一个。如果您使用%a 打印十六进制表示,您会得到0x1.097da017f903cp-30x1.097da017f903dp-3
  • 对于x = 1.5707963267948965579989817342720925807952880859375(尽可能接近IEEE-754 binary64获取到π/ 2),此例程产生1.0000000000000002220446049250313080847263336181640625。尽管该错误可能在 OP 的精度范围内,但通常认为正弦例程返回 [-1, +1] 之外的值是错误的,因为可以编写使用正弦和余弦的代码来假设所有计算值都在功能。另外,它需要11次迭代,每次迭代都有一个除法,所以速度并不快。
  • 对于x = 3.141592653589793115997963468544185161590576171875(尽可能接近 IEEE-754 到 π),此例程返回的值是正确值的两倍,因此它具有很大的相对误差。另外,它需要 22 次迭代,所以速度并不快。
  • 对于x = 100,此例程生成 -144871497020291474280939520。正确的结果大约是 0.50636564110975879060561055666767060756683349609375。
  • @JL2210 先做,这样在你做任何事情之前值都会被标准化。
猜你喜欢
  • 2014-01-22
  • 1970-01-01
  • 1970-01-01
  • 2010-10-06
  • 1970-01-01
  • 2019-04-01
  • 2019-08-13
  • 2011-06-03
  • 2016-06-16
相关资源
最近更新 更多