計算機是怎么存儲數(shù)字的

從一個最簡單的例子開始吧。

我們來做幾個小學生的加法題。8 + 9 等于多少?0.8 + 0.9 等于多少?在 Chrome 里面簡單用 Javascript 這門語言簡單測試一下,我們得到的結(jié)果是:
8 + 9 = 17
0.8 + 0.9 = 1.7000000000000002。
是的,你沒有看錯,計算機給出了 1.7000000000000002 這個答案。難道計算機錯了嗎?

計算機錯了,又沒錯。為什么呢?因為計算機是用二進制來表示這些數(shù)字的,有很多數(shù)在計算機里面是沒法精確表示的。先來看看什么是二進制。

十進制、二進制

我們都知道,計算機只能懂 0 和 1,也就是說計算機里面所有的數(shù)字,都是二進制形式的。那么一個普通的數(shù)字,用二進制的形式究竟該如何表示呢?譬如:

  • 78 如何使用二進制表示?

對于正整數(shù),數(shù)學上有一個轉(zhuǎn)換的公式,就是「除 2 取余,逆序排列」法。具體做法是:用 2 整除十進制整數(shù),可以得到一個商和余數(shù);再用 2 去除商,又會得到一個商和余數(shù),如此進行,直到商為 0 時為止,然后把先得到的余數(shù)作為二進制數(shù)的低位有效位,后得到的余數(shù)作為二進制數(shù)的高位有效位,依次排列起來。對于 78 這個十進制整數(shù)來說,對應(yīng)的二進制是 1001110.

  • 0.5 如何使用二進制表示?

對于純小數(shù),也有一個固定的轉(zhuǎn)換法:乘 2 取整,順序排列。具體做法就是:用 2 乘十進制小數(shù),可以得到積,將積的整數(shù)部分取出,再用 2 乘余下的小數(shù)部分,又得到一個積,再將積的整數(shù)部分取出,如此進行,直到積中的小數(shù)部分為零,此時 0 或 1 為二進制的最后一位。對于十進制的 0.5 來說,對應(yīng)的二進制表示就是 0.1

  • 8.5 如何使用二進制表示?

好了,有零有整的小數(shù),在計算機里面怎么表示呢?可能大家已經(jīng)知道了,分成整數(shù)和純小數(shù)兩部分,分別轉(zhuǎn)換,然后相加起來。那么 8.5 的二進制形式可以表示為:1000.1

好了,這是基礎(chǔ),后面的內(nèi)容就開始慢慢接近計算機的工作模式了。

二進制能準確表示十進制數(shù)字嗎

首先看這個問題:

  • 0.1 這個小數(shù),用二進制該如何表示

按照前面的轉(zhuǎn)換規(guī)則,我們先看看看 0.1 怎么表示:
0.1 * 2 = 0.2 ---- 0 (1)
0.2 * 2 = 0.4 ---- 0 (2)
0.4 * 2 = 0.8 ---- 0 (3)
0.8 * 2 = 1.6 ---- 1 (4)
0.6 * 2 = 1.2 ---- 1 (5)
0.2 * 2 = 0.4 ---- 0 (6)

算到這里,我們得到了 0.000110 這樣的一段二進制串,但是不能再算下去了,因為我們看到第 6 行和第 2 行完全一樣,再計算就一直循環(huán)下去了。所以,0.1 這樣一個十進制小數(shù),在計算機的二進制里面根本沒法精確表示。

思考一下,0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9 這 9 個小數(shù),有幾個是可以精確表示的?

  • 精確表示 Vs 精確顯示

通過前面的代碼我們知道,0.8 + 0.9 的結(jié)果,計算機并不能精確表示,但是我們在 Javascript 中定義這樣一個數(shù)值變量

var ha = 1.7

在控制臺打印的話,結(jié)果又確實是 1.7。

好像很矛盾。顯然,通過后面的例子可以知道 Javascript 可以精確顯示 1.7(哪怕 1.7 不能被精確表示),這不是和最開始我們的結(jié)果矛盾嗎?

其實這是一個想當然的錯誤。在直觀上我們認為 0.8 + 0.9 = 1.7 是必然成立的(數(shù)學上確實如此),而且 1.7 又能被精確顯示,那最開始的結(jié)果就應(yīng)該等于 1.7,對不對?

而實際上,在計算機里 0.8 + 0.9 根本不等于 1.7(什么?)!因為 0.8 和 0.9 都不能被精確表示,數(shù)值的精度丟失在了每一個環(huán)節(jié),而不是最后的結(jié)果上。

我們可以用數(shù)學中四舍五入的概念類比一下。我們計算 1.6 + 2.8 保留整數(shù),我們覺得結(jié)果應(yīng)該是 4(4.4 舍入)。但是我們用另一種方法:先把 1.6 舍入成 2,再把 2.8 舍入成 3,最后得到 5。通過不同的運算,我們得到了完全不一樣的結(jié)果!所以,在 0.8 + 0.9 的運算中,參與運算的兩個數(shù)精度一開始已經(jīng)丟失了,所以他們的結(jié)果也不再是 1.7 了。

看到這里,我們是不是要懷疑計算機的一切結(jié)果了?連幾個小數(shù)都無法精確表示的話,其他數(shù)值計算結(jié)果如何保證準確呢?其實,大家不用擔心,計算機在做浮點運算的時候,常常會因為無法精確表示而進行近似或舍入,而其結(jié)果在我們?nèi)粘I钏璧木确秶鷥?nèi),都還是準確的。

剛才說到了浮點運算,照字面的理解就是浮點數(shù)的運算了。但是,浮點數(shù)是什么呢?計算機里面是怎么表示這些數(shù)字的呢?

浮點數(shù)是什么

為了弄清楚浮點數(shù),我們需要先看定點數(shù)。所謂定點數(shù),是計算機中采用的一種數(shù)的表示方法,參與運算的數(shù)的小數(shù)點位置固定不變。那么小數(shù)點位置可變的自然就是所謂的「浮點數(shù)」了。這里要先澄清一個概念:浮點數(shù)并不一定等于小數(shù),定點數(shù)也并不一定就是整數(shù);整數(shù)和小數(shù)是我們小學數(shù)學中使用的,在計算機的世界里,只有定點數(shù)和浮點數(shù)。

  • 定點整數(shù)

小數(shù)點位固定在最后一位之后稱為定點整數(shù)。若機器字長為 8 位,那么它能表示的整數(shù)范圍是 -127 - 127(考慮正負數(shù)的符號)。例如 0111 表示 7。

  • 定點小數(shù)

小數(shù)點固定在最高位之后稱為定點小數(shù)。若機器字長為 8 位,數(shù)值表示范圍是[-(1-2^(-7)), 1-2^(-7)]。例如 1111 表示 -0.875。

  • 浮點數(shù)

在計算機的硬件中是沒有小數(shù)點這個東西的。CPU 能處理的東西都是整的。所以,小數(shù)就要用類似科學計數(shù)法的方式來表示。如 1.234 在計算機里面,可以理解成用 1234 和 -3 兩個整數(shù)來表示:1234 * 10 的 -3 次方,這類數(shù)就叫「浮點數(shù)」。當然,計算機里面的浮點數(shù)是二進制的。任意一個二進制浮點數(shù) V 都可以表示成下面的形式:

V = (-1)^s x M x 2^E

這里:

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

舉例來說,十進制的 5.0,寫成二進制是 101.0,相當于 1.01 x 2^2。那么按照上面的格式,可以得出 s = 0, M = 1.01, E = 2。

CPU 在處理這類數(shù)的運算時需要比整數(shù)運算復(fù)雜得多的電路設(shè)計,且速度比整數(shù)運算慢很多(所以很多年前,浮點運算能力是衡量 CPU 性能的重要指標)。

浮點數(shù)的更多問題

歷史上計算機科學家們曾提出過多種解決方案,最終獲得廣泛應(yīng)用的是 IEEE 754 標準中的方案。IEEE 754 規(guī)定,對于 32 位的浮點數(shù),最高的 1 位是符號位 s,接著的 8 位是指數(shù) E,剩下的 23 位為有效數(shù)字 M:

32 位浮點數(shù)的分段表示

IEEE 754 對有效數(shù)字 M 和指數(shù) E,還有一些特別規(guī)定。

前面說過,1≤M<2,也就是說,M 可以寫成 1.xxxxxx 的形式,其中 xxxxxx 表示小數(shù)部分。IEEE 754 規(guī)定,在計算機內(nèi)部保存 M 時,默認這個數(shù)的第一位總是 1,因此可以被舍去,只保存后面的 xxxxxx 部分。比如保存 1.01 的時候,只保存 01,等到讀取的時候,再把第一位的 1 加上去。這樣做的目的,是節(jié)省 1 位有效數(shù)字。以 32 位浮點數(shù)為例,留給 M 只有 23 位,將第一位的 1 舍去以后,等于可以保存 24 位有效數(shù)字。

至于指數(shù) E,情況就比較復(fù)雜。

首先,E 是沒有符號的。這意味著,如果 E 為 8 位,它的取值范圍為 0~255;但是,我們知道,科學計數(shù)法中的 E 是可以出現(xiàn)負數(shù)的,所以 IEEE 754 規(guī)定,E 的真實值必須再減去一個中間數(shù)(偏移值)。IEEE 754 標準規(guī)定該固定值為 2^(E-1) - 1,對于 8 位的 E,這個中間數(shù)是 127。

比如,2^10 的 E 是 10,所以保存成 32 位浮點數(shù)時,必須保存成10+127=137,即 10001001。

然后,指數(shù) E 還可以再分成三種情況:

  1. E不全為 0 或不全為 1。這時,浮點數(shù)就采用上面的規(guī)則表示,即指數(shù) E 的計算值減去 127,得到真實值,再將有效數(shù)字 M 前加上第一位的 1;
  2. E全為 0。這時,浮點數(shù)的指數(shù) E 等于 1-127,有效數(shù)字 M 不再加上第一位的 1,而是還原為 0.xxxxxx 的小數(shù)。這樣做是為了表示 ±0,以及接近于 0 的很小的數(shù)字;
  3. E 全為 1。這時如果有效數(shù)字 M 全為0,表示 ±無窮大(正負取決于符號位s);如果有效數(shù)字M不全為0,表示這個數(shù)不是一個數(shù)(NaN)

浮點數(shù)表示范圍與表示個數(shù)
對于計算機來說,浮點數(shù)可表示的范圍相當大,但這并不等于表示個數(shù)。從數(shù)量級分析一下,32bit 浮點數(shù)的表示范圍是 10 的 38 次方,而表示個數(shù)呢,是 10 的 10 次方。 能夠被表示的數(shù)只有 1/100000000…. (大概有30個零)。

總之,計算機中數(shù)字的存儲、表示、計算是非常復(fù)雜的,涉及到的內(nèi)容,都夠單獨出一本大部頭的書了,大家有興趣可以自己找資料深入研究一下。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容