摘要:說起浮點數(shù),大家都是又恨又愛的。當(dāng)小數(shù)不為時,浮點數(shù)的值為,即不是一個數(shù)。所以,整個浮點數(shù)的二進(jìn)制表示就是。最后其實,浮點數(shù)有很多坑。因此,我們在使用浮點數(shù)的時候,一定要小心。還有,涉及到金額計算的時候,一定不能使用浮點數(shù)。
本文為作者自己的總結(jié)的,由于作者的水平限制,難免會有錯誤,歡迎大家指正,感激不盡。
說起浮點數(shù),大家都是又恨又愛的。愛呢,是因為,只有它可以方便地使用小數(shù);恨呢,是因為它并不能精確地表示小數(shù)。
以 PHP 為例:floor((0.1 + 0.7) * 10) 這樣一個函數(shù)調(diào)用,根據(jù)數(shù)學(xué)老師死得晚原理,大家都能得出 8 這個結(jié)果??墒鞘聦嵣夏兀克鼤祷?7。數(shù)學(xué)老師的棺材板。。。(╯‵□′)╯︵┻━┻
可是為什么會出現(xiàn)這種情況呢?這就要從浮點數(shù)的特性說起了。
萬物皆二進(jìn)制我們都知道,在計算機(jī)中,一切的一切都是二進(jìn)制表示的。假設(shè)一個 4 字節(jié)整型的十進(jìn)制數(shù) 8,在大端表示的機(jī)器中,表示成 00000000 00000000 00000000 00001000(0x0000008)。將十進(jìn)制整數(shù)轉(zhuǎn)換成二進(jìn)制數(shù),是非常容易的??墒?,小數(shù)呢?比如,我們要表示 1.75,該怎么存儲在計算機(jī)中呢?顯然,不能像整數(shù)一樣存儲了。
小數(shù)的二進(jìn)制讓我們回憶一下,在十進(jìn)制中,小數(shù)是怎么計算的。上面的 1.75 我們是這么算的:1 × 10^0 + 7 × 10^-1 + 5 × 10^-2 。那么我們按照相同的規(guī)則,來用二進(jìn)制計算一下小數(shù)部分:0.75 = 1/2 + 1/4,也就是 1 × 2^-1 + 1 × 2^-2 ,再加上前面的整數(shù)部分,那么整個式子就變成了 1 × 2^0 + 1 × 2^-1 + 1 × 2^-2 ,寫成二進(jìn)制形式就是 1.11。所以,1.75 的二進(jìn)制表示是 1.11。
對于將小數(shù)轉(zhuǎn)換為二進(jìn)制,和整數(shù)部分除二取余相反的,是乘二取整。
0.75 * 2 = 1.5 -> 1
0.5 * 2 = 1 -> 1
所以我們同樣可以得出 1.11。
科學(xué)計數(shù)法好了,我們已經(jīng)知道如何表示一個小數(shù)的二進(jìn)制了。辣么,問題來了。學(xué)過 C 語言的同學(xué)都知道,一個 float 只有 4 字節(jié),一個 double 也只有 8 字節(jié)。那么,這么表示一個小數(shù),好像范圍很有限。
在數(shù)學(xué)老師哭暈在廁所之前,我們應(yīng)該還記得十進(jìn)制數(shù)中有這么一個東西——科學(xué)計數(shù)法,我們可以很方便地用它來表示很大的十進(jìn)制數(shù)。那么,同理,我們也可以用在浮點數(shù)的表示上。
讓我們先來回憶一下,科學(xué)計數(shù)法的表示。假設(shè)我們有一個數(shù) 17500,我們可以用科學(xué)計數(shù)法表示成 1.75 × 10^4 。我們照葫蘆畫瓢,在二進(jìn)制數(shù)中,假設(shè)有一個數(shù)是 11010。我們來和十進(jìn)制對應(yīng)一下。十進(jìn)制是乘 10,那么二進(jìn)制就是乘 2,我們對應(yīng)的就可以寫成 1.101 × 2^100 。對,其實就是這么簡單。那也許有的人會問了,為什么不寫成 0.1101 × 2^101 呢?我們再來回憶一下,在十進(jìn)制科學(xué)計數(shù)法中,是不是有一個規(guī)定,整數(shù)部分的范圍是 [1,10)。那對應(yīng)到我們的二進(jìn)制數(shù)上,這個規(guī)定就可以變成 [1,2) 了,沒錯,對應(yīng)關(guān)系就是這么簡單。
浮點數(shù)好了,我們現(xiàn)在也知道怎么使用二進(jìn)制來表示小數(shù),以及使用科學(xué)計數(shù)法來表示二進(jìn)制小數(shù)了。那么,我們距離把數(shù)字存入計算機(jī)內(nèi)存僅剩一步之遙了,我們要把所有的東西存到內(nèi)存里去,那么我們就需要合理地分配內(nèi)存空間。浮點數(shù)有兩種,一種是單精度浮點數(shù)(float),占用 4 字節(jié)的內(nèi)存。其中,1 位是符號位,8 位是階碼(冪),23 位是尾數(shù)(小數(shù)部分)。
細(xì)心的各位可能會發(fā)現(xiàn),好像沒有整數(shù)部分?別急,這就是上面那個規(guī)定的有用之處。當(dāng)整數(shù)部分在 [1,2) 之間時,也就只可能取到一個值 1,那么,對于這個值,我們是不是就可以當(dāng)做默認(rèn)值而不記錄在浮點數(shù)的表示中了?而這樣,我們的浮點數(shù)的精度又多了一位(小數(shù)部分的位數(shù)決定了精度)。這種表示叫做隱含 1 開頭的表示。
規(guī)格化與非規(guī)格化 偏置值到了這里,我們發(fā)現(xiàn),第一位是浮點數(shù)的正負(fù)符號,那么,對于一個科學(xué)計數(shù)法來說,階碼同樣需要有正負(fù)。而在單精度中,階碼只有 8 位;雙精度中,階碼只有 11 位。如果我們給階碼表示成補(bǔ)碼,那么,我們能夠表示的數(shù)的范圍就會縮小,這樣顯然是不劃算的。于是,偏置值就由此誕生了。
規(guī)格化的值(階碼不全為 0 或 1)在內(nèi)存中的規(guī)格化的浮點數(shù)表示中,階碼并非是 2 的冪,而是經(jīng)過計算的結(jié)果,這個計算公式就是 e - Bias,這里的 Bias 就是偏置值,而 e 就是階碼在浮點數(shù)中的二進(jìn)制表示。Bias 的值是 2^k-1 - 1(單精度是 127,雙精度是 1023),所以,e - Bias 的取值范圍就是 [-126, 127](單精度)和 [-1022, 1023](雙精度)。其實如果對補(bǔ)碼了解的比較好的同學(xué),應(yīng)該就能看出來,這其實就是省略了符號位的補(bǔ)碼表示)。
通過上面的隱含 1 開頭的表示的尾數(shù),我們可以計算出基數(shù) M = 1 + f。那么我們整個的浮點數(shù)可以寫成這樣一個表達(dá)式:M × (e - Bias)。
非規(guī)格化的值(階碼全為 0)對于規(guī)格化和非規(guī)格化的值來說,我們都可以用同一個式子來表示。不過,為了某些更加方便的原因(這里就不展開講了),對它們做了區(qū)分。如果按照規(guī)格化的計算來看,階碼的值是 0 - Bias,不過在這里,我們讓階碼的值等于 1 - Bias。同樣的,由于我們給階碼加了 1,那么整個浮點數(shù)就會向左移動一位,那么,我們需要讓浮點數(shù)的值不變,M 就不在需要上面整數(shù)部分的 1 了,所以 M = f。
同時,我們會發(fā)現(xiàn)一個問題,那就是 +0.0 和 -0.0 在浮點數(shù)的二進(jìn)制表示上是不同的。
特殊值(階碼全為 1)最后,還剩下這樣一種數(shù)字,那就是階碼全為 1 的情況。當(dāng)小數(shù)為 0 的時候,浮點數(shù)的值為 ∞。當(dāng)小數(shù)不為 0 時,浮點數(shù)的值為 NaN,即不是一個數(shù)(Not a Number)。
計算浮點數(shù)好了,扯了這么多,我們現(xiàn)在回到最開始的問題上,floor((0.1 + 0.7) * 10) = 7。我們先看 0.1 的二進(jìn)制表示。
首先,我們將十進(jìn)制小數(shù)轉(zhuǎn)換成二進(jìn)制小數(shù),可以得到 0.000[1100]···。讓我們轉(zhuǎn)換成浮點數(shù)的二進(jìn)制表示。按照上面的規(guī)則,它可以被表示成科學(xué)計數(shù)法 1.10011001100110011001100 × 2^-4 ,這樣,階碼就是 -4 + 127 = 123,二進(jìn)制表示為 01111011。所以,整個浮點數(shù)的二進(jìn)制表示就是 00111101110011001100110011001100(0x3dcccccc)。同樣的,0.7 會表示為00111101001100110011001100110011(0x3d333333)。
首先我們要對階碼小的數(shù)進(jìn)行對階,然后再進(jìn)行尾數(shù)的加法,這樣,我們得到的值就是 00111101111001100110011001100101。我們將其轉(zhuǎn)換成十進(jìn)制,發(fā)現(xiàn),它是小于 0.8 的。因此,當(dāng)我們再進(jìn)行乘法運算向下取整時,會等于 7。
最后其實,浮點數(shù)有很多坑。因此,我們在使用浮點數(shù)的時候,一定要小心。還有,涉及到金額計算的時候,一定不能使用浮點數(shù)。
參考文獻(xiàn)本文為作者自己讀書總結(jié)的文章,由于作者的水平限制,難免會有錯誤,歡迎大家指正,感激不盡。
《深入理解計算機(jī)系統(tǒng)(第 3 版)》第 2.4.2 節(jié)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/88639.html
摘要:說起浮點數(shù),大家都是又恨又愛的。當(dāng)小數(shù)不為時,浮點數(shù)的值為,即不是一個數(shù)。所以,整個浮點數(shù)的二進(jìn)制表示就是。最后其實,浮點數(shù)有很多坑。因此,我們在使用浮點數(shù)的時候,一定要小心。還有,涉及到金額計算的時候,一定不能使用浮點數(shù)。 本文為作者自己的總結(jié)的,由于作者的水平限制,難免會有錯誤,歡迎大家指正,感激不盡。 說起浮點數(shù),大家都是又恨又愛的。愛呢,是因為,只有它可以方便地使用小數(shù);恨呢,...
摘要:一直在用,發(fā)現(xiàn)并不了解。并且獨立于語言,可以在各語言間進(jìn)行數(shù)據(jù)交換。字符是大小敏感的。注意是轉(zhuǎn)義字符,還支持其他轉(zhuǎn)義字符,見參考數(shù)字可以整數(shù),浮點數(shù),科學(xué)計數(shù)法表示。元素類型最好是同一類型,畢竟大多數(shù)語言不支持元素類型多種類型。 一直在用JSON,發(fā)現(xiàn)并不了解JSON。好吧,花點時間學(xué)習(xí)下。 一、JSON的概念:什么是JSON? 全稱:JavaScript Object Natatio...
摘要:而的浮點數(shù)設(shè)置的偏移值是,因為指數(shù)域表現(xiàn)為一個非負(fù)數(shù),位,所以,實際的,所以。這是因為它們在轉(zhuǎn)為二進(jìn)制時要舍入部分的不同可能造成的不同舍 IEEE 754 表示:你盡管抓狂、罵娘,但你能完全避開我,算我輸。 一、IEEE-754浮點數(shù)捅出的那些婁子 首先我們還是來看幾個簡單的問題,能說出每一個問題的細(xì)節(jié)的話就可以跳過了,而如果只能泛泛說一句因為IEEE754浮點數(shù)精度問題,那么下文還是...
閱讀 2403·2021-09-28 09:45
閱讀 3650·2021-09-24 09:48
閱讀 2323·2021-09-22 15:49
閱讀 3176·2021-09-08 16:10
閱讀 1656·2019-08-30 15:54
閱讀 2394·2019-08-30 15:53
閱讀 3076·2019-08-29 18:42
閱讀 2922·2019-08-29 16:19