問題的拋出:
版權聲明:本文為博主原創文章,轉載請附上原文出處鏈接和本聲明。2019-10-03,00:56:39。
作者By-----溺心與沉浮----博客園
為什么兩個浮點數相減時,有時出乎我們意料之外的值呢?例如3.1415927 - 3.1415926 = 0.0000002?(例子我隨便舉得,大家不要在乎這個,例子中這個值我也沒有碰到過,但我相信大家在做浮點數運算時,肯定有這種類似的情況)這涉及到精確問題。
1/3用分數可以來很好的表示,可是如果不允許用分數表示呢?如何保證數盡可能的等於1/3呢?相信大家都知道,0.3......小數點后面3的數量越多,表示的就更加靠近。
就如十進制系統中不能准確表示出1/3!同樣二進制系統也無法准確表示1/10。這也就解釋了為什么浮點型減法出現了“減不盡”的精度丟失問題。
概述:
float和double在存儲方式上都是遵從IEEE的規范的,float的存儲方式如下圖所示:
double的存儲方式如下圖所示:
步驟:
將一個float型轉化為內存存儲格式的步驟為:
1、先將這個實數的絕對值化為二進制格式
2、將這個二進制格式實數的小數點左移或右移n位,直到小數點移動到第一個有效數字的右邊。(請類比十進制的科學計數法)
3、從小數點右邊第一位開始數出二十三位數字放入第22到第0位。
4、如果實數是正的,則在第31位放入“0”,否則放入“1”。
5、如果n 是左移得到的,說明指數是正的,第30位放入“1”。如果n是右移得到的或n=0,則第30位放入“0”。
6、如果n是左移得到的,則將n減去1后化為二進制,並在左邊加“0”補足七位,放入第29到第23位。 如果n是右移得到的或n=0,則將n化為二進制后在左邊加“0”補足七位,再各位求反,再放入第29到第23位。
首先我們需要搞清楚下面兩個問題:
(1)十進制整數如何轉化為二進制數
算法很簡單,舉個例子,11表示成二進制數:
11 / 2 余 1
5 / 2 余 1
2 / 2 余 0
1 / 2 余 1
0結束 11進制表示為(從下往上)1011
這里提一點,只要遇到除以后的結果為0了就結束了,大家想想,所有的整數除以2是不是一定能夠最終得到0呢。換句話說,所有的整數轉變為二進制數的算法會不會無線循環下去呢?絕對不會,整數永遠可以用二進制精確表示,但小數不一定了。
(2)十進制小數如何轉化為二進制數
算法是乘以2直到沒有了小數為止。舉個例子,0.9表示成二進制數
0.9 × 2 = 1.8 取整數部分 1
0.8(1.8的小數部分) × 2 = 1.6 取整數部分 1
0.6 × 2 = 1.2 取整數部分 1
0.2 × 2 = 0.4 取整數部分 0
0.4 × 2 = 0.8 取整數部分 0
0.8 × 2 = 1.6 取整數部分 1
0.6 × 2 = 1.2 取整數部分 1
0.2 × 2 = 0.4 取整數部分 0
0.4 × 2 = 0.8 取整數部分 0
...... 0.9二進制表示為(從上往下):11100110011001100......
注意:上面的計算過程循環了,相信你也發現了,也就是說本例子中0.9×2永遠不可能消滅小數部分,這樣算法講無限循環下去。很顯然,小數的二進制表示有時是不可能精確的。其實道理很簡單,十進制系統中能不能准確表示處1/3呢?同樣二進制系統也無法准確表示1/10。這也就解釋了為什么浮點型減法出現了“減不盡”的精度丟失問題。
舉例:
以8.25舉例,看看8.23在計算中是如何存儲的
(1)首先將整數部分8轉換成2進制
8 / 2 余 0
4 / 2 余 0
2 / 2 余 0
1 / 2 余 1
(2)小數部分0.25轉換成二進制
0.25 × 2 = 0.5 取整數部分 0
0.5 × 2 = 1.0 取整數部分 1
(3)整數部分與小數部分都已轉換成了二進制,那么8.25的二進制表示為1000.01,雖然是可以這么表示了,但是計算機認識嗎?所有的內容(數,字母,漢字,符號)在計算機世界里只有2種形態,0和1,因此這不是我們最終要展現的數!
(4)8.25---->>>1000.01用二進制的科學計數法表示(還記得十進制的科學計數法嗎?換個進制來使用)1.00001 × 2的3次方,指數為3。
(5)首先8.25我們表現形式是正數,那么31位中填0(記得前面說的有符號數與無符號數的博文嗎?),0x7FFFFFFF, 0x80000000這兩個數便是有符號數的一個界限,所以在有符號數中最高位為1,代表負,最高位為0,代表正!
但0x80000000它一定代表着負數嗎?不是,在無符號數中,最高位為1,依然是正數。怎么區分,看使用者如何定義。這里我不做介紹了!
正數,符號位,我們在第31位中填0,
指數部分,我們的n是左移得到的,說明我們的指數為正,因此第30位我們填1,然后我們將指數n減1,得到2,它的二進制是10,並在左邊添0,從第29位開始到第23位,我們湊夠7位,因此指數部分是10000010
(6)22 - 0尾數部分怎么填呢?我們8.25轉換成二進制不是1000.01也即1.00001 × 2的3次方嗎?我們取科學計數法小數點后面的數,從第22位開始,全部往里扔,后面不足全部補0
(7)最終的表示:
整理一下,我們得到:0100 0001 0000 0100 0000 0000 0000 0000,這便是我們最終需要的二進制表示形式,轉換成16進制為:0x41040000,我們用編譯器查看一下
1 // xiaoyu1.cpp : Defines the entry point for the console application. 2 // 3
4 #include "stdafx.h"
5
6 void Function() 7 { 8 float a = 8.25f; 9 } 10
11 int main(int argc, char* argv[]) 12 { 13 Function(); 14 return 0; 15 }
1 6: void Function() 2 7: { 3 00401020 push ebp 4 00401021 mov ebp,esp 5 00401023 sub esp,44h 6 00401026 push ebx 7 00401027 push esi 8 00401028 push edi 9 00401029 lea edi,[ebp-44h] 10 0040102C mov ecx,11h 11 00401031 mov eax,0CCCCCCCCh 12 00401036 rep stos dword ptr [edi] 13 8: float a = 8.25f; 14 00401038 mov dword ptr [ebp-4],41040000h 15 9: } 16 0040103F pop edi 17 00401040 pop esi 18 00401041 pop ebx 19 00401042 mov esp,ebp 20 00401044 pop ebp 21 00401045 ret
通過反匯編查看得知,壓入棧中的8.25,是0x41040000
例子2
既然8.25的會了,那么-8.25呢?如何表示呢?
很簡單,首先將-8.25的絕對值用二進制表示出來就行了!跟8.25表示形式一模一樣,只不過第31位為1,因為是負數,所以-8.25的最終二進制表示為0xC1040000,我們反匯編查看一下。看是不是這個呢?
經過反匯編觀察,發現與我們計算結果一致吧~~~~~
例子3
如何將一個只有小數部分的數用二進制來表示呢?(這也是一個難點,與有整數部分不一樣的是,只有小數部分的數,通常它的科學計數法表示中指數為負),我們還沒有探討過指數為負的情況!
我以0.25為例!
0.25的二進制表示為:
0.25 × 2 = 0.5 取整數部分為 0
0.5 × 2 = 0.5 取整數部分為 1
(1)0.25用二進制表示為0.01,用科學計數法來表示為1 × 2的負2次方,0.25為正數,因此第31為為0,指數n向右得到,因此第30為為0,指數n(-2)減1得到-3,-3用32位來表示是0xFFFFFFFD,FD為1111 1101,從最低位開始數7個數出來放入23到29中
(2)尾數部分,0.01的科學計數法為1.0 × 2的負2次方,小數點后面為0,因此,22 -0位中全部添0即可,最終表示為0011 1110 1000 0000 0000 0000 0000 0000,用十六進制表示為0x3E800000,我們反匯編觀察看看
例子4
-0.25呢?如何表示呢?
0xBE800000,讀者自己去試試吧,其實也不用試,十分簡單,我剛才不是算了0.25的了嗎?只需要把0.25的二進制表示復制過來,然后第31位改成1就是-0.25的值了~~~~
float與double
float在內存中占4字節,double在內存中占8個字節,如果讀者您弄懂了上面的過程的話,相信你也一定明白了double的存儲過程,double存儲中提供了更多的尾數部分,這為小數點后面的精確度提供了更多的位數!使值更加精確,就像是99.99%與99.9999999999999999999999999999999999%的區別,就像是買黃金,如果只是商品交易,那么99.99%那么完全夠用了,因為99.99%它就可以說成是24K純金,但是科學實驗中呢?99.99%純嗎?它肯定沒有99.9999999999999999999999999999999999%純度高對吧!因為后者表示的精度更高,在平時中的程序中,float代表的精度其實已經足夠我們使用,因此,在平時代碼編寫中,float定義小數就足夠了,及其特殊情況下我們采用double,明白了小數在計算機中是如何存數的了嗎?