計算機中浮點數的表示
大二學的計算機組成原理,回顧下其中的知識
一個加法引發的思考
上次在一個論壇裡看到了Java裡的一些不正常的操作,比如說0.05+0.01的結果居然不是等於0.06,截圖為證:(雖然說在學計算機組成原理的時候貌似也碰到過,但是那時並沒有深入研究)
於是我便就此問題進行深入的探討,不過卻發現要計算機組成原理的相關知識,大二學的計算機組成原理,現在基本上全部還給老師了,於是重新回顧下計算機組成原理裡相關的知識。
計算機中的原碼、反碼、補碼、移碼
原碼
在計算機中增加了一個符號位來表示數字的正負值,符號位為0表示正數,符號位為1表示負數,是在其數值的基礎上加了符號位,舉個例子:
- 123的原碼為:0111 1011,第一個0表示符號位,後面的就是其二進位制數值。
- -123的原碼為:1111 1011,第一個1表示符號位,後面的就是其二進位制數值。
早期就是用原碼來表示數字的,但是這樣會出問題,舉個例子,假如用8位二進位制數來表示一個數,那麼1的原碼為 0000 0001,那麼-1的原碼為 1000 0001,正常來說 1+(-1) = 0,但是如果用原碼來進行相加的話,結果就是 1000 0010 ,用原碼來表示也就是-2,和我們正確的結果不一樣,這就是原碼在計算上的缺點。同樣用原碼來表示數會出現+0和-0的情況,但是我們的0只有一個。
反碼
反碼也就是在原碼的基礎上做了一些改變,正數的反碼就是其原碼,負數的反碼就是其原碼除了符號位之後各個位取反。舉個例子:
- 123的反碼為:0111 1000,也就是其原碼。
- -123的反碼為:1000 0111,也就是除了符號位之外,每一位取反。
補碼
補碼是在反碼的基礎上增加了一些規則,正數的補碼是其本身,也就是其原碼,而負數的補碼也就是除符號位之外,其餘各位取反,取反之後的結果加一(負數的補碼相當於在其反碼之後加一)。舉個例子:
- 123的補碼為:0111 1000,也就是其原碼。
- -123的補碼為:1000 1000,也就是其反碼加一。
移碼
移碼的計算是在補碼的符號位上進行取反,舉個例子:
- 123的移碼為:1111 1000。
- -123的移碼為:0000 1000。
為什麼計算機中要用這些來表示數?
計算機不能夠像人一樣直接識別10進位制的數字,像1、2、3、4、5、6等,計算機中只能識別二進位制的資料,所以計算機中的資料儲存是以二進位制為基礎的,既然這樣,那麼就涉及到二進位制資料的加減乘除,那麼計算機中是如何進行處理的呢?
我們還是回到剛才討論的1+(-1)的問題,也就是1-1的問題,如果原碼錶示就會出現0000 0001+1000 0001 = 1000 0010,從而得出-2的結果,這顯然是不正確的,所以我們引入了反碼,將二者的反碼相加,0000 0001+1111 1110 = 1111 1111,然後將結果從反碼轉換為原碼,也就是1000 0000,也就是-0,這樣解決了原碼減法的問題,但是還是存在問題,也就是+0和-0,在人類的認知中+0和-0是一樣的,所以在計算機中也只能有一種表示。
由於反碼無法解決+0和-0的問題,我們才引入補碼的概念,在補碼裡,+0的補碼為,0000 0000,-0的補碼為(1000 0000)原碼 ——>(1111 1111)反碼 ——>(0000 0000)補碼,從其反碼到補碼的過程由於加一之後溢位,而8位二進位制數只能儲存8位,所以溢位的丟棄,所以在補碼裡,+0,和-0的補碼是一樣的。
值得一提的是幾個特殊數的補碼,-128的補碼是 1000 0000,而我們知道原碼錶示的時候,最高位是要用作符號位的,所以8位二進位制數的原碼錶示範圍為-(27-1)~(27-1)也就是(-127~+127),所以-128的原碼是無法直接寫出來的,既然這樣,那-128為什麼還有補碼呢?
在解決上面這個問題之前,我們先來了解一下“同餘定理“的概念,假如有兩個數a和b,如果(a-b)能夠被m整除,那麼就稱a和b對模m同餘,這就是同餘定理。現在我們回到之前,補碼的提出是為了解決兩個問題,第一個是計算機裡的減法,將減法變成加法來做;第二個問題就是+0和-0的解決。而補碼的提出就是為了讓負數變成能夠加的正數。這裡我們舉個例子,我們知道那種掛鐘是以12為單位的,如果現在是8點,我們想將其變成5點,有兩種方式,第一種是順時針撥動9格,另外一種是逆時針撥動3格。逆時針撥動3格很容易理解,就是8-3=5,而順時針撥動9格即是8+9 = 8+(12-3) = 17=12+5。兩種方式都能夠得到結果,但是第一種方法採取的是化減為加的方法。
那麼我們可以借鑑這樣的思路來實現計算機中的減法變加法,通過以上的介紹我們很容易知道8位二進位制的模為256,因為滿256就進位。那麼,我們來計算1+(-2)的時候,就可以計算1+(256-2)=255,而255的表示為1111 1111,剛好就是結果-1的補碼((-1)——>(1000 0001)原碼 ——>(1111 1110)反碼 ——>(1111 1111)補碼)。
那麼也就是說,求一個數的補碼還可以這樣求,一個數的補碼 = 模-這個數的絕對值。既然如此,我們就可以求-128的補碼,-128的補碼等於256-128,而256-128得到的結果是128,也就是(1000 0000),所以-128的補碼是(1000 0000)。而我們知道,能夠通過一個數的補碼從而求其原碼,方法是符號位不變,其他位取反,然後末尾加一,當對符號位有進位時,進位忽略(當然這是針對符號位為 1的,若符號位為0,那麼原碼即是補碼)。既然如此那麼我們根據-128的補碼求其原碼,(1000 0000)補碼 ——>取反(1111 1111)加一(1000 0000)原碼也就是說,-128的原碼是-0,不過的確是這樣,因為在補碼裡+0和-0的補碼是一樣的,所以選擇其中一個表示0,當然+0就被選上了,那麼多出的一個自然就被用作-128,所以8位二進位制數的補碼錶示的數字要比原碼和反碼多一個-128。
所以我們來做一個小總結:(基於8位二進位制數)
- -128的補碼是[1000 0000],沒有原碼(根據補碼求得的原碼在原碼看來是-0),沒有反碼。
- +128在8位二進位制數中是沒有原碼、反碼、補碼。
- +1的原碼是[0000 0001],反碼是[0111 1110],補碼是[0000 0001]。
- -1的原碼是[1000 0001],反碼是[1111 1110],補碼是[1111 1111]。
- 0的原碼是[0000 0000],反碼是[0111 1111],補碼是[0000 0000]。
計算機中如何表示小數?
在計算機組成原理這門課中我們學過,浮點數的表示有分為32位浮點數的表示和64位浮點數的表示,他們的表示方法如下圖:
上面的兩張圖分別表示的是32位浮點數在計算機中的表示和64位浮點數在計算機中的表示,這裡的階碼就類似我們使用科學計數法的指數,舉個例子,123我們用科學計數法表示為1.23×102在這裡2就是階數,而我們在利用科學計數法進行加減的時候是這樣進行的,比如說1.23×102+2.556×103,我們的做法是通過移動小數點將它們的階數變成一致,然後再進行相加。而我們通常會把小的階數轉化成大的階數,然後再進行相加,所以轉化成0.123×103+2.556×103=2.679×103。
而在計算機中同樣如此,我們之前討論過了,在計算機中是以補碼的形式進行加減操作的,所以如果階碼也是用補碼的話就造成了一個問題?上面我們說過我們在進行數字的相加的時候要進行對階操作,也就是將階數小的轉化成階數大的(這裡可能有同學會問為什麼不將大階對小階,你可以試一下,原因是大階對小階造成的數字丟失要比小階對大階要大)。那這樣的話,首先要進行階數的比較,不然怎麼知道誰大誰小,如果階碼採取補碼的方式,那麼在一個浮點數裡就有兩個符號位,這樣不便直接通過無邏輯的二進位制進行比較,所以階碼這裡不能採取有符號數,只能採取無符號數,既然這樣我們為什麼要用移碼來表示階碼呢?
這樣,我們先來提出機器零的概念,在計算機中是不能夠準確的表示自然界中的0的,只能表示一個比較接近於0的數,所以在計算機中,當一個數小於計算機能夠表示最小數的時候,計算機就認為這個數為0,這就是機器零,而我們知道浮點數的表示方法(32位浮點數)為(-1)S1.M×2E-127 ,其中S位符號位,M位尾數,E為階碼。所以當尾數全為0的時候不論階碼多少,這個數為0,而當階碼小於能表示的範圍的最小值的時候,不論尾數為多少,這個數為0(這是因為當階碼小於能夠表示範圍的最小值的時候,那麼整個數也就小於能夠表示範圍的最小值了,自然也就當0看待,那麼就不用了管尾數的多少了)。
弄懂了機器零的概念,我們再回到上面討論的用移碼來表示階碼的問題。我們知道機器0在二進位制的表示上是不為0的,只是在概念上為0,那麼為了使機器零在表示上為0,我們引入了移碼,移碼在原來補碼的基礎上符號位取反,相當於是最高位加1,等同於整個數在原來的基礎上加了2n-1其中n表示的是二進位制數的位數。那也就是說在32位浮點數的裡原來的數加上128就能夠得到階碼了?實際上不是128而是127,那這又是什麼原因呢?如果單純的按照移碼的定義,那麼偏置為128能夠表示的數的範圍為-128~127,但是要注意0000 0000和1111 1111,這兩個數分別表示非規格化數和無窮大,也就是兩個邊界狀態,那也就是說我們實際的移碼範圍是0000 0001到1111 1110,也就是1到254,如果採取偏置為128那麼,表示的範圍是-127到126,和原來的範圍相比左右各收縮一個單位,如果偏置為127,那麼表示的範圍是-126到127,和原來的範圍相比,負數表達的範圍小了,正數表達的範圍沒變,我們知道,在計算機中一個更大的數的表示比一個更小的數的表示要重要,所以在這裡我們的偏置取127,也就是2n-1-1,那麼在64位浮點數裡偏置自然也就是1023了。
浮點數的轉換以及加減
通過上面知識的回顧,我們來幾道題目練下手,加深印象。
- (-0.75)10轉化成IEEE 754單精度浮點表示
- 求 C0A00000H對應的IEEE 754單精度浮點數的值為多少?
- 計算兩浮點數的和,均是10進位制,0.5和-0.4375之和。(單精度浮點數)
回到本文開頭提出的問題
那麼我們只需要將0.05和0.01分別用64為浮點數表示,之後再進行相加即可,步驟如下:
在將0.05化成二進位制小數的時候採用瞭如下Java程式輔助計算:
在此之後筆者又找到這樣的例子,並用剛才的方法進行驗證,最終得出正確的結果,如下:
一些規則
- 進行浮點數加減的時候首先是對階,對階是小階對大階,小階差大階多少,小階的尾數向右移多少,在右移的時候,最高位1被移出,最高位補0。
- 進行尾數相加的時候是補碼相加,IEEE 754裡尾數是原碼錶示的,所以我們要化成補碼然後相加,正數的補碼即其本身,負數的補碼符號位不變,其它位取反,最後再加1。而且在尾數相加的時候採取的是二位符號位,00表示正數,11表示負數。(注意:這裡的尾數是要包含隱藏位的)
- 當尾數相加的時候,溢位了(符號位出現01或者10的時候),當符號位為01的時候表示上溢,符號位以0為準,為正值,不過溢位之後要做右規操作,然後做舍入操作,舍入操作即是0舍1入。
- 當尾數相加,符號位為10的時候表示下溢,符號位以1為準,為負值,溢位之後做右規操作,然後做舍入操作,舍入操作即是0舍1入。
- 當尾數相加沒有出現溢位的時候,此時如果最高位和符號位相同,那麼需要對尾數做左規處理,直到最高位與符號位數字不同。