計算機是如何存儲數字的

前言

int a=12;
int b=1.234;

問:a 和 b 在計算機中到底是如何存儲的?

答:轉為二進制?

問:轉成二進制就直接存儲了?

答:...

問:小數 b 如何轉成二進制的?

答:...

我們日常更多的時候都在使用十進制的數,要知道我們的祖先可厲害了,應該很長一段時間都在使用 16 進制。譬如一個成語叫“半斤八兩”,解釋:半斤、八兩輕重相等,比喻彼此不相上下。等等!半斤和八兩怎么相等,對的,宋代一斤就是等于十六兩,當然半斤等于八兩。

都知道計算機使用的是二進制,只用 0 和 1 就能表示數字,但是計算機到底是如何解決數字存儲的問題?下面分別討論整數和小數的存儲:

如何存儲整數

假設我們現在有一個 8 位的操作系統,最高位是符號位,1 表示負數,0 表示正數,所以能表示的最大的整數區間為 11111111 ~ 01111111 => -127 ~ 127

但是在存儲表達的時候會遇到一個問題,10000000 和 00000000 兩個 0 的問題,+0 和 -0 應該也是相等的,這給電路設計上帶來了很多麻煩和多余的計算規則。

十進制轉成二進制

簡單點的記憶就是:除 2 取余再倒序。

  • 12 / 2 = 6..0
  • 6 / 2 = 3..0
  • 3 / 2 = 1..1
  • 1 / 2 = 0..1

0011 倒序 即為 1100,轉成八位即在前面補0,則結果為 0000_1100。

0000_1100 是 java8 中的寫法,我覺得看起來比較舒服且前后連貫,_ 是為了看起來比較清楚人為添加的,后面沿用這種寫法,0000_1100 = 00001100= 0000 1100
都是相等。

一個天才的設計師

看了很多的文檔沒有找到解決這個問題的作者是誰?不管是誰反正提出了補碼這個概念,真的是有效的解決了這個問題,不僅解決了 0 這個問題還帶來了一個更大的優點,后面補充。

正數的原碼、反碼、補碼

正數原碼 = 反碼 = 補碼

  • 12 的原碼是 0000_1100
  • 12 的反碼是 0000_1100
  • 12 的補碼是 0000_1100

這里并沒有廢話,盡是科學。

負數的原碼、反碼、補碼

反碼 = 原碼符號位不變其他位取反

補碼 = 反碼 + 1

  • -12 的原碼是 1000_1100
  • -12 的反碼是 1111_0011
  • -12 的補碼是 1111_0100

0 的存儲

+0

  • 原碼:0000_0000
  • 反碼:0000_0000
  • 補碼:0000_0000

-0

  • 原碼:1000_0000
  • 反碼:1111_1111
  • 補碼:0000_0000

這里應該把前面的問題都解決了吧,補碼不僅解決了 +0 和 -0 的問題,還神奇的把符號位都去掉了,計算機在運行的過程中從而可以更簡單的進行運算。

減法運算

為了效率,計算機底層計算的時候是沒有減法運算,減法運算都轉成加法運算。

12 - 12 => 12 + (-12) => 0000_1100 + 1111_0100 => 0000_0000

  • 0000_1100 + 1111_0100 = 0 + 0 = 0
  • 0000_1100 + 1111_0100 = 0 + 0 = 0
  • 0000_1100 + 1111_0100 = 1 + 1 = 0 進 1
  • 0000_1100 + 1111_0100 = 1+ 0 + 進1= 0 進 1
  • 0000_1100 + 1111_0100 = 0+ 1 + 進1= 0 進 1
  • 0000_1100 + 1111_0100 = 0+ 1 + 進1= 0 進 1
  • 0000_1100 + 1111_0100 = 0+ 1 + 進1= 0 進 1
  • 0000_1100 + 1111_0100 = 0+ 1 + 進1= 0 進 1

因為只能存儲 8 位,最后一個進 1 爆掉就剩下 0000_0000 了。

這里都是以 8 位計算機進行舉例,現在的 32 位或者 64 位計算機都是同理的。

如何存儲浮點數

相信很多人小數轉成二進制都是不知道如何運算的,更別談存儲了,下面我就娓娓道來。

文章剛開始編輯的時候將浮點數稱為小數,由于發現這樣不專業,其實小數不一定都是浮點數,在那個沒有標準各自為政的早計算機時代其實還是有定點數,感興趣可以看參考文檔。

浮點數轉成二進制

1.234 在計算機中轉成二進制是按照是分整數部分和小數部分進行的,整數部分上面以前講到,這里說下小數部分 0.234 為例:

簡單點的記憶就是:乘 2 取整再順序。

  • 0.234 * 2 = 0.468 => 整數部分為 0 => 取 0
  • 0.468 * 2 = 0.936 => 整數部分為 0 => 取 0
  • 0.936 * 2 = 1.872 => 整數部分為 1 => 取 1 (并將整數部分抹去)
  • 0.872 * 2 = 1.744 => 整數部分為 1 => 取 1 (并將整數部分抹去)
  • 0.744 * 2 = 1.488 => 整數部分為 1 => 取 1 (并將整數部分抹去)
  • 0.488 * 2 = 0.976 => 整數部分為 0 => 取 0
  • 0.976 * 2 = 1.952 => 整數部分為 1 => 取 1 (并將整數部分抹去)
  • 0.952 * 2 = 1.904 => 整數部分為 1 => 取 1 (并將整數部分抹去)
  • 0.904 * 2 = 1.808 => 整數部分為 1 => 取 1 (并將整數部分抹去)
  • 0.808 * 2 = 1.616 => 整數部分為 1 => 取 1 (并將整數部分抹去)
  • 0.616 * 2 = 1.232 => 整數部分為 1 => 取 1 (并將整數部分抹去)
  • 0.232 * 2 = 0.464 => 整數部分為 0 => 取 0
  • ...

下面就不寫下去了結果是 0.001110111110...,寫的越長精度越高。

可見浮點數存儲必將是一個頭疼的問題,浮點數底層的邏輯肯定也是比整數更為復雜。

IEEE754 標準

電氣電子工程師學會(英語:Institute of Electrical and Electronics Engineers)簡稱為 IEEE,IEEE754是專門規定浮點數該如何存儲的一個標準,規定了四種表示浮點數值的方式:單精確度(32位)、雙精確度(64位)、延伸單精確度(43比特以上,很少使用)與延伸雙精確度(79比特以上,通常以80位實現)。

任意一個浮點數都可以表示為:

V = (-1)^s \times M \times 2^E

 (-1)^s 表示符號位,當 s=0,V 為正數;當 s=1,V 為負數。
 M 表示有效數字,大于等于 1,小于 2。
 2^E 表示指數位。

例子:V = 0.234(十進制) = 0.001110111110(二進制) = 1.110111110 * 2^-3 ,則 s=0 ,M= 1.110111110,E=-3。

IEEE754 規定:

  • 對于 32 位的浮點數,最高的 1 位是符號位 s,接著的 8 位是指數 E,剩下的 23 位為有效數字 M。

  • 對于 64 位的浮點數,最高的 1 位是符號位 s,接著的 11 位是指數 E,剩下的 52 位為有效數字 M。

IEEE754 還有一些特殊的規定:

針對 M

由于 1<= M <=2 ,所以 M 始終為 1.xxxx 形式,xxxx 表示小數,那些對于計算機底層嚴苛的設計師這時候又要將 1 這一位舍掉,只保留 xxxx 部分,這樣的好處是可以多儲存一位有效數字。

針對 E

由于 E 是一個 8 位的無符號存儲,只能表示 0 ~ 255,現實中的指數還存在負數,所以規定:E必須再加上一個中間數,對于 8 位的 E,這個中間數是 127;對于 11 位的 E,這個中間數是 1023,這樣就可以將指數的表達范圍擴大到 -127 ~ +128-1023 ~ +1024

實際中的取值范圍是 -126~+127,-127 和 128 被用作特殊值處理,雙精度同理。

阮一峰的博客中寫的是 減去一個中間數 應該是有誤的。

舉個例子,如果 E=17,則 17+127 =144 ,實際的存儲指為 144。

針對 E 還有一些特殊值的情況

  1. 如果指數 E 是 0 并且尾數的小數部分是 0,這個數是 ±0(和符號位相關)。
  2. 如果指數 E 是 1 并且尾數的小數部分是0,這個數是±∞(同樣和符號位相關)
  3. 如果指數 E 是 1 并且尾數的小數部分非0,這個數表示為不是一個數(NaN)。

計算

V = 0.234(十進制) = 0.00111011111001110110110010(二進制) = 1.11011111001110110110010 * 2^-3 ,則 s=0 ,M= 1.110111110,E=-3。

根據 IEEE754 標準轉化 :

以 單精度為例

s 不變
E=-3+127=124(十進制) = 0111_1100(二進制)

M=110_1111_1001_1101_1011_0010

結果將他們連接 0_0111_1100_110_1111_1001_1101_1011_0010(s_E_M)

可以在線校驗結果

小結

這篇文章詳細介紹了計算機如何存儲整數以及浮點數,個人也是從朦朧狀態到理解透徹,參考了眾多大佬的文章,在此表示感謝。有問題的朋友可以通過郵箱聯系到我 jake.zou.me@gmail.com

計算機之美妙不可言啊!

參考文檔

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容