在C語言中,math.h中定義的各種數學函數在電腦上具體是怎麼實現的?

有一天突然想知道C語言中如何計算各種函數的。可是打開math.h頭文件後只看到一大堆函數的聲明,卻沒有具體內容,那這到底是怎麼實現的呢?


  1. math.h 里的函數都是定義在 libm 里,而每個 libm 實現都不同
  2. gcc 的 glibm 中數學函數的實現完全是平台依存的,在 x86 機器上,能調用 FPU 指令的就用 FPU(比如 sqrt() 就實際上調用 FSQRT,log() 調用的是 FYL2X),否則再自己實現
  3. 如果需要軟體實現,方法基本上是泰勒級數。當然對於 sin 這類,可以用專門的優化演算法,比如 CORDIC
  4. CPU 中的電路基本上也就是泰勒級數。

下面介紹一下 CORDIC 演算法。CORDIC 最初是用來算 arctan(y/x) 的。由於這個值本質上是點 (x,y) 的幅角,因此我們可以用一個座標旋轉把它弄到橫軸上:

egin{pmatrix}x

可是這裡的 正是我們要求的啊……不過不要緊,我們可以用二分搜索:第一次順時針旋轉 45 度,然後看新的座標給弄到橫軸的上面還是下面,如果在上面就順時針旋轉 22.5 度,在下面就逆時針。換言之,每次旋轉角度減半,這樣就能把點逐漸靠近橫軸了。而每次旋轉所用的三角函數值完全可以存儲在表格里,用查表的方法得到。

不過即使這樣我們仍然有大量的乘法運算(每次旋轉做了 4 次乘法),對矩陣稍作修改就可以得到:

egin{pmatrix}x

換言之每次旋轉的時候,為了保持點到原點距離不變我們乘以了一個係數 K_i,不過我們這裡只關心幅角不關心模,所以可以把它去掉(後果是變換時點會離原點越來越遠):

egin{pmatrix}x

這樣浮點乘法減少了兩次。

然後我們還是不甘心,因為浮點乘法實在是太慢了,能不能把它徹底消除呢?實際上是可以的。在剛才的布驟里我們每次旋轉角減半,實際上並不需要這麼做——我們可以選擇

sigma_i=arctan(2^{-i}) ;;; (i ge 0)

這樣一組特殊的角,就能讓每次旋轉中的乘法完全退化為加減法,這就可以大大簡化電路設計。

當然 CORDIC 除了可以算 arctan,還有許多函數都可以計算,包括 sin、cos、tan 和模。


JIT腳本引擎:完成20個浮點函數

大學的時候用x86寫了Sin,Cos,Tan,Cot,Sec,Csc,ASin,ACos,ATan,ACot,ASec,ACsc,Sqrt,Exp,Ln,Abs,Round,Trunc,Ceil,Floor,可供參考


授人以魚不如授人以漁,告訴你查代碼的方法可以解決所有同類問題。

math.h 應該是聲明,實現應該在 libm.so 中,然後你可以查找一下 libm.so 包的包名,發現它位於 libc6-dev 這個包,然後查找對應的源代碼,如果是 Debian/Ubuntu 可以用 apt-get source libc6-dev 來下載源代碼到當前目錄,之後便可以查看源代碼了。

如果是其他的發行版,歡迎各位補充查代碼的方法。


代碼我沒看過,不過計算機算三角函數是不大可能使用泰勒級數的,泰勒級數收斂太慢了,貌似用切比雪夫多項式展開的多一些


fdlibm


http://www.netlib.org/fdlibm/

一種 libm 的軟浮點的實現。


需要編譯器提供浮點加減乘除。


.h文件是個頭文件,這意味著對其的inculde操作會且僅僅會導致編譯器把include聲明替換成頭文件全文.而實際上C/C++的庫管理十分原始,和java等語言不同.C/C++中多個.c/.cpp源文件能夠合併成一個可執行文件/庫完全是鏈接器的作用.在編譯器生成中間文件之後,給鏈接器指定哪些文件需要鏈接成一個最終文件之後,鏈接器就會鏈接或者報告鏈接失敗.而鏈接器能夠鏈接多個文件原因是因為所鏈接使用的二進制代碼中間文件中有相同的函數聲明,並且有且僅有一個有函數之實現.而二進制中間文件的這一點又是靠著其上游的源代碼文件而保證的,具體地說如果多個文件具有函數A的相同聲明,而其中一個文件還具有函數A的實現,那麼這幾個文件的函數A就會被鏈接器認為是一個函數從而獲得鏈接.而此時頭文件就起到了提取相同函數聲明的作用.

此外,頭文件的這樣用法還使得庫的聲明和實現相分離,有利於模塊化編程,也有利於庫的源代碼保護.

實際使用的時候,引用其中的頭文件就需要和頭文件所實現的庫鏈接,而這需要程序員自己設定好.Linux下如@pansz 大牛所說爲了運行程序必須使用ld鏈接上glibc,Windows下math.h的相關函數實現在msvcrt.dll中,因此經過cl.exe生成的程序就需要依賴此dll.


我想題主想知道的應該是,這些函數具體是怎麼寫的,而不是樓上各位告訴的樓主如何找到這些函數的包位置,想必題主能問出這樣的問題,即使找到了源代碼,也不容易看懂(主要指那些比較複雜的)。

我來簡單的給你解釋一下一些數學函數在計算機里都是怎麼實現的吧

首先需要明確的是,CPU只支持加、減、乘、除以及一些布爾邏輯運算,嚴格的說,CPU僅支持布爾運算,其它的一切運算都是基於布爾運算設計出來的複雜電路。當然我這句話還是相當不全面的,比如CPU還支持一些為其它工作而專門優化的運算如3D變幻之類,但這些我們暫時不考慮。

問題來了,既然CPU只支持+-*/,那如何計算出冪運算呢?

比如3的5次方,CPU如何計算?很簡單,我們只要把這個運算轉換成只含有加減乘除的運算就可以了,3的5次方明顯等於3*3*3*3*3,這樣CPU就可以計算了。

那問題又來了,Sin(30度)計算機又是如何計算的呢?其實還是那句話,把它轉換成只含有加減乘除的運算,至於如何轉換,如果我沒記錯的話(我覺得我很可能記錯了…我果然還是記錯了……謝@楊芳斐 ) ,應該是泰勒級數,總之最後可以把它寫成一個很長的式子,裡面只包含加減乘除這些簡單運算,式子越長,精度越高(對了,在這裡不要幻想計算機計算這個Sin(30度)可時可以在計算機內部畫一個很大的三角形然後去測量其各邊長然後計算……),至於具體是怎麼變幻的,你可以去看《高等數學》,就是大部分理科生本科時修的那本,當然我想其它的高等數學書里肯定也會有講,諾,就是這本。當然了,這本書很難看下去的,為求方便,你可以去問一個成績比較好的理科大一學生,在大一下學期時應該已經學到了這裡的……

哦對了,至於僅支持邏輯運算的CPU如何能計算加減乘除,你可以去看一看《數字邏輯》這本書,裡面有講使用最基本的邏輯門構造加法器這類功能的電路

大概是這本吧

內容如有疏漏之處,還請大牛們指出~


補充一下@謝然 的內容……

#include &
#include &
using namespace std;
int main( )
{
int a,b;
scanf("%d%d",a,b);
int c;
c=a^b;
int d;
int e;
d=ab;
#define Plus1
d=d&<&<1; e=c^d; d=cd; c=e; Plus1;Plus1;Plus1;Plus1; Plus1;Plus1;Plus1;Plus1; Plus1;Plus1;Plus1;Plus1; Plus1;Plus1;Plus1;Plus1; Plus1;Plus1;Plus1;Plus1; Plus1;Plus1;Plus1;Plus1; Plus1;Plus1;Plus1;Plus1; Plus1;Plus1;Plus1;Plus1; //32 printf("%d",c); }

大致上一可以把32位加法看成這樣,當然,在cpu里,這是以電路的形式存在的。

另外,CPU裡面三角函數可以用泰勒級數求。

sinx=x-x^3/3!+x^5/5!-x^7/!+....

另外,現在計算三角函數應該都有預處理優化的,是C的庫在做還是處理器做的我不是很清楚&>&<,應該有一部分處理器是支持三角函數的計算的。。


總結一下:

C的頭文件只是一句函數聲明,實現的代碼在標準庫對應的.lib或者.so或者.dll,也就是靜態或動態庫文件里;

如果你反彙編庫文件里對應的代碼段,可能發現如 vczh 給的彙編代碼一樣,直接調用的FPU的代碼,也就是說浮點處理單元能直接計算cos等等;

那麼FPU是怎麼進行計算的呢,或者如果沒有FPU怎麼進行計算呢?這就要如 謝然 所說的那樣,看《高等數學》啦,Taylor定理是最基本的思想,即通過只有加減乘除的冪級數來近似計算,更具體的要看《數值計算》相關的教程了,裡面會介紹更高效的演算法,但一般仍是通過冪級數來計算的,因為CPU都實現了加減乘除;

還有沒有別的方法呢?

在嵌入式應用里,它的CPU一般性能比較差,有的都沒有FPU,只能進行定點算術,不過這些應用對數據範圍和精度的要求一般是可以事先估計的,直接把算好的數放到數組裡,用時查表就行,比如每0.1°的cos,考慮到周期性,只要0~90°共存900個數,對存儲要求不高,又能大大提高效率。


推薦閱讀:

數據結構中所講的動態分配的數組如何在 C 語言中實現?
c語言是否可以通過調用void函數來完成對數組的賦值?
C編譯器用什麼語言寫的?
這個指針C語言如何聲明?
不用QT,你能讓UI同時運行在Mac, IOS, Windows, Android, Linux上嗎?

TAG:編程語言 | C編程語言 | C |