整數、浮點數在電腦中的存儲
- 2019 年 10 月 8 日
- 筆記
一、簡述
1.1 電腦底層存儲數據的基本原理
電腦要處理的資訊是多種多樣的,如數字、文字、符號、圖形、音頻、影片等,這些資訊在人們的眼裡是不同的。但對於電腦來說,它們在記憶體中都是一樣的,都是以二進位的形式來表示。要想學習編程,就必須了解二進位,它是電腦處理數據的基礎。
記憶體條是一個非常精密的部件,包含了上億個電子元器件,它們很小,達到了納米級別。這些元器件,實際上就是電路;電路的電壓會變化,要麼是 0V,要麼是 5V,只有這兩種電壓。5V 是通電,用1來表示,0V 是斷電,用0來表示。所以,一個元器件有2種狀態,0 或者 1。我們通過電路來控制這些元器件的通斷電,會得到很多0、1的組合。例如,8個元器件有 28=256 種不同的組合,16個元器件有 216=65536 種不同的組合。雖然一個元器件只能表示2個數值,但是多個結合起來就可以表示很多數值了。
我們可以給每一種組合賦予特定的含義,例如,可以分別用 1101000、00011100、11111111、00000000、01010101、10101010 來表示 C、語、言、中、文、網 這幾個字,那麼結合起來 1101000 00011100 11111111 00000000 01010101 10101010 就表示」C語言中文網「。一般情況下我們不一個一個的使用元器件,而是將8個元器件看做一個單位,即使表示很小的數,例如 1,也需要8個,也就是 00000001。1個元器件稱為1比特(Bit)或1位,8個元器件稱為1位元組(Byte),那麼16個元器件就是2Byte,32個就是4Byte,以此類推。我們平時使用電腦時,通常只會設計到 KB、MB、GB、TB 這幾個單位,PB 和 EB 這兩個高級單位一般在大數據處理過程中才會用到。
- 1Byte = 8 Bit
- 1KB = 1024Byte = 2^10Byte
- 1MB = 1024KB = 2^20Byte
- 1GB = 1024MB = 2^30Byte
- 1TB = 1024GB = 2^40Byte
- 1PB = 1024TB = 2^50Byte
- 1EB = 1024PB = 2^60Byte
所以,在記憶體中沒有abc這樣的字元,也沒有gif、jpg這樣的圖片,只有0和1兩個數字,電腦也只認識0和1。所以,電腦使用二進位,而不是我們熟悉的十進位,寫入記憶體中的數據,都會被轉換成0和1的組合。
1.2 數據的類型
數據的類型有很多,不同的程式語言會將數據的類型分為不同的類別。但是一般的分類是兩大類:基本數據類型和引用類型。
- 基本數據類型:直接存儲數值。一般有:整型(byte / short / int / long)、浮點型(float / double)、布爾型(boolean)和字元型(char)。
- 引用類型:存儲的是地址,數組、字元串、結構體、對象等
二、整數的存儲
2.1 整數的基本概念
大家知道,整數包括負數,零,和正數。電腦中的整數分為有符號數和無符號數。
- 有符號數:最高位表示符號,即最高位為0,表示正數,最高位為1,表示負數。如果用N位來表示整數,那麼有符號數的範圍為:[-2^(N-1),(2^(N-1))-1]。用8位來表示有符號整數數,由於第8位用於表示了符號,因此,整數的表示範圍為[-128,+127]。
- 無符號數:表示非負數,整個位數都用來表示整數的值。如果用N位來表示整數,無符號數的表示範圍為[0,(2^N)-1]。用8位來表示有符號整數數,則無符號數的表示範圍為[0,255]。
2.2 整數的編碼方式
整數的編碼分為原碼、反碼、和補碼。計算里使用的是補碼的存儲方式。它們的定義如下:
- 原碼:在數值前面增加了一位符號位(即最高位為符號位),該位為0表示正數,該位為1表示負數,其餘位表示數值的大小。
- 反碼:正數的反碼與其原碼相同。負數的反碼是對其原碼逐位取反,但符號位除外。
- 補碼:正數的補碼與其原碼相同,負數的補碼就是對該負數的反碼加1。
因為電腦是以補碼來存儲整數的,所以補碼就顯得很重要。那麼如何計算整數的補碼呢?下面以具體例子來說明。
100 的補碼:01100100 0 的補碼:0 -100 的補碼:絕對值:01100100 –>取反加1:10011011+1 –>10011100 1 的補碼:00000001 -1 的補碼:絕對值:00000001 –>取反加1:111111110+1 –>11111111 127 的補碼:01111111 -128 的補碼:絕對值:10000000 –>取反加1:01111111+1 –>10000000 在電腦系統中,數值一律用補碼來表示(存儲)。

從定義可以看出,正數的補碼,反碼,原碼相同。0的補碼就是本身。那麼負數的原碼和補碼如何轉換呢?已知一個負數求補碼方法:絕對值原碼按位求反加1。已知負數補碼求負數方法:符號位不變,其他位按位求反加1。對於8位整數來說,補碼的表示範圍為[-128,127]。 大家應該記住一些常見的補碼的表示,這些數包括但不局限於下面表中列出的數:

那麼有了原碼,電腦為什麼還要用補碼呢?
來看看它們的運算情況。 假設字長為8位 ,那麼原碼的運算方式為: 1 – 1 = 1 + ( -1 ) =(00000001) + (10000001) = (10000010) = -2 ,這顯然不正確。原碼在兩個整數的加法運算中是沒有問題的,問題出現在帶符號位的負數身上。 原碼無法滿足運算要求,因此對除符號位外的其餘各位逐位取反就產生了反碼。反碼的取值空間和原碼相同且一一對應。下面是反碼的減法運算: 1 – 1 = 1 + ( -1 )= (00000001) + (11111110) = (11111111) = ( -0 ) 沒問題。 1 – 2 = 1 + ( -2 ) = (00000001) + (11111101) = (11111110) = ( -1 ) 正確。反碼的問題出現在(+0)和(-0)上,因為在人們的計算概念中零是沒有正負之分的。 再來看補碼的加減運算如下: 1 – 1 = 1 + (-1) = (00000001) + (11111111) = (00000000) = 0 正確。 1 – 2 = 1 + (-2) = (00000001) + (11111110) = (11111111) = ( -1 ) 正確。
通過補碼的運算,可以看出補碼的設計目的是:
- 使符號位能與有效值部分一起參加運算,從而簡化運算規則。
- 使減法運算轉換為加法運算,進一步簡化電腦中運算器的線路設計。
- 此外,在補碼中用-128代替了-0,所以沒有+0和-0之分,符合常理,所以補碼的表示範圍為: -128~0~127共256個。
注意:-128沒有相對應的原碼和反碼,-128的補碼為:10000000。
三、浮點數的存儲
一般的程式語言都是將浮點類型的數據採用單精度類型( float)和雙精度類型(double)來存儲,float 數據佔用 32bit,double 數據佔用 64bit,我們在聲明一個變數 float f= 2.25f; 的時候,是如何分配記憶體的呢?如果胡亂分配,那世界豈不是亂套了么,其實不論是 float 還是 double 在存儲方式上都是遵從 IEEE 的規範的, float 遵從的是 IEEE R32.24 ,而 double 遵從的是 R64.53。無論是單精度還是雙精度在存儲中都分為三個部分:
- 浮點數表示的數值:V = (-1)^s × M × 2^E
- 符號(sign) :1個bit表示,當s=0,V為正數;當s=1,V為負數。
- 階碼(exponent) :E的作用是對浮點數加權,用於存儲科學計數法中的指數數據,並且採用移位存儲。float類型的階碼是 8 bits,double類型的階碼是 11 bits。
- 尾數(significand) :M是一個二進位小數,因為是二進位,所以科學計數法中這個值範圍是:1≤M<2。(和十進位中範圍為1~10一樣)
R32.24 和 R64.53 的存儲方式都是用科學計數法來存儲數據的。比如 8.25 用十進位的科學計數法表示就為:8.25*10^1 ,而 120.5 可以表示為:1.205*10^2 ,這些小學的知識就不用多說了吧。而我們的傻蛋電腦根本不認識十進位的數據,他只認識 0, 1,所以在電腦存儲中,首先要將上面的數更改為二進位的科學計數法表示, 8.25 用二進位表示可表示為 1000.01,大家不會連這都不會轉換吧?那我估計要沒轍了。 120.5 用二進位表示為:1110110.1 用二進位的科學計數法表示 1000.01 可以表示為 1.0001* 2^3,1110110.1可以表示為 1.1101101* 2^6。

IEEE 754對有效數字M和指數E,還有一些特別規定。前面說過,1≤M<2,也就是說,M可以寫成1.xxxxxx的形式,其中xxxxxx表示小數部分。IEEE 754規定,在電腦內部保存 M時,默認這個數的第一位總是1,因此可以被捨去,只保存後面的xxxxxx部分。比如保存1.01的時候,只保存01,等到讀取的時候,再把第一位的1加上去。這樣做的目的,是節省1位有效數字。以32位float浮點數為例,留給M只有23位,將第一位的1捨去以後,等於可以保存24位有效數字。 道理就是在這裡,那 24bit 能精確到小數點後幾位呢,我們知道 9 的二進位表示為 1001,所以 4bit 能精確十進位中的 1 位小數點, 24bit 就能使 float 能精確到小數點後 6 位。
至於指數E,情況就比較複雜。 首先,E為一個無符號整數(unsigned int)這意味著,如果E為8位 (float類型) ,它的取值範圍為0~255;如果E為11位(double類型),它的取值範圍 為0~2047。但是,我們知道,科學計數法中的E是可以出現負數的(因為0.75用科學計數法表示就是1.1*2^-1),所以IEEE 754規定,存入記憶體時E的真實值必須再加上一個中間數,對於8位的E,這個中間數是127;對於11位的E,這個中間數是1023。比如,2^10的E是10,所以保存成32位浮點數時,必須保存成10+127=137,即10001001。
接下來我們看下 8.25用float類型存儲的數據到底是什麼樣的?8.25f用二進位的科學計數法表示為:1.0001*2^3,按照上面的存儲方式,符號位s = 0,表示為正;指數位 E = 3+127=130 ,尾數部分為1.0001,去掉最前面的整數1,就是M = 0001,所以8.25f用float類型在記憶體中存儲的格式就是:
