C如何计算sin()和其他数学函数?


248

我一直在研究.NET反汇编和GCC源代码,但似乎找不到任何地方的实际实现sin()以及其他数学函数...它们似乎总是在引用其他内容。

谁能帮我找到他们?我觉得C不能运行的所有硬件都不太可能支持硬件中的trig函数,因此在某处一定有软件算法,对吗?


我所知道的几种方法,功能,可以进行计算,并写了我自己的程序来使用泰勒级数为乐趣的计算功能。我对生产语言是多么真实感到好奇,因为尽管我认为我的算法非常聪明(显然并非如此),但我所有的实现总是慢几个数量级。


2
请注意,此实现依赖于。您应指定您最感兴趣的是哪一个实现。
杰森

3
我标记了.NET和C,因为我在这两个地方都看过,但都找不到。尽管查看.NET的反汇编,但看起来它可能正在调用非托管C,据我所知,它们具有相同的实现。
汉克2010年

Answers:


213

在GNU libm中,的实现sin取决于系统。因此,您可以在sysdeps的相应子目录中的某个位置找到每种平台的实现。

一个目录包含IBM提供的C语言实现。自2011年10月以来,这是在sin()典型的x86-64 Linux系统上调用时实际运行的代码。它显然比fsin汇编指令要快。源代码:sysdeps / ieee754 / dbl-64 / s_sin.c,查找__sin (double x)

此代码非常复杂。没有一个软件算法在x值的整个范围内都尽可能快且准确,因此该库实现了几种不同的算法,其首要任务是查看x并确定使用哪种算法。

  • x非常非常接近0时,sin(x) == x是正确的答案。

  • 再往前走一点,sin(x)使用熟悉的泰勒级数。但是,这仅在0附近才是准确的,所以...

  • 当角度大于约7°时,将使用另一种算法,计算sin(x)和cos(x)的泰勒级数逼近,然后使用来自预先计算表的值来细化该逼近。

  • 何时| x | > 2,以上算法都不起作用,因此代码首先从计算一些可以接近sin或接近0的值开始cos

  • 还有另一个分支可以处理x为NaN或无穷大。

这段代码使用了一些我从未见过的数字技巧,尽管据我所知,它们在浮点专家中可能是众所周知的。有时,几行代码需要几个段落来解释。例如,这两行

double t = (x * hpinv + toint);
double xn = t - toint;

(有时)用于将x减小到接近于0的值,该值与x的差为π/ 2的倍数,特别是xn×π/ 2。无需分割或分支的完成方式相当聪明。但是,根本没有任何评论!


GCC / glibc的较旧的32位版本使用了该fsin指令,这对于某些输入而言出乎意料地不准确。有一篇引人入胜的博客文章,仅用两行代码说明了这一点

fdlibm sin在纯C中的实现比glibc 的实现简单得多,并且受到好评。源代码:fdlibm / s_sin.cfdlibm / k_sin.c


35
要了解这确实是在x86上运行的代码,请执行以下操作:编译一个调用sin();键入gdb a.out,然后break sin,然后run,然后disassemble
杰森·奥伦多夫

5
@亨利:但是不要误以为是好的代码。真的很糟糕,不要学习那样的编码!
Thomas Bonini 2010年

2
@Andreas Hmm,您是对的,与fdlibm相比,IBM代码确实很糟糕。我编辑了答案,以添加指向fdlibm的正弦例程的链接。
杰森·奥伦多夫

3
@Henry:__kernel_sin不过,它是在k_sin.c中定义的,它是纯C语言。再次单击它-我第一次破坏了URL。
杰森·奥伦多夫

3
链接的sysdeps代码特别有趣,因为它已正确舍入。也就是说,它显然为所有输入值提供了最佳可能的答案,而这只是最近才成为可能。在某些情况下,这可能很慢,因为可能需要计算许多额外的数字才能确保正确的舍入。在其他情况下,这非常快-对于足够小的数字,答案就是角度。
布鲁斯·道森

66

正弦和余弦之类的功能在微处理器内部的微代码中实现。例如,英特尔芯片具有这些芯片的组装说明。AC编译器将生成调用这些汇编指令的代码。(相比之下,Java编译器则不会。Java在软件而不是硬件中评估触发功能,因此运行起来要慢得多。)

芯片使用泰勒级数来计算触发函数,至少不是完全没有。首先,他们使用CORDIC,但也可以使用较短的泰勒级数来完善CORDIC的结果,或者在特殊情况下(例如,在很小的角度下以相对高的精度计算正弦)。有关更多说明,请参见此StackOverflow答案


10
诸如正弦和余弦之类的先验数学函数可以用微码或在当前的32位台式机和服务器处理器中作为硬件指令来实现。情况并非总是如此,直到i486(DX)的所有浮点计算都在x86系列的软件(“ soft-float”)中完成,而没有单独的协处理器。并非所有这些功能(FPU)都具有超越功能(例如Weitek 3167)。
mctylr 2010年

1
你可以说得更详细点吗?如何使用泰勒级数“抛光”近似值?
汉克

4
至于“抛光”一个答案,假设您正在计算正弦和余弦。假设您知道某一点的精确值(例如,来自CORDIC的值),但希望该值在附近的点。然后,对于一个小的差异h,您可以应用泰勒近似值f(x + h)= f(x)+ h f'(x)或f(x + h)= f(x)+ h f'(x) + h ^ 2 f''(x)/ 2。
John D. Cook 2010年

6
x86 / x64芯片具有用于计算正弦(fsin)的汇编指令,但是该指令有时非常不准确,因此已很少使用。有关详细信息,请参见randomascii.wordpress.com/2014/10/09/…。大多数其他处理器没有正弦和余弦指令,因为在软件中进行计算会提供更大的灵活性,甚至可能更快。
2014年

3
通常不使用intel芯片内部的可口东西。首先,对于许多应用而言,操作的准确性和分辨率至关重要。当您达到第7位左右时,Cordic非常不准确,并且是不可预测的。其次,我听说它们的实现存在错误,这会导致更多问题。我看了一下Linux gcc的sin函数,果然,它使用了chebyshev。没有使用内置的东西。哦,同样,芯片中的Cordic算法比软件解决方案要慢。
唐纳德·默里

63

好的,小子,为专业人士准备的时间。...这是我对经验不足的软件工程师的最大抱怨之一。他们从头开始计算先验函数(使用泰勒级数),好像从来没有人做过这些计算。不对。这是一个定义明确的问题,非常聪明的软件和硬件工程师已解决了数千次,并且拥有定义明确的解决方案。基本上,大多数先验函数都使用Chebyshev多项式来计算它们。至于使用哪种多项式取决于情况。首先,关于此事的圣经是哈特和切尼写的一本书,叫做《计算机近似》。在那本书中,您可以确定是否具有硬件加法器,乘法器,除法器等,并确定哪些操作最快。例如,如果您有一个非常快的分频器,计算正弦的最快方法可能是P1(x)/ P2(x),其中P1,P2是Chebyshev多项式。如果没有快速除法器,则可能只是P(x),其中P的项比P1或P2的项多得多。因此,第一步是确定您的硬件及其功能。然后,选择适当的Chebyshev多项式组合(例如,对于余弦,通常形式为cos(ax)= aP(x),其中P再次为Chebyshev多项式)。然后确定所需的小数精度。例如,如果您想要7位数的精度,则可以在我提到的书中的相应表格中查找,它将为您提供(精度= 7.33)数字N = 4和多项式数字3502。N是阶数多项式(所以它是p4.x ^ 4 + p3.x ^ 3 + p2.x ^ 2 + p1.x + p0),因为N = 4。然后,您查询p4,p3,p2,p1的实际值,在书本背面的p0值低于3502(它们将处于浮点状态)。然后以以下形式在软件中实现算法:(((p4.x + p3).x + p2).x + p1).x + p0 ....这是将余弦值计算为7位小数的​​方式放置在该硬件上。

请注意,FPU中超验操作的大多数硬件实现通常都涉及一些微代码和类似的操作(取决于硬件)。Chebyshev多项式用于大多数先验,但不是全部。例如,平方根更快,首先使用查找表使用牛顿拉夫森方法的两次迭代。同样,那本书《计算机近似》将告诉您。

如果您打算实现这些功能,那么我建议任何人获得该书的副本。这些算法确实是圣经。请注意,有许多替代方法可用于计算这些值,例如cordic等,但是这些方法最适合只需要低精度的特定算法。为了每次都保证精度,切比雪夫多项式是必经之路。就像我说的,定义明确的问题。已经解决了50年了.....那就是它的完成方式。

就是说,现在有一些技术可以使用Chebyshev多项式以低次多项式获得单精度结果(例如上述余弦的示例)。然后,还有其他一些技术可以在值之间进行插值以提高精度,而不必使用更大的多项式,例如“ Gal的精确表方法”。后一种技术是指ACM文献所引用的文章。但是最终,切比雪夫多项式被用来获得90%的结果。

请享用。


6
我完全同意前几句话。此外,值得回顾的是,要保证精度来计算特殊功能是一个难题。您提到的聪明人一生中大部分时间都是这样做的。同样,从技术角度上讲,最小值-最大值多项式是受欢迎的选择,而切比雪夫多项式是它们的更简单代理。
Alexandre C.

161
-1代表不专业和杂乱无章的(并且有些粗鲁)的语气,并且由于该答案的实际非冗余内容(除去杂乱无章和无礼的事实)基本上可以归结为“他们经常使用Chebyshev多项式;请参见这本书”有关更多详细信息,这真的很好!” 您知道,这很可能是绝对正确的,但这并不是我们想要的关于SO 的独立答案。如此凝结下来,尽管如此,它会对这个问题发表了不错的评论。
Ilmari Karonen

2
早在游戏开发的早期,它通常是通过对速度至关重要的查找表来完成的。通常,我们不使用标准的lib函数。
topspin

4
我在嵌入式系统中经常使用查询表和bittian(而不是弧度),但这是用于特殊应用程序(例如您的游戏)的。我认为这家伙对c编译器如何计算浮点数的sin感兴趣。–
唐纳德·默里

1
啊,五十年前。我开始在McLaren系列的Burroughs B220上玩这样的游戏。后来的CDC硬件,然后是摩托罗拉68000。Arcsin混乱不堪-我选择了两个多项式的商,并开发了代码来找到最佳系数。
里克·詹姆斯

15

对于sin具体而言,利用泰勒展开会给你:

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

您将继续添加术语,直到它们之间的差值低于可接受的公差水平或只是有限数量的步骤(更快,但不够精确)。例如:

float sin(float x)
{
  float res=0, pow=x, fact=1;
  for(int i=0; i<5; ++i)
  {
    res+=pow/fact;
    pow*=-1*x*x;
    fact*=(2*(i+1))*(2*(i+1)+1);
  }

  return res;
}

注意:(1)之所以起作用,是因为小角度的近似sin(x)= x。对于更大的角度,您需要计算越来越多的项才能获得可接受的结果。您可以使用while参数并以一定的精度继续:

double sin (double x){
    int i = 1;
    double cur = x;
    double acc = 1;
    double fact= 1;
    double pow = x;
    while (fabs(acc) > .00000001 &&   i < 100){
        fact *= ((2*i)*(2*i+1));
        pow *= -1 * x*x; 
        acc =  pow / fact;
        cur += acc;
        i++;
    }
    return cur;

}

1
如果稍微调整系数(并将其硬编码为多项式),则可以更快地停止大约2次迭代。
里克·詹姆斯

14

是的,也有用于计算的软件算法sin。基本上,通常使用数字方法(例如,近似表示该函数的泰勒级数)使用数字计算机来计算此类物质。

数值方法可以将函数近似于任意精度,并且由于您在浮点数中的精度是有限的,因此它们非常适合这些任务。


12
真正的实现可能不会使用泰勒级数,因为有更有效的方法。您只需要在域[0 ... pi / 2]中正确进行逼近,并且其中的函数比泰勒级数更有效地提供逼近。
David Thornley,2010年

2
@大卫:我同意。我足够小心地在回答中提到“喜欢”一词。但是泰勒展开式是一种简单的方法,用于解释近似函数的方法背后的思想。就是说,我已经看到使用泰勒级数的软件实现(不确定是否已优化)。
Mehrdad Afshari 2010年

1
实际上,多项式逼近是计算三角函数的最有效方法之一。
杰里米·萨尔文

13

使用泰勒级数并尝试查找级数之间的关系,以免重复计算

这是余弦的一个示例:

double cosinus(double x, double prec)
{
    double t, s ;
    int p;
    p = 0;
    s = 1.0;
    t = 1.0;
    while(fabs(t/s) > prec)
    {
        p++;
        t = (-t * x * x) / ((2 * p - 1) * (2 * p));
        s += t;
    }
    return s;
}

使用这个我们可以使用已经使用过的和获得新的总和项(我们避免阶乘和x 2p

说明


2
您知道吗,您可以使用TeX使用Google Chart API来创建这样的公式?code.google.com/apis/chart/docs/gallery/formulas.html
Gab Royer 2010年

11

这是一个复杂的问题。x86系列的类似Intel的CPU具有该sin()功能的硬件实现,但是它是x87 FPU的一部分,并且不再用于64位模式(使用SSE2寄存器代替)。在那种模式下,使用软件实现。

有几种这样的实现。其中之一是在fdlibm中,并在Java中使用。据我所知,glibc实现包含fdlibm的一部分以及IBM贡献的其他部分。

超越函数的软件实现(例如sin()通常使用多项式的近似值)通常是从泰勒级数中获得的。


3
SSE2寄存器用于计算sin(),无论是在86也不在64模式,当然,罪在硬件中计算出与模式无关。嘿,这是我们住在的2010年:)
Igor Korkhov

7
@Igor:这取决于您要查看的数学库。事实证明,x86上最优化的数学库使用SSE软件实现,sin并且cos比FPU上的硬件指令要快。更简单,更幼稚的库倾向于使用fsinfcos指令。
斯蒂芬·佳能

@Stephen Canon:那些快速库是否像FPU寄存器一样具有80位精度?我有一个很偷偷摸摸的怀疑,他们认为速度胜于精度,这当然在许多情况下都是合理的,例如在游戏中。而且我确实相信,使用SSE和预先计算的中间表以32位精度计算正弦可能比使用FSIN全精度计算正弦更快。如果您告诉我这些快速库的名称,我将不胜感激,看看这很有趣。
伊戈尔·科尔霍夫

@Igor:在64位模式下的x86上,至少在我所知道的所有类似Unix的系统上,精度都限于64位,而不是x87 FPU的79位。的软件实现sin()恰好比fsin计算的实现快大约两倍(正是因为它的执行精度较低)。请注意,已知x87的实际精度要比其宣布的79位低。
托马斯·波宁

1
实际上,msvc运行时库中sin()的32位和64位实现均不使用FSIN指令。实际上,它们给出了不同的结果,例如sin(0.70444454416678678)。这将在32位程序中导致0.64761068800896837(正确,公差为0.5 *(eps / 2)),而在64位程序中将导致0.64761068800896848(错误)。
e.tadeu

9

如另一个答案所述,切比雪夫多项式是多项式,其中函数和多项式之间的最大差值尽可能小。那是一个很好的开始。

在某些情况下,最大误差不是您感兴趣的,而是最大相对误差。例如,对于正弦函数,x = 0附近的误差应该比较大值的误差小得多。你想要一个小亲戚误差。因此,您将计算sin x / x的Chebyshev多项式,然后将该多项式乘以x。

接下来,您必须弄清楚如何评估多项式。您希望以这样的方式进行评估:中间值较小,因此舍入误差较小。否则,舍入误差可能会比多项式误差大得多。对于正弦函数之类的函数,如果您粗心大意,那么即使x <y,您为sin x计算的结果也可能大于sin y的结果。因此,需要仔细选择计算顺序并计算舍入误差的上限。

例如,sin x = x-x ^ 3/6 + x ^ 5/120-x ^ 7/5040 ...如果天真地计算sin x = x *(1-x ^ 2/6 + x ^ 4 / 120-x ^ 6/5040 ...),则括号中的该函数减小,并且如果y是x的下一个大数,则有时sin y会比sin x小。取而代之的是计算出sin x = x-x ^ 3 *(1/6-x ^ 2/120 + x ^ 4/5040 ...)的情况。

例如,在计算切比雪夫多项式时,通常需要将系数取整到双精度。但是,尽管Chebyshev多项式是最佳的,但系数舍入为双精度的Chebyshev多项式并不是双精度系数的最优多项式!

例如,对于sin(x),您需要x,x ^ 3,x ^ 5,x ^ 7等的系数。您可以执行以下操作:用多项式(x + bx ^ 3 + cx ^ 5 + dx ^ 7)的精度高于双精度,然后将a舍入为双精度,得到A。a和A之间的差异会很大。现在用多项式(bx ^ 3 + cx ^ 5 + dx ^ 7)计算(sin x-Ax)的最佳近似值。您将获得不同的系数,因为它们适应了a和A之间的差异。将b舍入为双精度B。然后使用多项式cx ^ 5 + dx ^ 7近似(sin x-Ax-Bx ^ 3),依此类推。您将获得几乎与原始Chebyshev多项式一样好的多项式,但比将Chebyshev舍入为双精度的要好得多。

接下来,应在选择多项式时考虑舍入误差。您发现在多项式中误差最小的多项式,忽略了舍入误差,但是您想优化多项式加舍入误差。拥有Chebyshev多项式后,就可以计算舍入误差的范围。假设f(x)是您的函数,P(x)是多项式,E(x)是舍入误差。您不想优化| f(x)-P(x)|,您想优化| f(x)-P(x)+/- E(x)|。您将得到一个略有不同的多项式,它试图在舍入误差较大的情况下将多项式误差减小,而在舍入误差较小的情况下将多项式误差放宽一些。

所有这一切将使您轻松舍入误差至多为最后一位的0.55倍,其中+,-,*,/舍入误差为至多最后一位的0.50倍。


1
这是一个多么漂亮的一个解释可以计算的sin(x)有效,但它并没有真正似乎回答OP的问题,这是专门关于如何共同C库/编译器计算。
Ilmari Karonen

Chebyshev多项式可将某个间隔内的最大绝对值最小化,但不会使目标函数和多项式之间的最大差最小化。Minimax多项式可以做到这一点。
埃里克·波斯特皮希尔

9

关于像三角函数sin()cos()tan()一直没有提到,5年后,高品质的三角函数的一个重要方面:范围减少

这些功能中的任何一个的早期步骤都是将弧度角减小到2 *π间隔的范围。但是π是非理性的,因此简单的归约,例如x = remainder(x, 2*M_PI)引入误差as M_PI或机器pi是π的近似值。那么,该怎么办x = remainder(x, 2*π)

早期的库使用扩展的精度或精心设计的程序来提供质量结果,但是仍然在有限的范围内double。当像这样要求较大的值时sin(pow(2,30)),结果将毫无意义,或者0.0可能将错误标志设置为TLOSS总精度或PLOSS部分精度损失。

将较大的值良好地减小到-π到π的范围是一个具有挑战性的问题,它可以与基本触发函数(例如sin())本身的挑战相匹敌。

一个很好的报告是对大量论据的论证减少:对最后一点的贡献(1992年)。它很好地涵盖了该问题:讨论了各种平台(SPARC,PC,HP,30多种)上的需求以及它们的运行方式,并提供了一种解决方案算法,可提供从到的所有 质量结果。double-DBL_MAXDBL_MAX


如果原始参数以度为单位,但可能有很大的价值,请fmod()首先使用以提高精度。好fmod()不会导致错误,因此可提供出色的范围缩小。

// sin(degrees2radians(x))
sin(degrees2radians(fmod(x, 360.0))); // -360.0 < fmod(x,360) < +360.0

各种触发身份,remquo()甚至可以提供更多改进。范例:sind()


6

库函数的实际实现取决于特定的编译器和/或库提供程序。无论是通过硬件还是软件完成,是否为泰勒扩展等,都将有所不同。

我知道那绝对没有帮助。


5

它们通常以软件实现,并且在大多数情况下不会使用相应的硬件(即,组装)调用。但是,正如Jason指出的那样,这些都是特定于实现的。

请注意,这些软件例程不是编译器源代码的一部分,而是可以在相应的库中找到,例如clib或GNU编译器的glibc。参见http://www.gnu.org/software/libc/manual/html_mono/libc.html#Trig-Functions

如果您想要更好的控制,则应仔细评估您的需求。一些典型的方法是对查询表进行插值,汇编调用(通常很慢)或其他近似方案,例如平方根的Newton-Raphson。


5

如果要用软件而不是硬件来实现,则可以在《数字配方》的第5章中找到对此问题的明确答案。我的副本在一个盒子里,所以我无法提供细节,但是简短的版本(如果我没记错的话)是您将其tan(theta/2)作为原始操作并从那里计算其他操作。计算是一系列近似完成,但它的东西,收敛得多快于泰勒级数。

抱歉,如果不把手放在书上,我再也记不起来了。


5

没有什么比打源和了解某人在常用的库中实际完成它的方式更好的了。让我们特别看一下一个C库实现。我选择了uLibC。

这是sin函数:

http://git.uclibc.org/uClibc/tree/libm/s_sin.c

看起来像处理了几种特殊情况,然后进行了一些参数约简,以将输入映射到范围[-pi / 4,pi / 4],(将参数分为两部分,大部分和一条尾巴)打电话之前

http://git.uclibc.org/uClibc/tree/libm/k_sin.c

然后在这两个部分上进行操作。如果没有尾巴,则使用次数为13的多项式生成近似答案。如果存在尾巴,则根据以下原理可以得到少量的校正加法:sin(x+y) = sin(x) + sin'(x')y


4

每当评估这样的功能时,在某种程度上最有可能是:

  • 插值表(用于快速,不准确的应用程序,例如计算机图形)
  • 对收敛至期望值的级数的评估-可能不是泰勒级数,更可能是基于花式正交的东西,例如Clenshaw-Curtis。

如果没有硬件支持,则编译器可能会使用后一种方法,只发出汇编代码(不带调试符号),而不是使用ac库---使您难以在调试器中跟踪实际代码。


4

正如许多人指出的那样,它取决于实现。但是据我了解您的问题,您对数学函数的实际软件实现感兴趣,但是却没有找到答案。如果是这种情况,那么您在这里:

  • http://ftp.gnu.org/gnu/glibc/下载glibc源代码
  • 查看dosincos.c位于解压后的glibc根 \ sysdeps \ ieee754 \ dbl-64文件夹中的文件
  • 同样,您可以找到数学库其余部分的实现,只需查找具有适当名称的文件

您还可以查看带有.tbl扩展名的文件,它们的内容无非就是二进制形式的不同功能的预计算值的大表。这就是实现如此之快的原因:与其计算所有使用的序列的所有系数,不如进行快速查找,这快得多。顺便说一句,他们确实使用Tailor级数来计算正弦和余弦。

我希望这有帮助。


4

我将尝试回答sin()在当前的x86处理器(例如Intel Core 2 Duo)上使用GCC的C编译器编译的C程序中的情况。

在C语言中的标准C库包括普通的数学函数,不包括在语言本身(例如powsincos分别为电源,正弦,余弦)。其标头包含在math.h中

现在,在GNU / Linux系统上,这些库函数由glibc(GNU libc或GNU C库)提供。但是GCC编译器希望您使用编译器标志链接到数学库libm.so),-lm以启用这些数学函数的用法。我不确定为什么它不是标准C库的一部分。这些将是浮点函数或“ soft-float”的软件版本。

另外:将数学函数分开的原因是历史性的,据我所知,它可能仅是在非常老的Unix系统中减小可执行程序的大小,可能是在共享库可用之前。

现在,编译器可以优化标准C库函数sin()(由提供libm.so),以替换为对CPU / FPU内置sin()函数的本机指令的调用,该指令作为FPU指令(FSIN对于x86 / x87)存在于像Core 2系列这样的较新处理器(这可以回溯到i486DX之前的大部分时间)这将取决于传递给gcc编译器的优化标志。如果告诉编译器编写可以在任何i386或更高版本的处理器上执行的代码,则不会进行这种优化。该-mcpu=486标志将通知编译器进行这样的优化是安全的。

现在,如果程序执行了sin()函数的软件版本,它将基于CORDIC(坐标旋转数字计算机)或BKM算法,或可能是现在通常用于计算的表格或幂级数计算来执行这样的先验功能。[Src:http//en.wikipedia.org/wiki/Cordic#Application]

gcc的任何最新版本(约2.9x起)也提供了sin的内置版本,__builtin_sin()该版本将作为优化来替代对C库版本的标准调用。

我敢肯定,这就像泥泞一样清晰,但希望能给您比您期望的更多的信息,并且有很多要点来学习您自己。



3

不要使用泰勒级数。正如上面的几个人所指出的那样,切比雪夫多项式既更快又更准确。这是一个实现(最初来自ZX Spectrum ROM):https : //albertveli.wordpress.com/2015/01/10/zx-sine/


2
这似乎并没有真正回答所问的问题。该OP是要求三角函数如何通过共同的C编译器/库计算(和我敢肯定ZX频谱没有资格),他们怎么不应该被计算。但是,这可能是对一些早期答案的有用评论
Ilmari Karonen

1
啊,你是对的。它应该是评论,而不是答案。我已经有一段时间没有使用SO了,但是忘记了系统是如何工作的。无论如何,我认为Spectrum实现非常重要,因为它的CPU速度非常慢,而速度至关重要。最好的算法肯定还算不错,因此对于C库来说,使用Chebyshev多项式实现trig函数将是一个好主意。
艾伯特·韦利

2

实际上,使用泰勒级数通过代码可以很容易地计算正弦/余弦/正切值。自己写一个大概需要5秒钟。

整个过程可以用下面的公式总结:

罪恶与成本膨胀

这是我为C编写的一些例程:

double _pow(double a, double b) {
    double c = 1;
    for (int i=0; i<b; i++)
        c *= a;
    return c;
}

double _fact(double x) {
    double ret = 1;
    for (int i=1; i<=x; i++) 
        ret *= i;
    return ret;
}

double _sin(double x) {
    double y = x;
    double s = -1;
    for (int i=3; i<=100; i+=2) {
        y+=s*(_pow(x,i)/_fact(i));
        s *= -1;
    }  
    return y;
}
double _cos(double x) {
    double y = 1;
    double s = -1;
    for (int i=2; i<=100; i+=2) {
        y+=s*(_pow(x,i)/_fact(i));
        s *= -1;
    }  
    return y;
}
double _tan(double x) {
     return (_sin(x)/_cos(x));  
}

4
这是一个非常糟糕的实现,因为它没有使用正弦和余弦序列的连续项具有非常简单的商。这意味着可以将乘法和除法的数量从此处的O(n ^ 2)减少到O(n)。通过减半和平方来进一步减少,例如在bc(POSIX多精度计算器)数学库中完成。
Lutz Lehmann 2014年

2
它似乎也没有按照要求回答问题。OP询问的是普通C编译器/库如何计算trig函数,而不是用于自定义重新实现。
Ilmari Karonen

2
我认为这是一个很好的答案,因为它回答了这个问题的精髓(我当然只能猜想)对诸如sin()之类的“黑匣子”函数的好奇心。这是唯一的答案,它使人们有机会通过几秒钟的时间来掩盖正在发生的事情,而不是阅读一些优化的C源代码,从而快速了解正在发生的事情。
Mike M

实际上,库使用了更为优化的版本,因为您意识到一旦有了一个术语,就可以通过乘以一些值来获得下一个术语。请参阅Blindy的答案中的示例。您正在一次又一次地计算能力和阶乘,这要慢得多
phuclv


0

来自Blindy答案的改进代码版本

#define EPSILON .0000000000001
// this is smallest effective threshold, at least on my OS (WSL ubuntu 18)
// possibly because factorial part turns 0 at some point
// and it happens faster then series element turns 0;
// validation was made against sin() from <math.h>
double ft_sin(double x)
{
    int k = 2;
    double r = x;
    double acc = 1;
    double den = 1;
    double num = x;

//  precision drops rapidly when x is not close to 0
//  so move x to 0 as close as possible
    while (x > PI)
        x -= PI;
    while (x < -PI)
        x += PI;
    if (x > PI / 2)
        return (ft_sin(PI - x));
    if (x < -PI / 2)
        return (ft_sin(-PI - x));
//  not using fabs for performance reasons
    while (acc > EPSILON || acc < -EPSILON)
    {
        num *= -x * x;
        den *= k * (k + 1);
        acc = num / den;
        r += acc;
        k += 2;
    }
    return (r);
}

0

这样做的本质在于杰拉德·惠特利(Gerald Wheatley)的《应用数值分析》摘录:

当您的软件程序要求计算机获取在此处输入图片说明或的值时 在此处输入图片说明,您是否想知道,如果它可以计算的最强大的函数是多项式,那么如何获取这些值?它不会在表中查找这些内容并进行内插!而是,计算机从一些多项式中近似除多项式以外的所有函数,这些多项式经过了精心设计以非常准确地给出值。

上面要提到的几点是,某些算法实际上只是从表中进行插值,尽管仅适用于前几次迭代。还要注意它是如何提及计算机使用近似多项式而不指定哪种类型的近似多项式的。正如线程中的其他人指出的那样,在这种情况下,切比雪夫多项式比泰勒多项式更有效。


-1

如果你想要的sin

 __asm__ __volatile__("fsin" : "=t"(vsin) : "0"(xrads));

如果你想要的cos

 __asm__ __volatile__("fcos" : "=t"(vcos) : "0"(xrads));

如果你想要的sqrt

 __asm__ __volatile__("fsqrt" : "=t"(vsqrt) : "0"(value));

那么,当机器指令执行时,为什么要使用不正确的代码呢?


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.