轉載於http://blog.163.com/yql_bl/blog/static/847851692008112013117685/
因為要參加軟考了(當然也只有考試有這種魅力),我得了概浮點數轉化為二進制表示這個最難的知識點(個人認為最難)。俺結合大量的從網上收集而來的資料現整理如下,希望對此知識點感興趣的pfan有所幫助。
基礎知識:
十進制轉十六進制;
十六進制轉二進制;
IEEE制定的浮點數表示規則;
了解:
目前C/C++編譯器標准都遵照IEEE制定的浮點數表示法來進行float,double運算。這種結構是一種科學計數法,用符號、指數和尾數來表示,底數定為2——即把一個浮點數表示為尾數乘以2的指數次方再添上符號。下面是具體的規格:
符號位 階碼 尾數 長度
float 1 8 23 32
double 1 11 52 64
以下通過幾個例子講解浮點數如何轉換為二進制數
例一:
已知:double類型38414.4。
求:其對應的二進制表示。
分析:double類型共計64位,折合8字節。由最高到最低位分別是第63、62、61、……、0位:
最高位63位是符號位,1表示該數為負,0表示該數為正;
62-52位,一共11位是指數位;
51-0位,一共52位是尾數位。
步驟:按照IEEE浮點數表示法,下面先把38414.4轉換為十六進制數。
把整數部和小數部分開處理:整數部直接化十六進制:960E。小數的處理:
0.4=0.5*0+0.25*1+0.125*1+0.0625*0+……
實際上這永遠算不完!這就是著名的浮點數精度問題。所以直到加上前面的整數部分算夠53位就行了。隱藏位技術:最高位的1不寫入內存(最終保留下來的還是52位)。
如果你夠耐心,手工算到53位那么因該是:38414.4(10)=1001011000001110.0110011001100110011001100110011001100(2)
科學記數法為:1.001011000001110 0110011001100110011001100110011001100,右移了15位,所以指數為15。或者可以如下理解:
1.001011000001110 0110011001100110011001100110011001100×2^15
於是來看階碼,按IEEE標准一共11位,可以表示范圍是-1024 ~ 1023。因為指數可以為負,為了便於計算,規定都先加上1023(2^10-1),在這里,階碼:15+1023=1038。二進制表示為:100 00001110;
符號位:因為38414.4為正對應 為0;
合在一起(注:尾數二進制最高位的1不要):
01000000 11100010 11000001 110 01100 11001100 11001100 11001100 11001100
例二:
已知:整數3490593(16進制表示為0x354321)。
求:其對應的浮點數3490593.0的二進制表示。
解法如下:
先求出整數3490593的二進制表示:
H: 3 5 4 3 2 1 (十六進制表示)
B: 0011 0101 0100 0011 0010 0001 (二進制表示)
│←───── 21────→│
即:
1.1010101000011001000012×221
可見,從左算起第一個1后有21位,我們將這21為作為浮點數的小數表示,單精度浮點數float由符號位1位,指數域位k=8位,小數域位(尾數)n=23位構成,因此對上面得到的21位小數位我們還需要補上2個0,得到浮點數的小數域表示為:
1 0101 0100 0011 0010 0001 00
float類型的偏置量Bias=2k-1-1=28-1-1=127,但還要補上剛才因為右移作為小數部分的21位,因此偏置量為127+21=148,就是IEEE浮點數表示標准:
V = (-1)s×M×2E
E = e-Bias
中的e,此前計算Bias=127,剛好驗證了E=148-127=21。
將148轉為二進制表示為10010100,加上符號位0,最后得到二進制浮點數表示1001010010101010000110010000100,其16進制表示為:
H: 4 A 5 5 0 C 8 4
B: 0100 1010 0101 0101 0000 1100 1000 0100
|←──── 21 ─────→ |
1|←─8 ─→||←───── 23 ─────→ |
這就是浮點數3490593.0(0x4A550C84)的二進制表示。
例三:
0.5的二進制形式是0.1
它用浮點數的形式寫出來是如下格式
0 01111110 00000000000000000000000
符號位 階碼 小數位
正數符號位為0,負數符號位為1
階碼是以2為底的指數
小數位表示小數點后面的數字
下面我們來分析一下0.5是如何寫成0 01111110 00000000000000000000000
首先0.5是正數所以符號位為0
再來看階碼部分,0.5的二進制數是0.1,而0.1是1.0*2^(-1),所以我們總結出來:
要把二進制數變成(1.f)*2^(exponent)的形式,其中exponent是指數
而由於階碼有正負之分所以階碼=127+exponent;
即階碼=127+(-1)=126 即 01111110
余下的小數位為二進制小數點后面的數字,即00000000000000000000000
由以上分析得0.5的浮點數存儲形式為0 01111110 00000000000000000000000
注:如果只有小數部分,那么需要右移小數點. 比如右移3位才能放到第一個1的后面, 階碼就是127-3=124.
例四 (20.59375)10 =(10100.10011 )2
首先分別將整數和分數部分轉換成二進制數:
20.59375=10100.10011
然后移動小數點,使其在第1,2位之間
10100.10011=1.010010011×2^4 即e=4
於是得到:
S=0, E=4+127=131, M=010010011 [感覺有錯誤!!!!]
最后得到32位浮點數的二進制存儲格式為:
0100 1001 1010 0100 1100 0000 0000 0000=(41A4C000)16
例五:
-12.5轉為單精度二進制表示
12.5:
1. 整數部分12,二進制為1100; 小數部分0.5, 二進制是.1,先把他們連起來,從第一個1數起取24位(后面補0):
1100.10000000000000000000
這部分是有效數字。(把小數點前后兩部分連起來再取掉頭前的1,就是尾數)
2. 把小數點移到第一個1的后面,需要左移3位(1.10010000000000000000000*2^3), 加上偏移量127:127+3=130,二進制是10000010,這是階碼。
3. -12.5是負數,所以符號位是1。把符號位,階碼和尾數連起來。注意,尾數的第一位總是1,所以規定不存這一位的1,只取后23位:
1 10000010 10010000000000000000000
把這32位按8位一節整理一下,得:
11000001 01001000 00000000 00000000
就是十六進制的 C1480000.
例六:
2.025675
1. 整數部分2,二進制為10; 小數部分0.025675, 二進制是.0000011010010010101001,先把他們連起來,從第一個1數起取24位(后面補0):
10.0000011010010010101001
這部分是有效數字。把小數點前后兩部分連起來再取掉頭前的1,就是尾數: 00000011010010010101001
2. 把小數點移到第一個1的后面,左移了1位, 加上偏移量127:127+1=128,二進制是10000000,這是階碼。
3. 2.025675是正數,所以符號位是0。把符號位,階碼和尾數連起來:
0 10000000 00000011010010010101001
把這32位按8位一節整理一下,得:
01000000 00000001 10100100 10101001
就是十六進制的 4001A4A9.
例七:
(逆向求十進制整數)一個浮點二進制數手工轉換成十進制數的例子:
假設浮點二進制數是 1011 1101 0100 0000 0000 0000 0000 0000
按1,8,23位分成三段:
1 01111010 10000000000000000000000
最后一段是尾數。前面加上"1.", 就是 1.10000000000000000000000
下面確定小數點位置。由E = e-Bias,階碼E是01111010,加上00000101才是01111111(127),
所以他減去127的偏移量得e=-5。(或者化成十進制得122,122-127=-5)。
因此尾數1.10(后面的0不寫了)是小數點右移5位的結果。要復原它就要左移5位小數點,得0.0000110, 即十進制的0.046875 。
最后是符號:1代表負數,所以最后的結果是 -0.046875 。
注意:其他機器的浮點數表示方法可能與此不同. 不能任意移植。
再看一例(類似例七):
比如:53004d3e
二進制表示為:
01010011000000000100110100111110
按照1個符號 8個指數 23個小數位划分
0 10100110 00000000100110100111110
正確的結果轉出來應該是551051722752.0
該怎么算?
好,我們根據IEEE的浮點數表示規則划分,得到這個浮點數的小數位是:
00000000100110100111110
那么它的二進制表示就應該是:
1.000000001001101001111102 × 239
這是怎么來的呢? 別急,聽我慢慢道來。
標准化公式中的M要求在規格化的情況下,取值范圍1<M<(2-ε)
正因為如此,我們才需要對原始的整數二進制表示做偏移,偏移多少呢?偏移2E。
這個“E”怎么算?上面的239怎么得來的呢?浮點數表示中的8位指數為就是告訴這個的。我們知道:
E = e-Bias
那么根據指數位:
101001102=>16610
即e=166,由此算出E=e-Bias=166-127=39,就是說將整數二進制表示轉為標准的浮點數二進制表示的時候需要將小數點左移39位,好,我們現在把它還原得到整數的二進制表示:
1 00000000100110100111110 0000000000000000
1│←───── 23─────→│← 16─→│
23+16=39,后面接着就是小數點了。
拿出計算器,輸入二進制數1000000001001101001111100000000000000000
轉為十進制數,不正是:551051722752么!
通過這例六例七,介紹了將整數二進制表示轉浮點數二進制表示的逆過程,還是希望大家不但能掌握轉化的方法,更要理解轉化的基本原理。
浮點數在計算機中存儲方式
作者: jillzhang
聯系方式:jillzhang@126.com http://www.cnblogs.com/jillzhang/archive/2007/06/24/793901.html
C語言和C#語言中,對於浮點類型的數據采用單精度類型(float)和雙精度類型(double)來存儲,float數據占用32bit,double數據占用64bit,我們在聲明一個變量float f= 2.25f的時候,是如何分配內存的呢?如果胡亂分配,那世界豈不是亂套了么,其實不論是float還是double在存儲方式上都是遵從IEEE的規范的,float遵從的是IEEE R32.24 ,而double 遵從的是R64.53。
無論是單精度還是雙精度在存儲中都分為三個部分:
- 符號位(Sign) : 0代表正,1代表為負
- 指數位(Exponent):用於存儲科學計數法中的指數數據,並且采用移位存儲
- 尾數部分(Mantissa):尾數部分
其中float的存儲方式如下圖所示:

而雙精度的存儲方式為:

R32.24和R64.53的存儲方式都是用科學計數法來存儲數據的,比如8.25用十進制的科學計數法表示就為:8.25*
,而120.5可以表示為:1.205*
,這些小學的知識就不用多說了吧。而我們傻蛋計算機根本不認識十進制的數據,他只認識0,1,所以在計算機存儲中,首先要將上面的數更改為二進制的科學計數法表示,8.25用二進制表示可表示為1000.01,我靠,不會連這都不會轉換吧?那我估計要沒轍了。120.5用二進制表示為:1110110.1用二進制的科學計數法表示1000.01可以表示為1.0001*
,1110110.1可以表示為1.1101101*
,任何一個數都的科學計數法表示都為1.xxx*
,尾數部分就可以表示為xxxx,第一位都是1嘛,干嘛還要表示呀?可以將小數點前面的1省略,所以23bit的尾數部分,可以表示的精度卻變成了24bit,道理就是在這里,那24bit能精確到小數點后幾位呢,我們知道9的二進制表示為1001,所以4bit能精確十進制中的1位小數點,24bit就能使float能精確到小數點后6位,而對於指數部分,因為指數可正可負,8位的指數位能表示的指數范圍就應該為:-127-128了,所以指數部分的存儲采用移位存儲,存儲的數據為元數據+127,下面就看看8.25和120.5在內存中真正的存儲方式。
首先看下8.25,用二進制的科學計數法表示為:1.0001*![]()
按照上面的存儲方式,符號位為:0,表示為正,指數位為:3+127=130 ,位數部分為,故8.25的存儲方式如下圖所示:

而單精度浮點數120.5的存儲方式如下圖所示:

那么如果給出內存中一段數據,並且告訴你是單精度存儲的話,你如何知道該數據的十進制數值呢?其實就是對上面的反推過程,比如給出如下內存數據:0100001011101101000000000000,首先我們現將該數據分段,0 10000 0101 110 1101 0000 0000 0000 0000,在內存中的存儲就為下圖所示:
![]()
根據我們的計算方式,可以計算出,這樣一組數據表示為:1.1101101*
=120.5
而雙精度浮點數的存儲和單精度的存儲大同小異,不同的是指數部分和尾數部分的位數。所以這里不再詳細的介紹雙精度的存儲方式了,只將120.5的最后存儲方式圖給出,大家可以仔細想想為何是這樣子的
![]()
下面我就這個基礎知識點來解決一個我們的一個疑惑,請看下面一段程序,注意觀察輸出結果
float f = 2.2f;
double d = (double)f;
Console.WriteLine(d.ToString("0.0000000000000"));
f = 2.25f;
d = (double)f;
Console.WriteLine(d.ToString("0.0000000000000"));
可能輸出的結果讓大家疑惑不解,單精度的2.2轉換為雙精度后,精確到小數點后13位后變為了2.2000000476837,而單精度的2.25轉換為雙精度后,變為了2.2500000000000,為何2.2在轉換后的數值更改了而2.25卻沒有更改呢?很奇怪吧?其實通過上面關於兩種存儲結果的介紹,我們已經大概能找到答案。首先我們看看2.25的單精度存儲方式,很簡單 0 1000 0001 001 0000 0000 0000 0000 0000,而2.25的雙精度表示為:0 100 0000 0001 0010 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000,這樣2.25在進行強制轉換的時候,數值是不會變的,而我們再看看2.2呢,2.2用科學計數法表示應該為:將十進制的小數轉換為二進制的小數的方法為將小數*2,取整數部分,所以0.282=0.4,所以二進制小數第一位為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,這樣永遠也不可能乘到=1.0,得到的二進制是一個無限循環的排列 00110011001100110011... ,對於單精度數據來說,尾數只能表示24bit的精度,所以2.2的float存儲為:
![]()
但是這樣存儲方式,換算成十進制的值,卻不會是2.2的,應為十進制在轉換為二進制的時候可能會不准確,如2.2,而double類型的數據也存在同樣的問題,所以在浮點數表示中會產生些許的誤差,在單精度轉換為雙精度的時候,也會存在誤差的問題,對於能夠用二進制表示的十進制數據,如2.25,這個誤差就會不存在,所以會出現上面比較奇怪的輸出結果。
