我一直在研究。net反汇编和GCC源代码,但似乎在任何地方都找不到sin()和其他数学函数的实际实现…他们似乎总是在引用别的东西。
有人能帮我找到他们吗?我觉得不太可能运行C语言的所有硬件都支持三角函数,所以一定有软件算法,对吧?
我知道有几种方法可以计算函数,并且为了好玩,我写了自己的例程来使用泰勒级数计算函数。我很好奇生产语言是如何做到这一点的,因为我的所有实现总是慢几个数量级,尽管我认为我的算法非常聪明(显然不是)。
我一直在研究。net反汇编和GCC源代码,但似乎在任何地方都找不到sin()和其他数学函数的实际实现…他们似乎总是在引用别的东西。
有人能帮我找到他们吗?我觉得不太可能运行C语言的所有硬件都支持三角函数,所以一定有软件算法,对吧?
我知道有几种方法可以计算函数,并且为了好玩,我写了自己的例程来使用泰勒级数计算函数。我很好奇生产语言是如何做到这一点的,因为我的所有实现总是慢几个数量级,尽管我认为我的算法非常聪明(显然不是)。
当前回答
我将尝试在一个C程序中回答sin()的情况,该程序用GCC的C编译器在当前的x86处理器(假设是Intel Core 2 Duo)上编译。
在C语言中,标准C库包含了一些常见的数学函数,而这些函数并不包含在语言本身中(例如pow, sin和cos分别表示幂,sin和cos)。它们的头文件包含在math.h中。
现在在GNU/Linux系统上,这些库函数是由glibc (GNU libc或GNU C库)提供的。但是GCC编译器希望您使用-lm编译器标志链接到数学库(libm.so),以启用这些数学函数的使用。我不确定为什么它不是标准C库的一部分。这些将是浮点函数的软件版本,或“软浮动”。
题外话:将数学函数分开的原因由来已久,据我所知,可能是在共享库可用之前,它仅仅是为了在非常古老的Unix系统中减少可执行程序的大小。
Now the compiler may optimize the standard C library function sin() (provided by libm.so) to be replaced with an call to a native instruction to your CPU/FPU's built-in sin() function, which exists as an FPU instruction (FSIN for x86/x87) on newer processors like the Core 2 series (this is correct pretty much as far back as the i486DX). This would depend on optimization flags passed to the gcc compiler. If the compiler was told to write code that would execute on any i386 or newer processor, it would not make such an optimization. The -mcpu=486 flag would inform the compiler that it was safe to make such an optimization.
现在,如果程序执行sin()函数的软件版本,它将基于CORDIC(坐标旋转数字计算机)或BKM算法,或者更可能是现在通常用于计算此类超越函数的表格或幂级数计算。(Src: http://en.wikipedia.org/wiki/Cordic应用程序)
任何最新的gcc版本(大约2.9倍以来)也提供了内置的sin版本__builtin_sin(),作为优化,它将用于取代对C库版本的标准调用。
我相信这是非常清楚的,但希望给你更多的信息比你期望的,和许多出发点,以了解更多自己。
其他回答
对于罪恶,用泰勒展开可以得到
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;
}
在GNU libm中,sin的实现依赖于系统。因此,您可以在sysdeps的适当子目录中找到每个平台的实现。
一个目录包含一个由IBM贡献的C语言实现。自2011年10月以来,这是在典型的x86-64 Linux系统上调用sin()时实际运行的代码。它显然比汇编指令中的f_f快。源代码:sysdeps/ieee754/dbl-64/s_sin.c,查找__sin (double x)。
这段代码非常复杂。没有一种软件算法在整个x值范围内尽可能快且准确,因此库实现了几种不同的算法,它的第一项工作是查看x并决定使用哪种算法。
When x is very very close to 0, sin(x) == x is the right answer. A bit further out, sin(x) uses the familiar Taylor series. However, this is only accurate near 0, so... When the angle is more than about 7°, a different algorithm is used, computing Taylor-series approximations for both sin(x) and cos(x), then using values from a precomputed table to refine the approximation. When |x| > 2, none of the above algorithms would work, so the code starts by computing some value closer to 0 that can be fed to sin or cos instead. There's yet another branch to deal with x being a NaN or infinity.
这段代码使用了一些我以前从未见过的数值技巧,尽管据我所知,它们可能在浮点专家中很有名。有时几行代码需要几段文字来解释。例如,这两条线
double t = (x * hpinv + toint);
double xn = t - toint;
(有时)用于将x减小到接近0的值,该值与x相差π/2的倍数,特别是xn × π/2。这种没有划分或分支的方式相当聪明。但是没有任何评论!
旧的32位版本的GCC/glibc使用fsin指令,这对于某些输入是非常不准确的。有一篇精彩的博客文章用两行代码说明了这一点。
fdlibm在纯C中实现sin要比glibc简单得多,而且注释很好。源代码:fdlibm/s_sin.c和fdlibm/k_sin.c
正如许多人指出的那样,它依赖于实现。但就我对你的问题的理解而言,你对数学函数的真正软件实现感兴趣,但只是没有找到一个。如果是这样的话,那么你是这样的:
从http://ftp.gnu.org/gnu/glibc/下载glibc源代码 查看位于解包的glibc根\sysdeps\ieee754\dbl-64文件夹中的文件dosincosc 类似地,您可以找到其余数学库的实现,只需查找具有适当名称的文件
您也可以看看扩展名为.tbl的文件,它们的内容只不过是以二进制形式的不同函数的预计算值的巨大表格。这就是为什么实现如此之快:而不是计算他们使用的任何级数的所有系数,他们只是做一个快速查找,这要快得多。顺便说一下,他们确实用裁缝级数来计算正弦和余弦。
我希望这能有所帮助。
如果您想查看这些函数在C语言中的实际GNU实现,请查看glibc的最新主干。参见GNU C库。
库函数的实际实现取决于特定的编译器和/或库提供程序。不管它是用硬件还是软件,不管它是不是泰勒展开,等等,都会有所不同。
我意识到这完全没有帮助。