浮點數那些事兒

2021-09-14 07:55:56 字數 3199 閱讀 8144

說起浮點數,大家都是又恨又愛的。愛呢,是因為,只有它可以方便地使用小數;恨呢,是因為它並不能精確地表示小數。

以 php 為例:floor((0.1 + 0.7) * 10)這樣乙個函式呼叫,根據數學老師死得晚原理,大家都能得出 8 這個結果。可是事實上呢?它會返回 7。數學老師的棺材板。。。(╯‵□′)╯︵┻━┻

可是為什麼會出現這種情況呢?這就要從浮點數的特性說起了。

我們都知道,在計算機中,一切的一切都是二進位制表示的。假設乙個 4 位元組整型的十進位制數 8,在大端表示的機器中,表示成00000000 00000000 00000000 000010000x0000008)。將十進位制整數轉換成二進位制數,是非常容易的。可是,小數呢?比如,我們要表示 1.75,該怎麼儲存在計算機中呢?顯然,不能像整數一樣儲存了。

讓我們回憶一下,在十進位制中,小數是怎麼計算的。上面的 1.75 我們是這麼算的:1 × 10^0 + 7 × 10^-1 + 5 × 10^-2 。那麼我們按照相同的規則,來用二進位制計算一下小數部分:0.75 = 1/2 + 1/4,也就是 1 × 2^-1 + 1 × 2^-2 ,再加上前面的整數部分,那麼整個式子就變成了 1 × 2^0 + 1 × 2^-1 + 1 × 2^-2 ,寫成二進位制形式就是 1.11。所以,1.75 的二進位制表示是 1.11。

對於將小數轉換為二進位制,和整數部分除二取餘相反的,是乘二取整。

0.75 * 2 = 1.5 -> 1

0.5 * 2 = 1 -> 1

所以我們同樣可以得出 1.11。

好了,我們已經知道如何表示乙個小數的二進位制了。辣麼,問題來了。學過 c 語言的同學都知道,乙個float只有 4 位元組,乙個double也只有 8 位元組。那麼,這麼表示乙個小數,好像範圍很有限。

在數學老師哭暈在廁所之前,我們應該還記得十進位制數中有這麼乙個東西——科學計數法,我們可以很方便地用它來表示很大的十進位制數。那麼,同理,我們也可以用在浮點數的表示上。

讓我們先來回憶一下,科學計數法的表示。假設我們有乙個數 17500,我們可以用科學計數法表示成 1.75 × 10^4 。我們照葫蘆畫瓢,在二進位制數中,假設有乙個數是 11010。我們來和十進位制對應一下。十進位制是乘 10,那麼二進位制就是乘 2,我們對應的就可以寫成 1.101 × 2^100 。對,其實就是這麼簡單。那也許有的人會問了,為什麼不寫成 0.1101 × 2^101 呢?我們再來回憶一下,在十進位制科學計數法中,是不是有乙個規定,整數部分的範圍是 [1,10)。那對應到我們的二進位制數上,這個規定就可以變成 [1,2) 了,沒錯,對應關係就是這麼簡單。

好了,我們現在也知道怎麼使用二進位制來表示小數,以及使用科學計數法來表示二進位制小數了。那麼,我們距離把數字存入計算機記憶體僅剩一步之遙了,我們要把所有的東西存到記憶體裡去,那麼我們就需要合理地分配記憶體空間。浮點數有兩種,一種是單精度浮點數(float),占用 4 位元組的記憶體。其中,1 位是符號位,8 位是階碼(冪),23 位是尾數(小數部分)。

細心的各位可能會發現,好像沒有整數部分?別急,這就是上面那個規定的有用之處。當整數部分在 [1,2) 之間時,也就只可能取到乙個值 1,那麼,對於這個值,我們是不是就可以當做預設值而不記錄在浮點數的表示中了?而這樣,我們的浮點數的精度又多了一位(小數部分的位數決定了精度)。這種表示叫做隱含 1 開頭的表示。

偏置值到了這裡,我們發現,第一位是浮點數的正負符號,那麼,對於乙個科學計數法來說,階碼同樣需要有正負。而在單精度中,階碼只有 8 位;雙精度中,階碼只有 11 位。如果我們給階碼表示成補碼,那麼,我們能夠表示的數的範圍就會縮小,這樣顯然是不划算的。於是,偏置值就由此誕生了。

規格化的值(階碼不全為 0 或 1)

在記憶體中的規格化的浮點數表示中,階碼並非是 2 的冪,而是經過計算的結果,這個計算公式就是 e - bias,這裡的 bias 就是偏置值,而 e 就是階碼在浮點數中的二進位制表示。bias 的值是 2^k-1 - 1(單精度是 127,雙精度是 1023),所以,e - bias 的取值範圍就是 [-126, 127](單精度)和 [-1022, 1023](雙精度)。其實如果對補碼了解的比較好的同學,應該就能看出來,這其實就是省略了符號位的補碼表示)。

通過上面的隱含 1 開頭的表示的尾數,我們可以計算出基數 m = 1 + f。那麼我們整個的浮點數可以寫成這樣乙個表示式:m × (e - bias)。

非規格化的值(階碼全為 0)

對於規格化和非規格化的值來說,我們都可以用同乙個式子來表示。不過,為了某些更加方便的原因(這裡就不展開講了),對它們做了區分。如果按照規格化的計算來看,階碼的值是 0 - bias,不過在這裡,我們讓階碼的值等於 1 - bias。同樣的,由於我們給階碼加了 1,那麼整個浮點數就會向左移動一位,那麼,我們需要讓浮點數的值不變,m 就不在需要上面整數部分的 1 了,所以 m = f。

同時,我們會發現乙個問題,那就是 +0.0 和 -0.0 在浮點數的二進位制表示上是不同的。

特殊值(階碼全為 1)

最後,還剩下這樣一種數字,那就是階碼全為 1 的情況。當小數為 0 的時候,浮點數的值為 ∞。當小數不為 0 時,浮點數的值為 nan,即不是乙個數(not a number)。

好了,扯了這麼多,我們現在回到最開始的問題上,floor((0.1 + 0.7) * 10) = 7。我們先看 0.1 的二進位制表示。

首先,我們將十進位制小數轉換成二進位制小數,可以得到 0.000[1100]···。讓我們轉換成浮點數的二進位制表示。按照上面的規則,它可以被表示成科學計數法 1.10011001100110011001100 × 2^-4 ,這樣,階碼就是 -4 + 127 = 123,二進位制表示為 01111011。所以,整個浮點數的二進位制表示就是00111101110011001100110011001100(0x3dcccccc)。同樣的,0.7 會表示為00111101001100110011001100110011(0x3d333333)。

首先我們要對階碼小的數進行對階,然後再進行尾數的加法,這樣,我們得到的值就是00111101111001100110011001100101。我們將其轉換成十進位制,發現,它是小於 0.8 的。因此,當我們再進行乘法運算向下取整時,會等於 7。

其實,浮點數有很多坑。因此,我們在使用浮點數的時候,一定要小心。還有,涉及到金額計算的時候,一定不能使用浮點數。

《深入理解計算機系統(第 3 版)》第 2.4.2 節

浮點數 儲存

關鍵字 體系結構 ieee754 浮點數 儲存 main 如果不執行上面的 讓我們來直接判斷,輸出的結果會是什麼?而在你執行程式之後,結果卻很讓人詫異 123.456001。為什麼會是123.456001?有六位小數可以理解,最後那個1是為何?有很多人解釋說最後那個1是亂碼,隨機的。嘿嘿 其實無論你...

浮點數操作

float fx 49.03f int nx fx 100 printf d nx 執行上述 結果 4902。用vc6.0,2005,gcc編譯執行結果都是一樣。為什麼會這樣呢,是因為浮點數運算具有不精確性。其實編譯上面的 編譯器會有警告的。warning c4244 initializing co...

浮點數比較

在數 算當中經常會涉及到判斷兩個數是否相等的情況 對於整數很好處理 a b這樣的乙個語句就可以解決全部的問題 但是對於浮點數是不同的 首先,浮點數在計算機當中的二進位制表達方式就決定了大多數浮點數都是無法精確的表達的 現在的計算機大部分都是數字計算機,不是模擬機,數字機的離散化的資料表示方法自然無法...