在編程中我們總要進(jìn)行一些數(shù)學(xué)運(yùn)算以及數(shù)字處理,尤其是浮點(diǎn)數(shù)的運(yùn)算和處理,這篇文章主要介紹C語言下的數(shù)學(xué)庫。而其他語言中的數(shù)學(xué)庫函數(shù)的定義以及最終實(shí)現(xiàn)也是通過對(duì)C數(shù)學(xué)庫的調(diào)用來完成的,其內(nèi)容大同小異,因此就不在這里介紹了。
C語言標(biāo)準(zhǔn)庫中的math.h定義了非常多的數(shù)學(xué)運(yùn)算和數(shù)字處理函數(shù)。這些函數(shù)大部分都是在C89標(biāo)準(zhǔn)中定義的,而有些C99標(biāo)準(zhǔn)下的函數(shù)我會(huì)特殊的說明,同時(shí)因?yàn)椴煌木幾g器下的C標(biāo)準(zhǔn)庫中有些函數(shù)的定義有差別,我也會(huì)分別的說明。
- 如果大家想了解C89以及C99請(qǐng)參考:http://www.cnblogs.com/xfiver/p/4477954.html
- 如果大家想了解GNUC和ANSIC請(qǐng)參考:http://www.cnblogs.com/balaamwe/archive/2012/01/07/2316076.html
- 如果大家想了解POSIX方面的東西請(qǐng)參考:http://blog.csdn.net/smstong/article/details/51614857 http://baike.baidu.com/item/POSIX?fr=aladdin
數(shù)字的范圍
整型
整型用來存儲(chǔ)整數(shù)數(shù)值,它按存儲(chǔ)的字節(jié)長(zhǎng)短分為:字符型,短整型,整型,長(zhǎng)整型。 所有類型的存儲(chǔ)長(zhǎng)度都是定長(zhǎng)的。既然類型是定長(zhǎng)的就有一個(gè)最大最小可表示的范圍,對(duì)于整型來說各種類型的最大最小的定義可以在limits.h中找到。下面表格列出了不同類型的存儲(chǔ)長(zhǎng)度和最大最小值:
類型 | 字節(jié)數(shù) | 最小值 | 宏定義 | 最大值 | 宏定義 | 備注 |
---|---|---|---|---|---|---|
char | 1 | -2^7 | SCHAR_MIN | 2^7-1 | SCHAR_MAX | |
unsigned char | 1 | 0 | UCHAR_MIN | 2^8-1 | UCHAR_MAX | |
short | 2 | -2^15 | SHRT_MIN | 2^15-1 | SHRT_MAX | |
unsigned short | 2 | 0 | USHRT_MIN | 2^16-1 | USHRT_MAX | |
int | 4? | -2^31 | INT_MIN | 2^31-1 | INT_MAX | |
unsinged int | 4? | 0 | UINT_MIN | 2^32-1 | UINT_MAX | |
long | 4? | -2^31 | LONG_MIN | 2^31-1 | LONG_MAX | |
unsigned long | 4? | 0 | ULONG_MIN | 2^32-1 | ULONG_MAX | |
long long | 8 | -2^63 | LLONG_MIN | 2^63-1 | LLONG_MAX | C99 |
unsigned long long | 8 | 0 | ULLONG_MIN | 2^64-1 | ULLONG_MAX | C99 |
對(duì)于int和long類型來說,二者的長(zhǎng)度是依賴于操作系統(tǒng)的字長(zhǎng)或者機(jī)器的字長(zhǎng)。因此如果我們要編寫跨平臺(tái)或跨系統(tǒng)的程序就應(yīng)該盡量減少對(duì)這兩個(gè)類型變量的直接定義。 下面表格列出了int和long兩種類型在不同操作系統(tǒng)字長(zhǎng)下的長(zhǎng)度。
類型 | 16位系統(tǒng)/字節(jié) | 32位系統(tǒng)/字節(jié) | 64位系統(tǒng)/字節(jié) |
---|---|---|---|
int | 2 | 4 | 4 |
long | 4 | 4 | 8 |
在很多系統(tǒng)中都對(duì)32位的整型以及64位的整型進(jìn)行特殊的定義,比如Windows中的DWORD,UINT32,INT64等等。
浮點(diǎn)型
浮點(diǎn)型用來存儲(chǔ)浮點(diǎn)數(shù)值。它按精度分為:單精度浮點(diǎn)型,雙精度浮點(diǎn)型,擴(kuò)展雙精度浮點(diǎn)型。 浮點(diǎn)數(shù)是連續(xù)并且無限的,但是計(jì)算機(jī)并不能表達(dá)出所有連續(xù)的值。因此對(duì)浮點(diǎn)數(shù)定義了最小規(guī)格化值和最大規(guī)格化值,這些定義可以在float.h中找到。下面表格列出了不同類型的存儲(chǔ)長(zhǎng)度和最值:
類型 | 字節(jié)數(shù) | 最小規(guī)格化值 | 宏定義 | 最大規(guī)格化值 | 宏定義 | 備注 |
---|---|---|---|---|---|---|
float | 4 | 1.175494351e-38 | FLT_MIN | 3.402823466e+38 | FLT_MAX | |
double | 8 | 2.2250738585072014e-308 | DBL_MIN | 1.7976931348623158e+308 | DBL_MAX | |
long double | 8? | 2.2250738585072014e-308 | LDBL_MIN | 1.7976931348623158e+308 | LDBL_MAX | C99 |
- 這里的FLT_MIN,DBL_MIN,LDBL_MIN并不是指最小可表示的浮點(diǎn)數(shù),而是最小規(guī)格化浮點(diǎn)值,具體我會(huì)在下面詳細(xì)介紹。
- 對(duì) long double 的定義,取決于編譯器和機(jī)器字長(zhǎng),所以對(duì)于不同平臺(tái)可能有不同的實(shí)現(xiàn),有的是8字節(jié),有的是10字節(jié),有的是12字節(jié)或16字節(jié)。
- 為了和數(shù)學(xué)中的無窮∞對(duì)應(yīng),標(biāo)準(zhǔn)庫中定義了一個(gè)宏:INFINITY來表示無窮大。比如1.0/0.0等于INFINITY,-1.0/0.0等于-INFINITY。無窮大可以進(jìn)行加減乘除操作,比如1.0/INFINITY == 0。
- 為了和數(shù)學(xué)中的非法數(shù)字對(duì)應(yīng),標(biāo)準(zhǔn)庫中定義了一個(gè)宏:NAN來表示非法數(shù)字。比如負(fù)數(shù)開方、負(fù)數(shù)求對(duì)數(shù)、0.0/0.0、0.0* INFINITY、INFINITY/INFINITY、INFINITY-INFINITY這些操作都會(huì)得到NAN。注意:如果是整數(shù)0/0會(huì)產(chǎn)生操作異常
浮點(diǎn)數(shù)的存儲(chǔ)結(jié)構(gòu)
浮點(diǎn)數(shù)不像整數(shù)那樣離散值,而是連續(xù)的值。但是用計(jì)算機(jī)來描述一個(gè)浮點(diǎn)數(shù)時(shí)就不可能完全實(shí)現(xiàn)其精度和連續(xù)性,現(xiàn)在的浮點(diǎn)型的存儲(chǔ)和描述普遍都是遵循IEEE754標(biāo)準(zhǔn)。如果您想詳細(xì)的了解關(guān)于浮點(diǎn)數(shù)的存儲(chǔ)格式那么您可以花費(fèi)一點(diǎn)時(shí)間來閱讀:https://wenku.baidu.com/view/d02978d8d15abe23482f4dac.html 這篇文章。
簡(jiǎn)單來說浮點(diǎn)數(shù)的存儲(chǔ)由:S(sign)符號(hào)位、E(exponent)指數(shù)位、M(mantissa 或significand)尾數(shù)位三個(gè)部分組成。我們以一個(gè)32位的float類型舉例來說,一個(gè)浮點(diǎn)數(shù)N的從高位到低位的存儲(chǔ)結(jié)構(gòu)如下:
也就是一個(gè)32位的浮點(diǎn)數(shù)由1個(gè)符號(hào)位,8個(gè)指數(shù)位,23個(gè)尾數(shù)位組成。 而為了表示不同類型的浮點(diǎn)數(shù),根據(jù)存儲(chǔ)格式對(duì)浮點(diǎn)數(shù)進(jìn)行了如下分類:
- 如果一個(gè)浮點(diǎn)數(shù)中指數(shù)位部分全為1,而尾數(shù)位部分全為0則這個(gè)浮點(diǎn)數(shù)表示為無窮大** INFINITY **,如果符號(hào)位為0表示正無窮大,否則就是負(fù)無窮大。
- 如果一個(gè)浮點(diǎn)數(shù)中指數(shù)位部分全為1,而尾數(shù)位部分不全為0則這個(gè)浮點(diǎn)數(shù)表示為非法數(shù)字NAN。因此可以看出非法數(shù)字并非一個(gè)數(shù)字而是一類數(shù)字。在下面介紹nan函數(shù)時(shí)我會(huì)更加深入的介紹NAN
- 如果一個(gè)浮點(diǎn)數(shù)中除符號(hào)位外全部都是0,那么這個(gè)浮點(diǎn)數(shù)就是0
- 如果一個(gè)浮點(diǎn)數(shù)中指數(shù)位部分全為0,而尾數(shù)位部分不全為0則這個(gè)浮點(diǎn)數(shù)稱為非規(guī)格化浮點(diǎn)數(shù),英文稱為:subnormal number 或 denormal number 或 denormalized number。非規(guī)格化浮點(diǎn)數(shù)常用來表示一個(gè)非常接近于0的浮點(diǎn)數(shù)。
- 如果一個(gè)浮點(diǎn)數(shù)中的指數(shù)位部分即非全1又非全0。那么這個(gè)浮點(diǎn)數(shù)稱之為規(guī)格化浮點(diǎn)數(shù),英文稱之為:normal number。我們上面定義的FLT_MIN, DBL_MIN 指的就是最小的規(guī)格化浮點(diǎn)數(shù)。
- 我們把規(guī)格化浮點(diǎn)數(shù)和非規(guī)格化浮點(diǎn)數(shù)合稱為可表示的浮點(diǎn)數(shù),英文稱之為:machine representable number
一個(gè)規(guī)格化浮點(diǎn)數(shù)N的值可以用如下公式算出:
從上面的公式中可以看出對(duì)于一個(gè)32位浮點(diǎn)數(shù)來說指數(shù)位占8位,最小值是1(排除全0為非常規(guī)浮點(diǎn)),而最大值是254(排除全1為無窮或者非法浮點(diǎn)),再減去127后得出指數(shù)部分的最小值為-126,最大值為127。同時(shí)我們發(fā)現(xiàn)除了23位尾數(shù)外,還有一個(gè)隱藏的1作為尾數(shù)的頭部。因此我們就很容易得出:
FLT_MIN = 1.0 * 2^-126 = 1.175494351e-38
FLT_MAX = (1.11111111111111111111111)b * 2^127 = 3.402823466e+38
一個(gè)非規(guī)格化浮點(diǎn)數(shù)N的值的可以用如下公式算出:
從上面的公式中可以看出對(duì)于一個(gè)32位的浮點(diǎn)數(shù)來說,我們發(fā)現(xiàn)雖然非規(guī)格化浮點(diǎn)的指數(shù)位部分全0,但是這里并不是0-127,而是1-127,同時(shí)發(fā)現(xiàn)尾數(shù)位部分并沒有使用隱藏的1作為尾數(shù)的頭部,而是將頭部的1移到了指數(shù)部分,這樣做的目的是為了保持浮點(diǎn)數(shù)字的連續(xù)性。我們可以看出當(dāng)一個(gè)浮點(diǎn)數(shù)小于FLT_MIN時(shí),他就變?yōu)榱艘粋€(gè)非規(guī)格化浮點(diǎn)。我們知道FLT_MIN的值是1.0 * 2^-126,而一個(gè)比FLT_MIN小的值就應(yīng)該是:(0.11111111111111111111111)b * 2^-126,而一個(gè)比0大的值就是:(0.00000000000000000000001)b * 2^-126。如果非規(guī)格化浮點(diǎn)數(shù)以-127作為指數(shù),而繼續(xù)使用1作為尾數(shù)的頭部時(shí),那么這種數(shù)字連續(xù)性將會(huì)被打破。這也是為什么要定義規(guī)格化浮點(diǎn)數(shù)和非規(guī)格化浮點(diǎn)數(shù)的意義所在。可以看出浮點(diǎn)數(shù)的這種存儲(chǔ)設(shè)計(jì)的精妙之處!!。
從上面兩種類型的浮點(diǎn)數(shù)中可以總結(jié)出浮點(diǎn)數(shù)的計(jì)算公式可以表示為:
** N = 符號(hào) * 尾數(shù) * 2^指數(shù) **
數(shù)學(xué)函數(shù)
??數(shù)字判斷函數(shù)或宏
//如果x是正無窮大返回1,負(fù)無窮大返回-1,否則返回0
int isinf(x)
//如果x是無窮大返回0
int isfinite(x)
//如果x是一個(gè)規(guī)格化浮點(diǎn)數(shù)則返回非0
int isnormal(x)
//如果x是一個(gè)非法的數(shù)字返回非0
int isnan(x)
//如果x是負(fù)數(shù)返回非0
int signbit(x)
/**
*返回浮點(diǎn)數(shù)的分類:
FP_INFINITE: x是無窮大或者無窮小
FP_NAN:x是一個(gè)非法數(shù)字
FP_NORMAL:x是一個(gè)規(guī)格化浮點(diǎn)數(shù)
FP_SUBNORMAL:x是一個(gè)非規(guī)格化浮點(diǎn)數(shù)
FP_ZERO:x是0
*/
int fpclassify(x)
??三角函數(shù)
1. 反余弦函數(shù): y = arccos(x)
extern float acosf(float x);
extern double acos(double x);
extern long double acosl(long double x);
2. 反正弦函數(shù):y = arcsin(x)
extern float asinf(float x);
extern double asin(double x);
extern long double asinl(long double x);
3. 反正切函數(shù):* y = arctan(x)*
extern float atanf(float x);
extern double atan(double x);
extern long double atanl(long double x);
4. 2個(gè)參數(shù)的反正切函數(shù):z = arctan(y/x)
extern float atan2f(float y, float x);
extern double atan2(double y, double x);
extern long double atan2l(long double y, long double x);
因?yàn)?em>arctan的定義域是在(-∞, +∞),而值域是在(-??/2, ??/2)之間。因此 :
atan2f(-1.0, 0.0) == -??/2; atan2f(1.0, 0.0) == ??/2;
這個(gè)函數(shù)提供的另外一個(gè)意義在于tan函數(shù)的值其實(shí)就是對(duì)邊除以鄰邊的結(jié)果,因此當(dāng)知道對(duì)邊和鄰邊時(shí)就可以直接用這個(gè)逆三角函數(shù)來求得對(duì)應(yīng)的弧度值。假如特殊情況下對(duì)邊和鄰邊的值都是0.0,那么如果你調(diào)用atan(0.0/0.0)得到的值將是NAN而不是0。因?yàn)?.0/0.0的值是NAN,而對(duì)NAN調(diào)用atan函數(shù)返回的也是NAN,但是對(duì)atan2(0.0,0.0)調(diào)用返回的結(jié)果就是正確值0。
5. 余弦函數(shù): y = cos(x)
extern float cosf(float x);
extern double cos(double x);
extern long double cosl(long double x);
6. 正弦函數(shù):y = sin(x)
extern float sinf(float x);
extern double sin(double x);
extern long double sinl(long double x);
7. 正切函數(shù):y = tan(x)
extern float tanf(float x);
extern double tan(double x);
extern long double tanl(long double x);
??雙曲函數(shù)
1. 反雙曲余弦函數(shù):y = arccosh(x)
extern float acoshf(float x);
extern double acosh(double x);
extern long double acoshl(long double x);
2. 反雙曲正弦函數(shù):y = arcsinh(x)
extern float asinhf(float x);
extern double asinh(double x);
extern long double asinhl(long double x);
3. 反雙曲正切函數(shù):y = arctanh(x)
extern float atanhf(float x);
extern double atanh(double x);
extern long double atanhl(long double x);
4. 雙曲余弦函數(shù):y = cosh(x)
extern float coshf(float x);
extern double cosh(double x);
extern long double coshl(long double x);
5. 雙曲正弦函數(shù):y = sinh(x)
extern float sinhf(float x);
extern double sinh(double x);
extern long double sinhl(long double x);
6. 雙曲正切函數(shù): y = tanh(x)
extern float tanhf(float x);
extern double tanh(double x);
extern long double tanhl(long double x);
??指數(shù)函數(shù)
1. 自然常數(shù)e為基數(shù)的指數(shù)函數(shù):y = e^x
extern float expf(float x);
extern double exp(double x);
extern long double expl(long double x);
2. 自然常數(shù)e為基數(shù)的指數(shù)減1:y = e^x - 1
extern float expm1f(float x);
extern double expm1(double x);
extern long double expm1l(long double x);
我們既然定義了exp函數(shù),那么按理說要實(shí)現(xiàn)e^x-1
就很簡(jiǎn)單,為什么要單獨(dú)定義這個(gè)函數(shù)呢?先看下面兩個(gè)輸出:
double o1 = exp(1.0e-13) - 1.0;
double o2 = expm1(1.0e-13);
printf("o1 = %e, o2 = %e", o1, o2);
//output: o1 = 9.992007e-14, o2 = 1.000000e-13
從上面的例子中發(fā)現(xiàn)當(dāng)用exp函數(shù)時(shí)出現(xiàn)了有效數(shù)字損失而expm1則沒有。出現(xiàn)這種問題的原因就是浮點(diǎn)加減運(yùn)算本身機(jī)制的問題,在浮點(diǎn)運(yùn)算中下面兩種類型的運(yùn)算都有可能出現(xiàn)損失有效數(shù)字的情況:
- 兩個(gè)相近的數(shù)相減
- 兩個(gè)數(shù)量級(jí)相差很大的數(shù)字相加減
我們可以做一個(gè)實(shí)驗(yàn),分別在調(diào)試器中查看a1,a2和b1,b2的結(jié)果:
double a1 = 5.37-5.36;
double a2 = (5.37*100 - 5.36*100)/100;
double b1 = 100.0-0.01;
double b2 = (100.0/0.01 - 0.01/0.01)*0.01;
//我們發(fā)現(xiàn)a1的值是0.0099999999999997868,而a2的值就是0.01
//我們發(fā)現(xiàn)b1的值是99.989999999999994而b2的值是99.990000000000009
從上面的例子中可以看出當(dāng)浮點(diǎn)數(shù)相近或者差異很大時(shí)加減運(yùn)算出現(xiàn)了有效數(shù)字損失的情況,同時(shí)上面的例子也給出了一個(gè)減少這種損失的簡(jiǎn)易解決方案。再回到上面exp函數(shù)的場(chǎng)景中,因?yàn)閑xp(1.0e-13)的值和1.0是非常接近,因此當(dāng)對(duì)這兩個(gè)數(shù)做減法時(shí)就會(huì)出現(xiàn)有效數(shù)字損失的情況。我們?cè)賮砜疾?strong>expm1函數(shù),這個(gè)函數(shù)主要用于當(dāng)x接近于0時(shí)的場(chǎng)景。我們知道函數(shù) y = e^x - 1 當(dāng)x趨近于0時(shí)的極限是0,因此我們可以用泰勒級(jí)數(shù)來展開他:
可以看出這個(gè)級(jí)數(shù)收斂的很快,因此可以肯定的是expm1函數(shù)的內(nèi)部實(shí)現(xiàn)就是通過上面的泰勒級(jí)數(shù)的方法來實(shí)現(xiàn)求值的。下面這段函數(shù)使用手冊(cè)的文檔也給出了用expm1代替exp函數(shù)的例子和說明:
Note that computations numerically equivalent to exp(x) - 1.0 are often
hidden in more complicated expressions; some amount of algebraic manipu-
lation may be necessary to take advantage of the expm1() function. Con-
sider the following example, abstracted from a developer's actual produc-
tion code in a bug report:
double z = exp(-x/y)*(x*x/y/y + 2*x/y + 2) - 2
When x is small relative to y, this expression is approximately equal to:
double z = 2*(exp(-x/y) - 1)
and all precision of the result is lost in the computation due to cata-
strophic cancellation. The developer was aware that they were losing
precision, but didn't know what to do about it. To remedy the situation,
we do a little algebra and re-write the expression to take advantage of
the expm1() function:
exp(-x/y)*(x*x/y/y + 2*x/y + 2) - 2
= (2*exp(-x/y) - 2) + exp(-x/y)*((x*x)/(y*y) + 2*x/y)
This transformation allows the result to be computed to a high degree of
accuracy as follows:
const double r = x/y;
const double emrm1 = expm1(-r);
double z = 2.0*emrm1 + (1.0 + emrm1)*(2.0 + r)*r;
It is not always easy to spot such opportunities for improvement; if an
expression involving exp() seems to be suffering from an undue loss of
accuracy, try a few simple algebraic operations to see if you can iden-
tify a factor with the form exp(x) - 1.0, and substitute expm1(x) in its
place.
3. 2為基數(shù)的指數(shù)函數(shù):y = 2^x
extern float exp2f(float x);
extern double exp2(double x);
extern long double exp2l(long double x);
4. 浮點(diǎn)數(shù)構(gòu)造函數(shù):* y = x * 2^n*
extern float ldexpf(float x, int n);
extern double ldexp(double x, int n);
extern long double ldexpl(long double x, int n);
既然上面已經(jīng)存在了一個(gè)exp函數(shù),如果我們要實(shí)現(xiàn)相同的功能按理來只要:x*exp(n)
就好了,為什么還要單獨(dú)提供一個(gè)新的ldexp函數(shù)呢?原因就是ldexp函數(shù)其實(shí)是一個(gè)用來構(gòu)造浮點(diǎn)數(shù)的函數(shù),我們知道浮點(diǎn)數(shù)的格式定義在IEEE754中,具體的結(jié)構(gòu)為:符號(hào)*尾數(shù)*2^指數(shù),剛好和ldexp所實(shí)現(xiàn)的功能是一致的,這里的x用來指定符號(hào)*尾數(shù),而n則指定為指數(shù)。因此我們就可以借助這個(gè)函數(shù)來實(shí)現(xiàn)浮點(diǎn)數(shù)的構(gòu)造。
5. 以FLT_RADIX基數(shù)的浮點(diǎn)數(shù)構(gòu)造函數(shù):y = x* FLT_RADIX^n
extern float scalbnf(float x, int n);
extern double scalbn(double x, int n);
extern long double scalbnl(long double x, int n);
extern float scalblnf(float x, long int n);
extern double scalbln(double x, long int n);
extern long double scalblnl(long double x, long int n);
這里的FLT_RADIX是浮點(diǎn)數(shù)存儲(chǔ)里面的基數(shù)(在float.h中有定義這個(gè)宏),一般情況下是2,這時(shí)候這個(gè)函數(shù)就和ldexp函數(shù)是一致的。但是有些系統(tǒng)的浮點(diǎn)數(shù)存儲(chǔ)并不是以2為基數(shù)(比如IBM 360的機(jī)器)。因此如果你要構(gòu)造一個(gè)和機(jī)器相關(guān)的浮點(diǎn)數(shù)時(shí)就用這個(gè)函數(shù)。
??對(duì)數(shù)函數(shù)
1. 自然常數(shù)e為基數(shù)的對(duì)數(shù)函數(shù):y = ln(x)
extern float logf(float x);
extern double log(double x);
extern long double logl(long double x);
2. 自然常數(shù)e為基數(shù)的對(duì)數(shù)函數(shù): y = ln(x + 1)
extern float log1pf(float x);
extern double log1p(double x);
extern long double log1pl(long double x);
這個(gè)函數(shù)的使用場(chǎng)景主要用于當(dāng)x趨近于0的情況,上面曾經(jīng)描述過當(dāng)兩個(gè)浮點(diǎn)數(shù)之間的數(shù)量值相差很大時(shí)數(shù)字的加減會(huì)存在有效位丟失的情況。因此如果我們用log函數(shù)來計(jì)算時(shí)當(dāng)x趨近于0的ln(x+1)時(shí)就會(huì)存在有效位的損失情況。比如下面的例子:
double o1 = log(1.0e-13 + 1);
double o2 = log1p(1.0e-13);
printf("o1 = %e, o2 = %e", o1, o2);
//output: o1 = 9.992007e-14, o2 = 1.000000e-13
可以看出函數(shù)log1p主要用于當(dāng)x接近于0時(shí)的場(chǎng)景。我們知道函數(shù) y = ln(x+1) 當(dāng)x趨近于0時(shí)的極限是0,因此我們可以用泰勒級(jí)數(shù)來展開他:
可以看出這個(gè)級(jí)數(shù)收斂的很快,因此可以肯定的是log1p函數(shù)的內(nèi)部實(shí)現(xiàn)就是通過上面的泰勒級(jí)數(shù)的方法來實(shí)現(xiàn)求值的。
3. 10為基數(shù)的對(duì)數(shù)函數(shù):y = log10(x)
extern float log10f(float x);
extern double log10(double x);
extern long double log10l(long double x);
4. 2為基數(shù)的對(duì)數(shù)函數(shù)1:y = log2(x)
extern float log2f(float x);
extern double log2(double x);
extern long double log2l(long double x);
5. FLT_RADIX為基數(shù)的對(duì)數(shù)函數(shù)并取整:y = floor(log2(x))
extern float logbf(float x);
extern double logb(double x);
extern long double logbl(long double x);
函數(shù)返回的是一個(gè)小于等于真實(shí)指數(shù)的最大整數(shù),也就是對(duì)返回的值進(jìn)行了floor操作,具體floor函數(shù)的定義見下面。這里的FLT_RADIX是浮點(diǎn)數(shù)的基數(shù),大部分系統(tǒng)定義為2。下面是這個(gè)函數(shù)的一些例子:
logb(2.5) == floor(log2(2.5)) == 1;
logb(4.0) == floor(log2(4.0)) == 2;
logb(4.1) == floor(log2(4.1)) == 2;
logb(7) == floor(log2(7)) == 2;
logb(7.9999) == floor(log2(7.9999)) == 2;
logb(8.0) == floor(log2(8.0)) == 3;
6. FLT_RADIX為基數(shù)的對(duì)數(shù)函數(shù)并取整:y = floor(log2(x))
extern int ilogbf(float x);
extern int ilogb(double x);
extern int ilogbl(long double x);
函數(shù)返回的是一個(gè)小于等于真實(shí)指數(shù)的最大整數(shù),也就是對(duì)返回的值進(jìn)行了floor操作,具體floor函數(shù)的定義見下面。需要注意的是這里返回的類型是整型,因此不可能存在返回NAN或者** INFINITY**的情況。下面是當(dāng)x是0或者負(fù)數(shù)時(shí)返回的特殊值:
FP_ILOGB0: 當(dāng)x是0時(shí)返回這個(gè)特殊值。
FP_ILOGBNAN:當(dāng)x是負(fù)數(shù)時(shí)返回這個(gè)特殊值。
這里區(qū)分一下log2,logb,ilogb 這三個(gè)函數(shù)的差異:
- logb,ilogb是以FLT_RADIX為基數(shù)的對(duì)數(shù),而log2則是以2為基數(shù)的對(duì)數(shù),雖然大部分系統(tǒng)中FLT_RADIX默認(rèn)是定義為2。
- log2,logb返回的都是浮點(diǎn)型,因此有可能返回INFINITY和NAN這兩個(gè)特殊值;而ilogb則返回的是整型,因此如果x是特殊的話那么將會(huì)返回FP_ILOGB0和FP_ILOGBNAN兩個(gè)值。
- log2返回的是有可能帶小數(shù)的指數(shù),而logb和ilogb則返回的是一個(gè)不大于實(shí)際指數(shù)的整數(shù)。
??絕對(duì)值函數(shù)
1. 取絕對(duì)值函數(shù):y = |x|
extern float fabsf(float);
extern double fabs(double);
extern long double fabsl(long double);
??冪函數(shù)
1. 平方根函數(shù):y = √x
extern float sqrtf(float x);
extern double sqrt(double x);
extern long double sqrtl(long double x);
2. 立方根函數(shù): y = ?x
extern float cbrtf(float x);
extern double cbrt(double x);
extern long double cbrtl(long double x);
3. 冪函數(shù):z = x ^ y
extern float powf(float x, float y);
extern double pow(double x, double y);
extern long double powl(long double x, long double y);
4. 歐幾里得距離函數(shù): *d =√x2+y2 *
extern float hypotf(float x, float y);
extern double hypot(double x, double y);
extern long double hypotl(long double x, long double y);
這個(gè)函數(shù)可以用來求直角三角形的斜邊長(zhǎng)度。
??誤差函數(shù)
誤差函數(shù)主要用于概率論和偏微分方程中使用,具體參考誤差函數(shù)
1. 誤差函數(shù)
extern float erff(float x);
extern double erf(double x);
extern long double erfl(long double x);
2. 互補(bǔ)誤差函數(shù)
extern float erfcf(float x);
extern double erfc(double x);
extern long double erfcl(long double x);
??伽瑪函數(shù)
1. 伽瑪函數(shù) :y = ??(x)
extern float lgammaf(float x);
extern double lgamma(double x);
extern long double lgammal(long double x);
2. 階乘函數(shù):y = (x-1)!
extern float tgammaf(float x);
extern double tgamma(double x);
extern long double tgammal(long double x);
伽瑪函數(shù)其實(shí)就是階乘在實(shí)數(shù)上的擴(kuò)展,一般我們知道3! = 3*2*1 = 8。那么我們要求2.5!怎么辦,這時(shí)候就可以用這個(gè)函數(shù)來實(shí)現(xiàn)。這個(gè)函數(shù)也可以用來進(jìn)行階乘計(jì)算。 注意這里是x-1后再計(jì)算的。
??取整函數(shù)
1. 返回一個(gè)大于等于x的最小整數(shù)
extern float ceilf(float x);
extern double ceil(double x);
extern long double ceill(long double x);
舉例來說我們要對(duì)于一個(gè)負(fù)浮點(diǎn)數(shù)按0.5進(jìn)行四舍五入處理:即當(dāng)某個(gè)負(fù)數(shù)的小數(shù)部分大于等于0并且小于0.5時(shí)則舍棄掉小數(shù)部分,而當(dāng)小數(shù)部分大于等于0.5并且小于1時(shí)則等于0.5。我們就可以用ceil函數(shù)來實(shí)現(xiàn)如下:
double y = ceil(x*0.5)/0.5;
2. 返回一個(gè)小于等于x的最大整數(shù)
extern float floorf(float x);
extern double floor(double x);
extern long double floorl(long double x);
舉例來說我們要對(duì)于一個(gè)正浮點(diǎn)數(shù)按0.5進(jìn)行四舍五入處理:即當(dāng)某個(gè)正數(shù)的小數(shù)部分大于等于0并且小于0.5時(shí)則舍棄掉小數(shù)部分,而當(dāng)小數(shù)部分大于等于0.5并且小于1時(shí)則等于0.5。我們就可以用floor函數(shù)來實(shí)現(xiàn)如下:
double y = floor(x*0.5)/0.5;
3. 返回一個(gè)最接近x的整數(shù)
extern float nearbyintf(float x);
extern double nearbyint(double x);
extern long double nearbyintl(long double x);
extern float rintf(float x);
extern double rint(double x);
extern long double rintl(long double x);
//下面三個(gè)函數(shù)返回的是整數(shù)。
extern long int lrintf(float x);
extern long int lrint(double x);
extern long int lrintl(long double x);
//下面三個(gè)函數(shù)是C99或者gnu99中的函數(shù)。
extern long long int llrintf(float x);
extern long long int llrint(double x);
extern long long int llrintl(long double x);
上述各函數(shù)的區(qū)別請(qǐng)參考:http://zh.cppreference.com/w/c/numeric/math/rint
4. 對(duì)x進(jìn)行四舍五入取整
extern float roundf(float x);
extern double round(double x);
extern long double roundl(long double x);
extern long int lroundf(float x);
extern long int lround(double x);
extern long int lroundl(long double x);
//下面三個(gè)函數(shù)是C99或者gnu99中的函數(shù)。
extern long long int llroundf(float x);
extern long long int llround(double x);
extern long long int llroundl(long double x);
如果x是正數(shù),那么當(dāng)小數(shù)部分小于0.5則返回的整數(shù)小于浮點(diǎn)數(shù),如果小數(shù)部分大于等于0.5則返回的整數(shù)大于浮點(diǎn)數(shù);如果x是負(fù)數(shù),那么當(dāng)小數(shù)部分小于0.5則返回的整數(shù)大于浮點(diǎn)數(shù),如果小數(shù)部分大于等于0.5則返回的整數(shù)小于浮點(diǎn)數(shù)。
** 如果我們要實(shí)現(xiàn)保留N位小數(shù)的四舍五入時(shí)。我們可以用如下的方法實(shí)現(xiàn):**
double y = round(x * pow(10, N)) / pow(10, N)
??數(shù)字拆分
1. 返回浮點(diǎn)數(shù)x的整數(shù)部分
extern float truncf(float x);
extern double trunc(double x);
extern long double truncl(long double x);
這個(gè)函數(shù)和floor函數(shù)的區(qū)別主要體現(xiàn)在負(fù)數(shù)上,對(duì)一個(gè)負(fù)數(shù)求floor則會(huì)返回一個(gè)小于等于負(fù)數(shù)的負(fù)整數(shù),而對(duì)一個(gè)負(fù)數(shù)求trunc則會(huì)返回一個(gè)大于等于負(fù)數(shù)的負(fù)整數(shù)。
如果我們要實(shí)現(xiàn)保留N位小數(shù)的截取時(shí)。我們可以用如下的方法實(shí)現(xiàn):
double y = trunc(x * pow(10, N)) / pow(10, N)
2. 返回x/y的余數(shù)1: z = mod(x, y)
extern float fmodf(float x, float y);
extern double fmod(double x, double y);
extern long double fmodl(long double x, long double y);
函數(shù)返回值r = x - n*y, 其中n等于x/y的值截取的整數(shù)。
3. 返回x/y的余數(shù)2: z = mod(x, y)
extern float remainderf(float x, float y);
extern double remainder(double x, double y);
extern long double remainderl(long double x, long double y);
函數(shù)返回值r = x - n*y, 其中n等于x/y的值取最接近的整數(shù),如果有兩個(gè)數(shù)都接近x/y,那么n就取偶數(shù)。比如我們要求remainder(7,2)
。因?yàn)?/2是3.5,按上面規(guī)則n就取4,因此最后的結(jié)果是r = 7 - 4*2 = -1。同樣我們可以得出remainder(7,3) == 7-2\*3 == 1
。
- 從上面的描述可以看出fmod和remainder的區(qū)別主要在于x/y的整數(shù)部分的處理不一樣:前者是取x/y的整數(shù)來算余數(shù),而后者則取最接近x/y的整數(shù)來算余數(shù)。
4. 返回x/y的余數(shù)和整數(shù)商
extern float remquof(float x, float y , int *quo);
extern double remquo(double x, double y, int *quo);
extern long double remquol(long double x, long double y, int * quo);
這個(gè)函數(shù)和** remainder**函數(shù)一樣,只不過會(huì)將整數(shù)商也返回給quo,也就是說r = x - n *y這個(gè)等式中,r作為函數(shù)的返回,而n則返回給quo。
5. 分解出x的整數(shù)和小數(shù)部分
extern float modff(float x, float p*);
extern double modf(double x, double p*);
extern long double modfl(long double x, long double p*);
函數(shù)返回小數(shù)部分,整數(shù)部分存儲(chǔ)在p中。這里面返回值和p都和x具有相同的符號(hào)。
6. 分解出x的指數(shù)和尾數(shù)部分
extern float frexpf(float x, int * p);
extern double frexp(double x, int * p);
extern long double frexpl(long double x, int * p);
函數(shù)返回尾數(shù)*符號(hào)部分,指數(shù)部分存儲(chǔ)在p中。需要明確的是如果浮點(diǎn)數(shù)x為0或者非規(guī)格化浮點(diǎn)數(shù)時(shí)按浮點(diǎn)數(shù)的定義格式返回尾數(shù)和指數(shù),而當(dāng)x為規(guī)格化浮點(diǎn)數(shù)那么返回的值的區(qū)間是[0.5, 1)。這里的返回值和指數(shù)值p和上面介紹的規(guī)格化浮點(diǎn)數(shù)格式:** 符號(hào) * (1.尾數(shù)) * 2^指數(shù) 有差異。因?yàn)榘凑斩x返回的尾數(shù)部分應(yīng)該是1.xxx,但是這里的返回值卻是[0.5, 1)。其實(shí)這并不矛盾,只是函數(shù)對(duì)返回的值做了特殊處理:因?yàn)橐粋€(gè)正浮點(diǎn)數(shù)可以表示為:1.m * 2^e ==> (2^0 + 0.m) * 2^e ==> (2^0 / 2 + 0.m / 2) *2^(e+1) =>(0.5 + 0.m/2) *2^(e+1)。因此frexp函數(shù)返回的真實(shí)值是: 尾數(shù)除以2,而p存儲(chǔ)的是:指數(shù)+1**
下面函數(shù)使用的一些例子:
int p1 = 0;
double y1 = frexp(16.0, &p); //y1=0.5, p= 5
int p2 = 0;
double y2 = frexp(1.0, &p); //y2=0.5, p = 1
int p3 = 0;
double y3 = frexp(0.0, &p); //y3=0, p = 0
這個(gè)函數(shù)和上面的ldexp函數(shù)為互逆函數(shù)。要詳細(xì)的了解浮點(diǎn)數(shù)存儲(chǔ)格式請(qǐng)參考IEEE754
??符號(hào)改變
1. 將y的符號(hào)賦值給x并返回具有和y相同符號(hào)的x值
extern float copysignf(float x, float y);
extern double copysign(double x, double y);
extern long double copysignl(long double x, long double y);
舉例如下:
copysign(10.0, 9.0) == 10;
copysign(-10.0, -9.0) == -10;
copysign(-10.0, 9.0) == 10;
copysign(10.0, -9.0) == -10;
這個(gè)函數(shù)的作用是實(shí)現(xiàn)符號(hào)的賦值,有就是將y的符號(hào)賦值給x。
??無效數(shù)字定義
1.生成一個(gè)quient NAN浮點(diǎn)數(shù)
extern float nanf(const char *tagp);
extern double nan(const char *tagp);
extern long double nanl(const char *tagp);
前面我有介紹了浮點(diǎn)數(shù)里面有兩個(gè)特殊的值:無窮INFINITY和非法NAN,既然這兩個(gè)數(shù)字都可以用浮點(diǎn)數(shù)來描述,那么他就肯定也有對(duì)應(yīng)的存儲(chǔ)格式。我們知道浮點(diǎn)數(shù)的格式為:符號(hào)*尾數(shù)*2^指數(shù)。在IEEE754標(biāo)準(zhǔn)中就對(duì)無窮和非法這兩種特殊的數(shù)進(jìn)行了定義:
- 當(dāng)浮點(diǎn)數(shù)中的指數(shù)部分的二進(jìn)制位全為1。而尾數(shù)部分的二進(jìn)制位全為0時(shí)則表示的浮點(diǎn)數(shù)是無窮INFINITY,如果符號(hào)位為0則表示正無窮大,而符號(hào)位為1則表示負(fù)無窮大。
- 當(dāng)浮點(diǎn)數(shù)中的指數(shù)部分的二進(jìn)制位全為1。而尾數(shù)部分的二進(jìn)制位不全為0時(shí)則表示的浮點(diǎn)數(shù)是非法數(shù)字NAN,或者表示為未定義的數(shù)字。
從上面的對(duì)NAN的定義可以得出非法數(shù)字并不是一個(gè)具體的數(shù)字而是一類數(shù)字,因此對(duì)兩個(gè)為NAN的浮點(diǎn)數(shù)字并不能用等號(hào)來比較。以32位IEEE單精度浮點(diǎn)數(shù)的NAN為例,按位表示即:S111 1111 1AXX XXXX XXXX XXXX XXXX XXXX,其中的S是符號(hào)位,而符號(hào)位后面的指數(shù)位為8個(gè)1表示這個(gè)數(shù)字是一個(gè)特殊的浮點(diǎn)數(shù),剩余的A和X則組成為了尾數(shù)部分,因?yàn)槭?strong>NAN 所以我們要求A和X這些位中至少有一個(gè)是1。在IEEE 754-2008標(biāo)準(zhǔn)中,又對(duì)NAN的類型進(jìn)行了細(xì)分:
- 如果A = 1,則該數(shù)是quiet NAN。也就是quiet NAN中尾數(shù)的最高位為1。
- 如果A為零、其余X部分非零,則是signaling NAN。
區(qū)分兩種NAN的目的是為了更好的對(duì)浮點(diǎn)數(shù)進(jìn)行處理。一般我們將signaling NAN來表示為某個(gè)數(shù)字未初始化,而將quiet NAN則用來表示浮點(diǎn)運(yùn)算的結(jié)果出現(xiàn)了某類異常,比如0除異常,比如負(fù)數(shù)開根異常等等。既然quiet NAN可以用來對(duì)無效數(shù)字進(jìn)行分類,也就是說我們可以構(gòu)建出一個(gè)有類別標(biāo)志的quiet NAN。因此nan函數(shù)就是一個(gè)專門構(gòu)建具有無效類別的NAN函數(shù)(繞了這么多終于說到點(diǎn)子上了)。nan函數(shù)中的tagp參數(shù)就是用來指定非法數(shù)字中的類別,雖然參數(shù)類型是字符串,但是要求里面的值必須是整數(shù)或者空字符串,而且系統(tǒng)在構(gòu)造一個(gè)quiet NAN時(shí)會(huì)將tagp所表示的整數(shù)放在除A外的其他尾數(shù)位上。下面是使用nan函數(shù)的例子:
float f1 = NAN; //0b01111111110000000000000000000000
float f2 = nanf(""); //0b01111111110000000000000000000000
float f3 = nanf("123"); //0b01111111110000000000000001111011
float f4 = nanf("456"); //0b01111111110000000000000111001000
float f5 = nanf("abc"); //0b01111111110000000000000000000000
具體操作時(shí)我們可以用如下來方法來處理各種異常情況:
//定義部分:
float testfn()
{
//有異常時(shí)根據(jù)不同的情況返回不同的nan。
if (異常1)
return nan("100");
else if (異常2)
return nan("200");
else
return 正常數(shù)字;
}
//調(diào)用部分:
float ret = testfn();
if (isnan(ret))
{
//取非法數(shù)字的錯(cuò)誤標(biāo)志部分
int exceptionType = ret & 0x3FFFFF;
if (exceptionType == 100)
{
}
else if (exceptionType == 200)
{
}
}
else
{
//正常處理。
}
有一個(gè)地方疑惑的是為什么NAN定義默認(rèn)值是一個(gè)quiet NAN而不是signaling NAN
??遞增函數(shù)
1. 返回x在y方向上的下一個(gè)可表示的浮點(diǎn)數(shù)。
extern float nextafterf(float x, float y);
extern double nextafter(double x, double y);
extern long double nextafterl(long double x, long double y);
extern double nexttoward(double x, long double y);
extern float nexttowardf(float x, long double y);
extern long double nexttowardl(long double x, long double y);
如果x等于y則返回x。這個(gè)函數(shù)主要用來實(shí)現(xiàn)那些需要高精度增量循環(huán)的處理邏輯。也就是說如果對(duì)浮點(diǎn)數(shù)進(jìn)行for循環(huán)處理時(shí),這個(gè)函數(shù)可以用來實(shí)現(xiàn)最小的浮點(diǎn)數(shù)可表示的數(shù)字的增量。比如下面的代碼:
for (double x = 0.1; x < 0.2; x=nextafter(x,0.2))
{
//...
}
注意這里是下一個(gè)可表示的浮點(diǎn)數(shù),也就是說當(dāng)x為0而y為1時(shí),那么返回的值將是最小的非常規(guī)浮點(diǎn)數(shù);而如果x為1而y為2時(shí),那么返回的值將是1+DBL_MIN(or FLT_MIN). 下面是具體的示例代碼:
// 0.0f == 0b00000000000000000000000000000000
float a = nextafterf(0.0f, 1.0f); //a == 0b00000000000000000000000000000001
// FLT_MIN == 0b00000000100000000000000000000000
float b = nextafterf(FLT_MIN, 1.0f); // b = 0b00000000100000000000000000000001
// 1.0f == 0b00111111100000000000000000000001
float c = nextafterf(1.0f, 1.1f); // c = 0b00111111100000000000000000000001
??比較函數(shù)
1. 返回x減去y的差如果x>y,否則返回0
extern float fdimf(float x, float y);
extern double fdim(double x, double y);
extern long double fdiml(long double x, long double y);
這個(gè)函數(shù)可以用來求兩個(gè)數(shù)的差,并且保證不會(huì)出現(xiàn)負(fù)數(shù)。下面是使用的例子:
double a = fdim(5.0, 3.0); //2.0
double b = fdim(5.0, 5.0); //0.0
double c = fdim(5.0, 6.0); //0.0
2. 返回x和y中大的數(shù)字: z = max(x,y)
extern float fmaxf(float x, float x);
extern double fmax(double x, double x);
extern long double fmaxl(long double x, long double x);
3. 返回x和y中小的數(shù)字: z = min(x,y)
extern float fminf(float x, float y);
extern double fmin(double x, double y);
extern long double fminl(long double x, long double y);
??浮點(diǎn)乘加運(yùn)算
1. 浮點(diǎn)乘加運(yùn)算:w = x*y + z
extern float fmaf(float x, float y, float z);
extern double fma(double x, double y, double z);
extern long double fmal(long double x, long double y, long double z);
這個(gè)函數(shù)返回x*y+z的結(jié)果,而且會(huì)保證中間計(jì)算不會(huì)丟失精度。這個(gè)函數(shù)會(huì)比直接用x*y+z要快,因?yàn)镃PU中專門提供了一個(gè)用于浮點(diǎn)數(shù)乘加的指令FMA。具體情況請(qǐng)參考關(guān)于浮點(diǎn)乘加器方面的資料和應(yīng)用。
結(jié)語
最后歡迎大家訪問我的github站點(diǎn) 多多點(diǎn)贊,多多支持!
參考文章:
http://www.cplusplus.com/reference/cmath/
http://www.gnu.org/software/libc/manual/html_node/Mathematics.html#Mathematics
https://wenku.baidu.com/view/d02978d8d15abe23482f4dac.html
http://blog.csdn.net/hyforthy/article/details/19649969
http://blog.csdn.net/patkritlee/article/details/53809880
http://zh.cppreference.com/w/c/numeric/math/rint
https://zh.wikipedia.org/wiki/NaN
http://www.cnblogs.com/konlil/archive/2011/07/06/2099646.html#commentform
https://en.wikipedia.org/wiki/Denormal_number