經常使用的數學函數以及浮點數處理函數

在編程中咱們總要進行一些數學運算以及數字處理,尤爲是浮點數的運算和處理,這篇文章主要介紹C語言下的數學庫。而其餘語言中的數學庫函數的定義以及最終實現也是經過對C數學庫的調用來完成的,其內容大同小異,所以就不在這裏介紹了。
C語言標準庫中的math.h定義了很是多的數學運算和數字處理函數。這些函數大部分都是在C89標準中定義的,而有些C99標準下的函數我會特殊的說明,同時由於不一樣的編譯器下的C標準庫中有些函數的定義有差異,我也會分別的說明。html

數字的範圍

整型

整型用來存儲整數數值,它按存儲的字節長短分爲:字符型短整型整型長整型。 全部類型的存儲長度都是定長的。既然類型是定長的就有一個最大最小可表示的範圍,對於整型來講各類類型的最大最小的定義能夠在limits.h中找到。下面表格列出了不一樣類型的存儲長度和最大最小值:node

類型 字節數 最小值 宏定義 最大值 宏定義 備註
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

對於int和long類型來講,兩者的長度是依賴於操做系統的字長或者機器的字長。所以若是咱們要編寫跨平臺或跨系統的程序就應該儘可能減小對這兩個類型變量的直接定義。 下面表格列出了int和long兩種類型在不一樣操做系統字長下的長度。git

類型 16位系統/字節 32位系統/字節 64位系統/字節
int 2 4 4
long 4 4 8

在不少系統中都對32位的整型以及64位的整型進行特殊的定義,好比Windows中的DWORD,UINT32,INT64等等。github

浮點型

浮點型用來存儲浮點數值。它按精度分爲:單精度浮點型雙精度浮點型擴展雙精度浮點型。 浮點數是連續而且無限的,可是計算機並不能表達出全部連續的值。所以對浮點數定義了最小規格化值和最大規格化值,這些定義能夠在float.h中找到。下面表格列出了不一樣類型的存儲長度和最值:express

類型 字節數 最小規格化值 宏定義 最大規格化值 宏定義 備註
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並非指最小可表示的浮點數,而是最小規格化浮點值,具體我會在下面詳細介紹。
  • 對 long double 的定義,取決於編譯器和機器字長,因此對於不一樣平臺可能有不一樣的實現,有的是8字節,有的是10字節,有的是12字節或16字節。
  • 爲了和數學中的無窮∞對應,標準庫中定義了一個宏:INFINITY來表示無窮大。好比1.0/0.0等於INFINITY,-1.0/0.0等於-INFINITY。無窮大能夠進行加減乘除操做,好比1.0/INFINITY == 0。
  • 爲了和數學中的非法數字對應,標準庫中定義了一個宏:NAN來表示非法數字。好比負數開方、負數求對數、0.0/0.0、0.0* INFINITY、INFINITY/INFINITY、INFINITY-INFINITY這些操做都會獲得NAN。注意:若是是整數0/0會產生操做異常

浮點數的存儲結構

浮點數不像整數那樣離散值,而是連續的值。可是用計算機來描述一個浮點數時就不可能徹底實現其精度和連續性,如今的浮點型的存儲和描述廣泛都是遵循IEEE754標準。若是您想詳細的瞭解關於浮點數的存儲格式那麼您能夠花費一點時間來閱讀:wenku.baidu.com/view/d02978… 這篇文章。編程

簡單來講浮點數的存儲由:S(sign)符號位、E(exponent)指數位、M(mantissa 或significand)尾數位三個部分組成。咱們以一個32位的float類型舉例來講,一個浮點數N的從高位到低位的存儲結構以下:app

浮點數的存儲結構
浮點數的存儲結構

也就是一個32位的浮點數由1個符號位,8個指數位,23個尾數位組成。 而爲了表示不一樣類型的浮點數,根據存儲格式對浮點數進行了以下分類:

  • 若是一個浮點數中指數位部分全爲1,而尾數位部分全爲0則這個浮點數表示爲無窮大 INFINITY ,若是符號位爲0表示正無窮大,不然就是負無窮大。
  • 若是一個浮點數中指數位部分全爲1,而尾數位部分不全爲0則這個浮點數表示爲非法數字NAN。所以能夠看出非法數字並不是一個數字而是一類數字。在下面介紹nan函數時我會更加深刻的介紹NAN
  • 若是一個浮點數中除符號位外所有都是0,那麼這個浮點數就是0
  • 若是一個浮點數中指數位部分全爲0,而尾數位部分不全爲0則這個浮點數稱爲非規格化浮點數,英文稱爲:subnormal number 或 denormal number 或 denormalized number。非規格化浮點數經常使用來表示一個很是接近於0的浮點數。
  • 若是一個浮點數中的指數位部分即非全1又非全0。那麼這個浮點數稱之爲規格化浮點數,英文稱之爲:normal number。咱們上面定義的FLT_MIN, DBL_MIN 指的就是最小的規格化浮點數。
  • 咱們把規格化浮點數和非規格化浮點數合稱爲可表示的浮點數,英文稱之爲:machine representable number

一個規格化浮點數N的值能夠用以下公式算出:ide

規格化浮點數計算公式
規格化浮點數計算公式

從上面的公式中能夠看出對於一個32位浮點數來講,指數位佔8位,最小值是1(全0爲很是規浮點),而最大值是254(全1爲無窮或者非法浮點),而減去127則表示指數部分的最小值爲-126,最大值爲127;同時咱們發現除了23位尾數外,還有一個隱藏的1做爲尾數的頭部。所以咱們就很容易得出:
FLT_MIN = 1.0 * 2^-126 = 1.175494351e-38
FLT_MAX = (1.11111111111111111111111)b * 2^127 = 3.402823466e+38函數

一個非規格化浮點數N的值的能夠用以下公式算出:ui

非規格化浮點數計算公式
非規格化浮點數計算公式

從上面的公式中能夠看出對於一個32位的浮點數來講,咱們發現雖然非規格化浮點的指數位部分全0,可是這裏並非0-127,而是1-127,同時發現尾數位部分並無使用隱藏的1做爲尾數的頭部,而是將頭部的1移到了指數部分,這樣作的目的是爲了保持浮點數字的連續性。咱們能夠看出當一個浮點數小於FLT_MIN時,他就變爲了一個非規格化浮點。咱們知道FLT_MIN的值是1.0 2^-126,而一個比FLT_MIN小的值就應該是:(0.11111111111111111111111)b \ 2^-126,而一個比0大的值就是:(0.00000000000000000000001)b * 2^-126。若是非規格化浮點數以-127做爲指數,而繼續使用1做爲尾數的頭部時,那麼這種數字連續性將會被打破。這也是爲何要定義規格化浮點數和非規格化浮點數的意義所在。能夠看出浮點數的這種存儲設計的精妙之處!!。

從上面兩種類型的浮點數中能夠總結出浮點數的計算公式能夠表示爲:
N = 符號 * 尾數 * 2^指數

數學函數

🍏數字判斷函數或宏

//若是x是正無窮大返回1,負無窮大返回-1,不然返回0
int isinf(x)

//若是x是無窮大返回0
int isfinite(x)

//若是x是一個規格化浮點數則返回非0
int  isnormal(x)

//若是x是一個非法的數字返回非0
int isnan(x)

//若是x是負數返回非0
int signbit(x)  

/**
*返回浮點數的分類:
FP_INFINITE:  x是無窮大或者無窮小
FP_NAN:x是一個非法數字
FP_NORMAL:x是一個規格化浮點數
FP_SUBNORMAL:x是一個非規格化浮點數
FP_ZERO:x是0
*/
int  fpclassify(x)複製代碼

🍎三角函數

1. 反餘弦函數: y = arccos(x)
extern float acosf(float x);
extern double acos(double x);
extern long double acosl(long double x);複製代碼
2. 反正弦函數:y = arcsin(x)
extern float asinf(float x);
extern double asin(double x);
extern long double asinl(long double x);複製代碼
3. 反正切函數: y = arctan(x)
extern float atanf(float x);
extern double atan(double x);
extern long double atanl(long double x);複製代碼
4. 2個參數的反正切函數: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);複製代碼

由於arctan的定義域是在(-∞, +∞),而值域是在(-𝜋/2, 𝜋/2)之間。所以 :
atan2f(-1.0, 0.0) == -𝜋/2; atan2f(1.0, 0.0) == 𝜋/2;
這個函數提供的另一個意義在於tan函數的值其實就是對邊除以鄰邊的結果,所以當知道對邊和鄰邊時就能夠直接用這個逆三角函數來求得對應的弧度值。假如特殊狀況下對邊和鄰邊的值都是0.0,那麼若是你調用atan(0.0/0.0)獲得的值將是NAN而不是0。由於0.0/0.0的值是NAN,而對NAN調用atan函數返回的也是NAN,可是對atan2(0.0,0.0)調用返回的結果就是正確值0。

5. 餘弦函數: y = cos(x)
extern float cosf(float x);
extern double cos(double x);
extern long double cosl(long double x);複製代碼
6. 正弦函數:y = sin(x)
extern float sinf(float x);
extern double sin(double x);
extern long double sinl(long double x);複製代碼
7. 正切函數:y = tan(x)
extern float tanf(float x);
extern double tan(double x);
extern long double tanl(long double x);複製代碼

🍐雙曲函數

1. 反雙曲餘弦函數:y = arccosh(x)
extern float acoshf(float x);
extern double acosh(double x);
extern long double acoshl(long double x);複製代碼
2. 反雙曲正弦函數:y = arcsinh(x)
extern float asinhf(float x);
extern double asinh(double x);
extern long double asinhl(long double x);複製代碼
3. 反雙曲正切函數:y = arctanh(x)
extern float atanhf(float x);
extern double atanh(double x);
extern long double atanhl(long double x);複製代碼
4. 雙曲餘弦函數:y = cosh(x)
extern float coshf(float x);
extern double cosh(double x);
extern long double coshl(long double x);複製代碼
5. 雙曲正弦函數:y = sinh(x)
extern float sinhf(float x);
extern double sinh(double x);
extern long double sinhl(long double x);複製代碼
6. 雙曲正切函數: y = tanh(x)
extern float tanhf(float x);
extern double tanh(double x);
extern long double tanhl(long double x);複製代碼

🍊指數函數

1. 天然常數e爲基數的指數函數:y = e^x
extern float expf(float x);
extern double exp(double x);
extern long double expl(long double x);複製代碼
2. 天然常數e爲基數的指數減1:y = e^x - 1
extern float expm1f(float x);
extern double expm1(double x); 
extern long double expm1l(long double x);複製代碼

咱們既然定義了exp函數,那麼按理說要實現e^x-1就很簡單,爲何要單獨定義這個函數呢?先看下面兩個輸出:

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複製代碼

從上面的例子中發現當用exp函數時出現了有效數字損失而expm1則沒有。出現這種問題的緣由就是浮點加減運算自己機制的問題,在浮點運算中下面兩種類型的運算都有可能出現損失有效數字的狀況:

  • 兩個相近的數相減
  • 兩個數量級相差很大的數字相加減

咱們能夠作一個實驗,分別在調試器中查看a1,a2和b1,b2的結果:

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;

//咱們發現a1的值是0.0099999999999997868,而a2的值就是0.01
//咱們發現b1的值是99.989999999999994而b2的值是99.990000000000009複製代碼

從上面的例子中能夠看出當浮點數相近或者差別很大時加減運算出現了有效數字損失的狀況,同時上面的例子也給出了一個減小這種損失的簡易解決方案。再回到上面exp函數的場景中,由於exp(1.0e-13)的值和1.0是很是接近,所以當對這兩個數作減法時就會出現有效數字損失的狀況。咱們再來考察expm1函數,這個函數主要用於當x接近於0時的場景。咱們知道函數 y = e^x - 1 當x趨近於0時的極限是0,所以咱們能夠用泰勒級數來展開他:

e^x-1泰勒級數展開
e^x-1泰勒級數展開

能夠看出這個級數收斂的很快,所以能夠確定的是 expm1函數的內部實現就是經過上面的泰勒級數的方法來實現求值的。下面這段函數使用手冊的文檔也給出了用 expm1代替 exp函數的例子和說明:

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爲基數的指數函數:y = 2^x
extern float exp2f(float x);
extern double exp2(double x); 
extern long double exp2l(long double x);複製代碼
4. 浮點數構造函數: 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);複製代碼

既然上面已經存在了一個exp函數,若是咱們要實現相同的功能按理來只要:x*exp(n)就行了,爲何還要單獨提供一個新的ldexp函數呢?緣由就是ldexp函數實際上是一個用來構造浮點數的函數,咱們知道浮點數的格式定義在IEEE754中,具體的結構爲:符號*尾數*2^指數,恰好和ldexp所實現的功能是一致的,這裏的x用來指定符號*尾數,而n則指定爲指數。所以咱們就能夠藉助這個函數來實現浮點數的構造。

5. 以FLT_RADIX基數的浮點數構造函數: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是浮點數存儲裏面的基數(在float.h中有定義這個宏),通常狀況下是2,這時候這個函數就和ldexp函數是一致的。可是有些系統的浮點數存儲並非以2爲基數(好比IBM 360的機器)。所以若是你要構造一個和機器相關的浮點數時就用這個函數。


🍋對數函數

1. 天然常數e爲基數的對數函數:y = ln(x)
extern float logf(float x);
extern double log(double x);
extern long double logl(long double x);複製代碼
2. 天然常數e爲基數的對數函數: y = ln(x + 1)
extern float log1pf(float x);
extern double log1p(double x);
extern long double log1pl(long double x);複製代碼

這個函數的使用場景主要用於當x趨近於0的狀況,上面曾經描述過當兩個浮點數之間的數量值相差很大時數字的加減會存在有效位丟失的狀況。所以若是咱們用log函數來計算時當x趨近於0的ln(x+1)時就會存在有效位的損失狀況。好比下面的例子:

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複製代碼

能夠看出函數log1p主要用於當x接近於0時的場景。咱們知道函數 y = ln(x+1) 當x趨近於0時的極限是0,所以咱們能夠用泰勒級數來展開他:

ln(x+1)的泰勒級數展開
ln(x+1)的泰勒級數展開

能夠看出這個級數收斂的很快,所以能夠確定的是log1p函數的內部實現就是經過上面的泰勒級數的方法來實現求值的。

3. 10爲基數的對數函數:y = log10(x)
extern float log10f(float x);
extern double log10(double x);
extern long double log10l(long double x);複製代碼
4. 2爲基數的對數函數1:y = log2(x)
extern float log2f(float x);
extern double log2(double x);
extern long double log2l(long double x);複製代碼
5. FLT_RADIX爲基數的對數函數並取整:y = floor(log2(x))
extern float logbf(float x);
extern double logb(double x);
extern long double logbl(long double x);複製代碼

函數返回的是一個小於等於真實指數的最大整數,也就是對返回的值進行了floor操做,具體floor函數的定義見下面。這裏的FLT_RADIX是浮點數的基數,大部分系統定義爲2。下面是這個函數的一些例子:

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爲基數的對數函數並取整:y = floor(log2(x))
extern int ilogbf(float x);
extern int ilogb(double x);
extern int ilogbl(long double x);複製代碼

函數返回的是一個小於等於真實指數的最大整數,也就是對返回的值進行了floor操做,具體floor函數的定義見下面。須要注意的是這裏返回的類型是整型,所以不可能存在返回NAN或者 INFINITY的狀況。下面是當x是0或者負數時返回的特殊值:

FP_ILOGB0:  當x是0時返回這個特殊值。
FP_ILOGBNAN:當x是負數時返回這個特殊值。複製代碼

這裏區分一下log2,logb,ilogb 這三個函數的差別:

  • logb,ilogb是以FLT_RADIX爲基數的對數,而log2則是以2爲基數的對數,雖然大部分系統中FLT_RADIX默認是定義爲2。
  • log2,logb返回的都是浮點型,所以有可能返回INFINITY和NAN這兩個特殊值;而ilogb則返回的是整型,所以若是x是特殊的話那麼將會返回FP_ILOGB0和FP_ILOGBNAN兩個值。
  • log2返回的是有可能帶小數的指數,而logb和ilogb則返回的是一個不大於實際指數的整數。

🍌絕對值函數

1. 取絕對值函數:y = |x|
extern float fabsf(float);
extern double fabs(double);
extern long double fabsl(long double);複製代碼

🍉冪函數

1. 平方根函數:y = √x
extern float sqrtf(float x);
extern double sqrt(double x);
extern long double sqrtl(long double x);複製代碼
2. 立方根函數: y = ∛x
extern float cbrtf(float x);
extern double cbrt(double x);
extern long double cbrtl(long double x);複製代碼
3. 冪函數: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. 歐幾里得距離函數: d =√x^2+y^2
extern float hypotf(float x, float y);
extern double hypot(double x, double y);
extern long double hypotl(long double x, long double y);複製代碼

這個函數能夠用來求直角三角形的斜邊長度。


🍇偏差函數

偏差函數主要用於機率論和偏微分方程中使用,具體參考偏差函數

1. 偏差函數
extern float erff(float x);
extern double erf(double x);
extern long double erfl(long double x);複製代碼
2. 互補偏差函數
extern float erfcf(float x);
extern double erfc(double x);
extern long double erfcl(long double x);複製代碼

🍓伽瑪函數

1. 伽瑪函數 :y = 𝚪(x)
extern float lgammaf(float x);
extern double lgamma(double x);
extern long double lgammal(long double x);複製代碼
2. 階乘函數:y = (x-1)!
extern float tgammaf(float x);
extern double tgamma(double x);
extern long double tgammal(long double x);複製代碼

伽瑪函數其實就是階乘在實數上的擴展,通常咱們知道3! = 3*2*1 = 8。那麼咱們要求2.5!怎麼辦,這時候就能夠用這個函數來實現。這個函數也能夠用來進行階乘計算。 注意這裏是x-1後再計算的。


🍈取整函數

1. 返回一個大於等於x的最小整數
extern float ceilf(float x);
extern double ceil(double x);
extern long double ceill(long double x);複製代碼

舉例來講咱們要對於一個負浮點數按0.5進行四捨五入處理:即當某個負數的小數部分大於等於0而且小於0.5時則捨棄掉小數部分,而當小數部分大於等於0.5而且小於1時則等於0.5。咱們就能夠用ceil函數來實現以下:

double y = ceil(x*0.5)/0.5;複製代碼
2. 返回一個小於等於x的最大整數
extern float floorf(float x);
extern double floor(double x);
extern long double floorl(long double x);複製代碼

舉例來講咱們要對於一個正浮點數按0.5進行四捨五入處理:即當某個正數的小數部分大於等於0而且小於0.5時則捨棄掉小數部分,而當小數部分大於等於0.5而且小於1時則等於0.5。咱們就能夠用floor函數來實現以下:

double y = floor(x*0.5)/0.5;複製代碼
3. 返回一個最接近x的整數
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);

//下面三個函數返回的是整數。
extern long int lrintf(float x);
extern long int lrint(double x);
extern long int lrintl(long double x);

//下面三個函數是C99或者gnu99中的函數。
extern long long int llrintf(float x);
extern long long int llrint(double x);
extern long long int llrintl(long double x);複製代碼

上述各函數的區別請參考:zh.cppreference.com/w/c/numeric…

4. 對x進行四捨五入取整
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);

//下面三個函數是C99或者gnu99中的函數。
extern long long int llroundf(float x);
extern long long int llround(double x);
extern long long int llroundl(long double x);複製代碼

若是x是正數,那麼當小數部分小於0.5則返回的整數小於浮點數,若是小數部分大於等於0.5則返回的整數大於浮點數;若是x是負數,那麼當小數部分小於0.5則返回的整數大於浮點數,若是小數部分大於等於0.5則返回的整數小於浮點數。

若是咱們要實現保留N位小數的四捨五入時。咱們能夠用以下的方法實現:

double y = round(x * pow(10, N)) / pow(10, N)複製代碼

🍒數字拆分

1. 返回浮點數x的整數部分
extern float truncf(float x);
extern double trunc(double x);
extern long double truncl(long double x);複製代碼

這個函數和floor函數的區別主要體如今負數上,對一個負數求floor則會返回一個小於等於負數的負整數,而對一個負數求trunc則會返回一個大於等於負數的負整數。

若是咱們要實現保留N位小數的截取時。咱們能夠用以下的方法實現:

double y = trunc(x * pow(10, N)) / pow(10, N)複製代碼
2. 返回x/y的餘數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);複製代碼

函數返回值r = x - n*y, 其中n等於x/y的值截取的整數。

3. 返回x/y的餘數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);複製代碼

函數返回值r = x - ny, 其中n等於x/y的值取最接近的整數,若是有兩個數都接近x/y,那麼n就取偶數。好比咱們要求remainder(7,2)。由於7/2是3.5,按上面規則n就取4,所以最後的結果是r = 7 - 4\2 = -1。一樣咱們能夠得出remainder(7,3) == 7-2\*3 == 1

  • 從上面的描述能夠看出fmodremainder的區別主要在於x/y的整數部分的處理不同:前者是取x/y的整數來算餘數,然後者則取最接近x/y的整數來算餘數。
4. 返回x/y的餘數和整數商
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);複製代碼

這個函數和 remainder函數同樣,只不過會將整數商也返回給quo,也就是說r = x - n *y這個等式中,r做爲函數的返回,而n則返回給quo。

5. 分解出x的整數和小數部分
extern float modff(float x, float p*);
extern double modf(double x, double p*);
extern long double modfl(long double x, long double p*);複製代碼

函數返回小數部分,整數部分存儲在p中。這裏面返回值和p都和x具備相同的符號。

6. 分解出x的指數和尾數部分
extern float frexpf(float x, int * p);
extern double frexp(double x, int * p);
extern long double frexpl(long double x, int * p);複製代碼

函數返回尾數*符號部分,指數部分存儲在p中。須要明確的是若是浮點數x爲0或者非規格化浮點數時按浮點數的定義格式返回尾數和指數,而當x爲規格化浮點數那麼返回的值的區間是[0.5, 1)。這裏的返回值和指數值p和上面介紹的規格化浮點數格式: 符號 * (1.尾數) * 2^指數 有差別。由於按照定義返回的尾數部分應該是1.xxx,可是這裏的返回值倒是[0.5, 1)。其實這並不矛盾,只是函數對返回的值作了特殊處理:由於一個正浮點數能夠表示爲: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函數返回的真實值是: 尾數除以2,而p存儲的是:指數+1

下面函數使用的一些例子:

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複製代碼

這個函數和上面的ldexp函數爲互逆函數。要詳細的瞭解浮點數存儲格式請參考IEEE754


🍑符號改變

1. 將y的符號賦值給x並返回具備和y相同符號的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;複製代碼

這個函數的做用是實現符號的賦值,有就是將y的符號賦值給x。


🍍無效數字定義

1.生成一個quient NAN浮點數
extern float nanf(const char *tagp);
extern double nan(const char *tagp);
extern long double nanl(const char *tagp);複製代碼

前面我有介紹了浮點數裏面有兩個特殊的值:無窮INFINITY和非法NAN,既然這兩個數字均可以用浮點數來描述,那麼他就確定也有對應的存儲格式。咱們知道浮點數的格式爲:符號*尾數*2^指數。在IEEE754標準中就對無窮和非法這兩種特殊的數進行了定義:

  • 當浮點數中的指數部分的二進制位全爲1。而尾數部分的二進制位全爲0時則表示的浮點數是無窮INFINITY,若是符號位爲0則表示正無窮大,而符號位爲1則表示負無窮大。
  • 當浮點數中的指數部分的二進制位全爲1。而尾數部分的二進制位不全爲0時則表示的浮點數是非法數字NAN,或者表示爲未定義的數字。

從上面的對NAN的定義能夠得出非法數字並非一個具體的數字而是一類數字,所以對兩個爲NAN的浮點數字並不能用等號來比較。以32位IEEE單精度浮點數的NAN爲例,按位表示即:S111 1111 1AXX XXXX XXXX XXXX XXXX XXXX,其中的S是符號位,而符號位後面的指數位爲8個1表示這個數字是一個特殊的浮點數,剩餘的A和X則組成爲了尾數部分,由於是NAN 因此咱們要求A和X這些位中至少有一個是1。在IEEE 754-2008標準中,又對NAN的類型進行了細分:

  • 若是A = 1,則該數是quiet NAN。也就是quiet NAN中尾數的最高位爲1。
  • 若是A爲零、其他X部分非零,則是signaling NAN

區分兩種NAN的目的是爲了更好的對浮點數進行處理。通常咱們將signaling NAN來表示爲某個數字未初始化,而將quiet NAN則用來表示浮點運算的結果出現了某類異常,好比0除異常,好比負數開根異常等等。既然quiet NAN能夠用來對無效數字進行分類,也就是說咱們能夠構建出一個有類別標誌的quiet NAN。所以nan函數就是一個專門構建具備無效類別的NAN函數(繞了這麼多終於說到點子上了)。nan函數中的tagp參數就是用來指定非法數字中的類別,雖然參數類型是字符串,可是要求裏面的值必須是整數或者空字符串,並且系統在構造一個quiet NAN時會將tagp所表示的整數放在除A外的其餘尾數位上。下面是使用nan函數的例子:

float f1 = NAN;           //0b01111111110000000000000000000000
     float f2 = nanf("");      //0b01111111110000000000000000000000
     float f3 = nanf("123");   //0b01111111110000000000000001111011
     float f4 = nanf("456");   //0b01111111110000000000000111001000 
     float f5 = nanf("abc");   //0b01111111110000000000000000000000複製代碼

具體操做時咱們能夠用以下來方法來處理各類異常狀況:

//定義部分:
float  testfn()
{
    //有異常時根據不一樣的狀況返回不一樣的nan。
   if (異常1)
    return nan("100");
 else if (異常2)
   return nan("200");
else
   return 正常數字;
}

//調用部分:

float ret = testfn();
if (isnan(ret))
{
      //取非法數字的錯誤標誌部分
      int exceptionType = ret & 0x3FFFFF;
      if (exceptionType == 100)
     {
     }
     else if (exceptionType == 200)
     {
     }
}
else
{
   //正常處理。
}複製代碼

有一個地方疑惑的是爲何NAN定義默認值是一個quiet NAN而不是signaling NAN


🥝遞增函數

1. 返回x在y方向上的下一個可表示的浮點數。
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。這個函數主要用來實現那些須要高精度增量循環的處理邏輯。也就是說若是對浮點數進行for循環處理時,這個函數能夠用來實現最小的浮點數可表示的數字的增量。好比下面的代碼:

for (double x = 0.1; x < 0.2; x=nextafter(x,0.2))
   {
         //...
    }複製代碼

注意這裏是下一個可表示的浮點數,也就是說當x爲0而y爲1時,那麼返回的值將是最小的很是規浮點數;而若是x爲1而y爲2時,那麼返回的值將是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複製代碼

🥑比較函數

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);複製代碼

這個函數能夠用來求兩個數的差,而且保證不會出現負數。下面是使用的例子:

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中大的數字: 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中小的數字: 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);複製代碼

🍅浮點乘加運算

1. 浮點乘加運算: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);複製代碼

這個函數返回x*y+z的結果,並且會保證中間計算不會丟失精度。這個函數會比直接用x*y+z要快,由於CPU中專門提供了一個用於浮點數乘加的指令FMA。具體狀況請參考關於浮點乘加器方面的資料和應用。

結語

最後歡迎你們訪問個人github站點 多多點贊,多多支持!

參考文章:

www.cplusplus.com/reference/c…
www.gnu.org/software/li…
wenku.baidu.com/view/d02978…
blog.csdn.net/hyforthy/ar…
blog.csdn.net/patkritlee/…
zh.cppreference.com/w/c/numeric…
zh.wikipedia.org/wiki/NaN
www.cnblogs.com/konlil/arch…
en.wikipedia.org/wiki/Denorm…

相關文章
相關標籤/搜索