平方根的C語言實現(一) —— 浮點數的存儲

  版權申明:本文爲博主窗戶(Colin Cai)原創,歡迎轉帖。如要轉貼,必須註明原文網址

  http://www.cnblogs.com/Colin-Cai/p/7203254.html 

  做者:窗戶

  QQ:6679072

  E-mail:6679072@qq.com

  曾經作一個硬件成本極度控制的項目,由於硬件成本極低,而且還須要實現較高的精度測量,過程當中也本身用C語言實現了正弦、餘弦、反正切、平方根等函數。html

  如下,不管是在個人實際項目中仍是本地的計算機系統,int都是4個字節且機器爲小端,除非特別說起,不然都如此默認。按理float的存儲沒有大小端之分,但是的確在powerpc大端上浮點數的存儲也同樣是和X86/ARM這樣的小端機相反。不過由於正好因大小端而決定浮點數的存儲順序,那麼本系列貼子裏全部的C語言程序至少在powerpc大端上也是效果相同的。算法

  儘管在這個項目中我很是想用double來存儲小數,但由於這須要翻一倍的存儲,從而只好做罷,爲了那可憐的存儲,我一度甚至想考慮實現3字節的浮點數來,但大體估算了偏差(至於如何估算一個公式計算的偏差,須要先利用浮點數的結構求自變量的偏差,而後要用到數值分析求公式偏差,之後有機會開貼單說),實在不靠譜,因而仍是用float單精度4字節來存儲浮點數。此項目後面圍繞着精度、運算時間,從而調整了好幾回數據產生、乃至算法的原理,不過這都不是這個系列裏要講的。本系列只講單精度4字節浮點數的平方根實現,一共分爲三節:sql

  第一節講浮點數的存儲;閉包

  第二節講手算平方根的原理;函數

  第三節講C語言最終實現。ui

  咱們先看浮點數是如何表示實數的,IEEE 754定義了浮點數的結構:spa

  在瞭解浮點數的存儲以前,咱們瞭解一下科學計數法。code

  咱們日常用的進位製爲十進制,全部不爲0的實數均可以表示爲s*a*10n,其中:s取1或-1,稱爲符號部分;a知足1≤a<10,稱爲小數部分;n爲整數,稱爲指數部分。htm

  咱們的計算機計數通常使用二進制,其道理不用我多說,浮點數也同樣用的二進制,用的是二進制下的科學計數法。仿照以前十進制下的科學計數法,便可得二進制下的科學計數。全部不爲0的實數,均可以表示爲s*a*10n,其中:s取1或-1,稱爲符號部分;a知足1≤a<2,稱爲小數部分;n爲整數,稱爲指數部分。32個bit中,最高位1個bits表示符號位s,緊接着的8個bits表示指數位,最後的23個bits表示a。blog

 

  S(1bits)  |   N(8bits)  |  A(23bits)

 

  用大寫表示,表明二進制,與科學計數法的s/n/a關係以下:

  若S爲0,則s取1;若S爲1,則s取-1。

  n = N(2) - 127,這裏N(2)是N的二進制值,

  在符號不會混亂的時候,下面就用N來代替N(2)

  a = 1 + A(2)*2-23 ,這裏是A的二進制值,

  在符號不會混亂的時候,下面就用A來代替A(2)

  浮點數和定點數同樣,也是離散的,4字節浮點數有32個bits,因此最多隻能表示232個不一樣的實數,是對實數的一種近似,但卻有很大的範圍,能夠知足咱們不少的需求了。

  寫一個C語言程序來驗證這點:

#include <stdio.h>
#include <string.h>
#include <inttypes.h>
int main(int argc, char **argv)
{
        union {
                float f;
                uint32_t u;
        } n;
        int i;

        scanf("%f", &n.f);
        for(i=31;i>=0;i--) {
                if(n.u&(1u<<i))
                        printf("1");
                else
                        printf("0");
                if(i==31 || i==23)
                        printf(" ");
        }
        printf("\n");
        printf("S:%u\n", (n.u&(1u<<31))>>31);
        printf("N:%u\n", (n.u&(0xff<<23))>>23);
        printf("A:%u\n", n.u&0x7fffff);
        return 0;
}

  隨便找幾個數驗證一下

$ echo 1 | ./a.out
0 01111111 00000000000000000000000
S:0
N:127
A:0
$ echo -1 | ./a.out
1 01111111 00000000000000000000000
S:1
N:127
A:0
$ echo 2 | ./a.out
0 10000000 00000000000000000000000
S:0
N:128
A:0
$ echo 3 | ./a.out
0 10000000 10000000000000000000000
S:0
N:128
A:4194304
$ echo 3.5 | ./a.out
0 10000000 11000000000000000000000
S:0
N:128
A:6291456
$ echo 3.75 | ./a.out
0 10000000 11100000000000000000000
S:0
N:128
A:7340032
$ echo 0.75 | ./a.out
0 01111110 10000000000000000000000
S:0
N:126
A:4194304
$ echo 0.875 | ./a.out
0 01111110 11000000000000000000000
S:0
N:126
A:6291456

上面的數都是知足的。

但是咱們再回頭看看,科學計數法其實也是有缺陷的,0實際上是沒法用科學計數法表示的,但是0使用的場合很是多,因此浮點數必須支持。因而單精度浮點數的2^32種表示中,不是每一種都是科學計數法。

IEEE754規定,單精度浮點數還支持非規格化的數,也就是否是科學計數法的數。

當指數位N爲0,也就是N的8個bits全是0的時候,符號位依然是符號位,

表示的數值是s* A*2-149

之因此後面的指數是149,是由於規格化的數所能表示的最小正數爲2-126,

而N爲0的時候所表示的最大數爲(223-1)*2-149,

二者十分接近,

$ echo 'scale=60;2^(-126);(2^23-1)*2^(-149);' | bc
.000000000000000000000000000000000000011754943508222875079687
.000000000000000000000000000000000000011754942106924410159919

編個C語言程序驗證一下

#include <stdio.h>
#include <string.h>
#include <inttypes.h>
int main(int argc, char **argv)
{
        union {
                float f;
                uint32_t u;
        } n;
        int i;

        scanf("%" PRIx32, &n.u);
        for(i=31;i>=0;i--) {
                if(n.u&(1u<<i))
                        printf("1");
                else
                        printf("0");
                if(i==31 || i==23)
                        printf(" ");
        }
        printf("\n");
        printf("S:%u\n", (n.u&(1u<<31))>>31);
        printf("N:%u\n", (n.u&(0xff<<23))>>23);
        printf("A:%u\n", n.u&0x7fffff);
        printf("%.60f\n", n.f);
        return 0;
}

  找幾個數驗證一下

$ echo 0x00000001 | ./a.out
0 00000000 00000000000000000000001
S:0
N:0
A:1
0.000000000000000000000000000000000000000000001401298464324817
$ echo 0x007fffff | ./a.out
0 00000000 11111111111111111111111
S:0
N:0
A:8388607
0.000000000000000000000000000000000000011754942106924410754870
$ echo 0x80000001 | ./a.out
1 00000000 00000000000000000000001
S:1
N:0
A:1
-0.000000000000000000000000000000000000000000001401298464324817
$ echo 0x807fffff | ./a.out
1 00000000 11111111111111111111111
S:1
N:0
A:8388607
-0.000000000000000000000000000000000000011754942106924410754870
$ echo 0x00000000 | ./a.out
0 00000000 00000000000000000000000
S:0
N:0
A:0
0.000000000000000000000000000000000000000000000000000000000000
$ echo 0x80000000 | ./a.out
1 00000000 00000000000000000000000
S:1
N:0
A:0
-0.000000000000000000000000000000000000000000000000000000000000

能夠看到,有0和-0的區別,浮點數的確就有這麼神奇。

另外,IEEE754規定,N等於127,也就是這8個bits全爲1的時候也是非規格數,分如下三種狀況:

S=0,N=127,A=0時,爲正無窮大;

S=1,N=127,A=0時,爲負無窮大;

N=127,A≠0是,爲NAN(not a number)。

同理,咱們仍是驗證一下:

$ echo 0x7f800000 | ./a.out
0 11111111 00000000000000000000000
S:0
N:255
A:0
inf
$ echo 0xff800000 | ./a.out
1 11111111 00000000000000000000000
S:1
N:255
A:0
-inf
$ echo 0x7f800001 | ./a.out
0 11111111 00000000000000000000001
S:0
N:255
A:1
nan
$ echo 0xff800001 | ./a.out
1 11111111 00000000000000000000001
S:1
N:255
A:1
nan

 

inf和-inf用於兩個實數經過運算產生,因其大小上已經超越浮點數最大可程度以表示的實數,只能用無窮大表示,或者浮點數除0。

而nan則是結果已經不是實數範疇了,好比inf參與了運算,再好比,負數開平方根也會產生nan,這是由於浮點數並非用於直接表示複數,浮點數並不是是要直接模擬一個近似的代數閉包。

相關文章
相關標籤/搜索