問題:浮點型資料存儲方式會導緻資料精度損失,增大計算誤差。
float fval = 0.45; // 單步調試發現其真實值為:0.449999988
double dval = 0.45; // 單步調試發現其真實值為:0.45000000000000001
當很多個這樣的單精度浮點型資料進行運算時,就會有累積誤差,使得運算結果達不到理想的結果。尤其是對那種需要判斷相等的情況(浮點型資料判斷相等會有誤差)。
是以我們可以通過把浮點型資料放大1e6倍,把它賦給一個整型變量,把得到的結果再除以1e6,就會使精度損失降到最低。
下面是浮點型資料存儲方式,轉自:https://www.cnblogs.com/wuyuan2011woaini/p/4105765.html
C語言和 C#語言中,對于浮點型的資料采用單精度類型(float)和雙精度類型(double)來存儲:
float 資料占用 32bit;
double 資料占用 64bit;
我們在聲明一個變量 float f = 2.25f 的時候,是如何配置設定記憶體的呢?
其實不論是 float 類型還是 double 類型,在存儲方式上都是遵從IEEE的規範:
float 遵從的是 IEEE R32.24;
double 遵從的是 IEEE R64.53;
單精度或雙精度在存儲中,都分為三個部分:
符号位 (Sign):0代表正數,1代表為負數;
指數位 (Exponent):用于存儲科學計數法中的指數資料;
尾數部分 (Mantissa):采用移位存儲尾數部分;
單精度 float 的存儲方式如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-YWan5COzcDZyYWOyMmZhhTO1ImYyMWYyQzMwY2MmRDMyYjMj9CX3IzLcVDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL1M3Lc9CX6MHc0RHaiojIsJye.gif)
雙精度 double 的存儲方式如下:
R32.24 和 R64.53 的存儲方式都是用科學計數法來存儲資料的,比如:
8.25 用十進制表示為:8.25 * 100
120.5 用十進制表示為:1.205 * 102
而計算機根本不認識十進制的資料,他隻認識0和1。是以在計算機存儲中,首先要将上面的數更改為二進制的科學計數法表示:
8.25 用二進制表示為:1000.01
118.5 用二進制表示為:1110110.1
而用二進制的科學計數法表示 1000.1,可以表示為1.0001 * 23
而用二進制的科學計數法表示 1110110.1,可以表示為1.1101101 * 26
任何一個數的科學計數法表示都為1. xxx * 2n ,尾數部分就可以表示為xxxx,由于第一位都是1嘛,幹嘛還要表示呀?是以将小數點前面的1省略。
由此,23bit的尾數部分,可以表示的精度卻變成了24bit,道理就是在這裡。(float有效位數相應的也會發生變化,而double則不會,因達不到)
那 24bit 能精确到小數點後幾位呢?我們知道9的二進制表示為1001,是以 4bit 能精确十進制中的1位小數點,24bit就能使 float 精确到小數點後6位;
而對于指數部分,因為指數可正可負(占1位),是以8位的指數位能表示的指數範圍就隻能用7位,範圍是:-127至128。是以指數部分的存儲采用移位存儲,存儲的資料為中繼資料 +127。
注意:
中繼資料+127:大概是指“指數”從00000000開始(表示-127)至11111111(表示+128)
是以,10000000表示指數1 (127 + 1 = 128 --> 10000000 ) ;
指數為 3,則為 127 + 3 = 130,表示為 01111111 + 11 = 10000010 ;
下面就看看 8.25 和 118.5 在記憶體中真正的存儲方式:
8.25 用二進制表示為:1000.01
8.25 用二進制的科學計數法表示為: 1.0001* 23 ,按照上面的存儲方式:
符号位為:0,表示為正;
指數位為:3+127=130,即 10000011;
尾數部分為:0001;
故8.25的存儲方式如下圖所示:
而單精度浮點數118.5的存儲方式如下圖所示:
那麼如果給出記憶體中一段資料,并且告訴你是單精度存儲的話,你将如何知道該資料的十進制數值呢?
其實就是對上面運算的反推過程,比如給出如下記憶體資料:01000010111011010000000000000000,
首先我們現将該資料分段:0 10000101 11011010000000000000000,在記憶體中的存儲就為下圖所示:
根據我們的計算方式,可以計算出這樣一組資料表示為:
1.1101101*2(133-127=6) = 1.1101101 * 26 = 1110110.1=118.5
而雙精度浮點數的存儲和單精度的存儲大同小異,不同的是指數部分和尾數部分的位數。是以這裡不再詳細的介紹雙精度的存儲方式了,隻将118.5的最後存儲方式圖給出:
下面就這個知識點來解決一個疑惑,請看下面一段程式,注意觀察輸出結果:
輸出的結果可能讓大家疑惑不解:
單精度的 2.2 轉換為雙精度後,精确到小數點後13位之後變為了2.2000000476837
而單精度的 2.25 轉換為雙精度後,變為了2.2500000000000
為何 2.2 在轉換後的數值更改了,而 2.25 卻沒有更改呢?
其實通過上面關于兩種存儲結果的介紹,我們大概就能找到答案。
2.25 的單精度存儲方式表示為:0 10000001 00100000000000000000000
2.25 的雙精度存儲方式表示為:0 10000000 0010010000000000000000000000000000000000000000000000000
這樣 2.25 在進行強制轉換的時候,數值是不會變的。
而我們再看看 2.2,用科學計數法表示應該為:
将十進制的小數轉換為二進制的小數的方法是:将小數*2,取整數部分。
0.2×2=0.4,是以二進制小數第一位為0.4的整數部分0;
0.4×2=0.8,第二位為0.8的整數部分0;
0.8×2=1.6,第三位為1;
0.6×2=1.2,第四位為1;
0.2×2=0.4,第五位為0;
...... 這樣永遠也不可能乘到=1.0,得到的二進制是一個無限循環的排列 00110011001100110011...
對于單精度資料來說,尾數隻能表示 24bit 的精度,是以2.2的 float 存儲為:
但是這種存儲方式,換算成十進制的值,卻不會是2.2。
因為在十進制轉換為二進制的時候可能會不準确(如:2.2),這樣就導緻了誤差問題!
并且 double 類型的資料也存在同樣的問題!
是以在浮點數表示中,都可能會不可避免的産生些許誤差!
在單精度轉換為雙精度的時候,也會存在同樣的誤差問題。
而對于有些資料(如2.25),在将十進制轉換為二進制表示的時候恰好能夠計算完畢,是以這個誤差就不會存在,也就出現了上面比較奇怪的輸出結果。