問題
????項(xiàng)目中如果有處理價格的需求那么很大概率要用到float類型表示一個小數(shù),那么如果定義一個比較高的精度邊界條件常常會遇到精度不足的情況,這篇文章就來討論一下這個問題??匆幌鲁绦蛑姓{(diào)試的截圖:

一個三位小數(shù)部分999的float類型變量,我們初始化值和實(shí)際值如圖所示。

一個8為小數(shù)部分99999999的float類型變量,我們初始化值和實(shí)際值如圖所示。
????我們看到第一張截圖顯示變量的實(shí)際值是一個接近于1.999的四舍五入的小數(shù);第二張截圖實(shí)際值干脆丟失了所有精度。那么是什么導(dǎo)致了這樣情況的呢,下面來仔細(xì)分析一下float類型變量。
float類型內(nèi)存模型
????首先來看一下float類型在內(nèi)存中的模型。flaot類型變量在內(nèi)存中占用32個二進(jìn)制位。它的內(nèi)存排布如下圖所示:

????上圖最左邊是高位,最右邊是低位。flaot類型變量分成三個部分:第一部分是最高1位,它代表符號位,0表示正值,1代表負(fù)值;第二部分高1位后的8位,這個8位代表指數(shù)部分值(2的x次方);第三部分是剩余23位,它代表二進(jìn)制科學(xué)記數(shù)法表示的實(shí)際小數(shù)點(diǎn)后面的值。這三個部分又被稱為符號位(sign bit),指數(shù)偏移值(exponent bias),分?jǐn)?shù)值(fraction)。下圖代表他們在運(yùn)算中的關(guān)系:

????先舉一個實(shí)際栗子整體看一下三部分如何拼裝出一個float值,然后再具體說一下比較難以理解的第二部分指數(shù)偏移值和第三部分分?jǐn)?shù)值。先看一個簡單的栗子,1.25。先給出它的內(nèi)存排布:高1位0(正數(shù)),隨后8位(0111 1111),隨后23位(010000...0)。第一部分0表示正數(shù),第二部分先算出它代表的十進(jìn)制數(shù)是127,這里規(guī)定指數(shù)偏移值是n(指數(shù)實(shí)際值) + 127的值,所以這里實(shí)際指數(shù)值應(yīng)該是127-127=0,也就是2的0次冪,實(shí)際上這就是2進(jìn)制科學(xué)計(jì)數(shù)法的移動位數(shù),在這里剛好是不用移位;我們接著看第三部分,第三部分是2進(jìn)制科學(xué)記數(shù)法的小數(shù)點(diǎn)后面的值,由于是科學(xué)記數(shù)法,所以第一位一定是1,故而省略,那么這里的科學(xué)記數(shù)法小數(shù)就成為了1.010000...0;結(jié)合第二部分移位0,所以整數(shù)部分是1(二進(jìn)制),小數(shù)部分0.01(二進(jìn)制),那么這個數(shù)的最終值就是(2的1次冪) + (2的-2次冪)= 1.25。
????那么它的內(nèi)存布局是怎么算出來的呢,尤其是第二部分和第三部分的值是如何得來的呢?再看一個栗子,-8.75,我們這一次從頭推算一下。首先符號位是1,8.75分別在小數(shù)點(diǎn)兩側(cè)換算成2機(jī)制數(shù),變成1000.11,這個小數(shù)變成科學(xué)記數(shù)法1.00011 * 2的3次冪,所以第二部分實(shí)際指數(shù)值為3,加上127就是內(nèi)存中真正的指數(shù)偏移值即130(二進(jìn)制1000 0010);第三部分是科學(xué)記數(shù)法的小數(shù)部分,所以是00011000...0。所以最終的二進(jìn)制值為1 10000010 00011000...0。
????通過上面兩個栗子,我想應(yīng)該可以很清晰的轉(zhuǎn)化一個float值到2進(jìn)制值了,那么接下來我們看一下float值的邊界,即所能表示的最大數(shù)和最小數(shù)。按照上面規(guī)則套一下,最大的值首先是正數(shù),其次指數(shù)部分最大偏移值為255(全部8為1),實(shí)際指數(shù)為255-127=128,即2的127次冪,剩余科學(xué)計(jì)數(shù)法小數(shù)部分全為1,這樣這個數(shù)應(yīng)該就是一個32位數(shù)所能表示的最大數(shù)了。實(shí)際中float類型所能表示的最大數(shù)跟上面的推算有出入,主要是在指數(shù)部分,指數(shù)偏移在IEEE規(guī)定中最大數(shù)指數(shù)偏移只能是254(1111 1110),指數(shù)偏移255(1111 1111)是有其他用途的。同樣最小數(shù)的約定也是不能指數(shù)偏移為0,最小為1。這個規(guī)范可以看一下下面的表格:

????上面表格已經(jīng)很清楚的表示了flaot類型數(shù)的合法值范圍,那么是不是在最大數(shù)和最小數(shù)之間的所有小數(shù)都可以用float表示呢?答案是精度無法保證?;氐介_頭的程序?qū)嶒?yàn),1.999為什么實(shí)際值不是999呢?通過上面的規(guī)則推算, 我們應(yīng)該可以看出小數(shù)部分的值是通過2的-n次冪累加而來(即0.5,0.25,0.125.......),這些數(shù)累加起來近似的表示一個小數(shù),如果小數(shù)部分剛好滿足2的-n次冪或者其和,那么精度就會完美表達(dá),如果不能(比如.999)那么就只能表示為一個近似的值,讓其盡量接近我們想要的值,這也正是第一個程序?qū)嶒?yàn)截圖中程序中實(shí)際的值并不是.999。那么再看小數(shù)部分8個9為什么無法表達(dá)呢?我們先看一下float所能表達(dá)的最小精度,即第三部分為000...01,這個二進(jìn)制小數(shù)部分即2的-23次冪,換算成十進(jìn)制是0.000000119209,我們可以看到float能表達(dá)的小數(shù)部分最小精度到了小數(shù)點(diǎn)后第7位,我們需要的小數(shù)點(diǎn)第8位float已經(jīng)無法通過近似模擬達(dá)到了,所以已經(jīng)超出了float類型的精度極限,故而程序的第二張截圖實(shí)際值無法表達(dá)精度了。
????以上就是float類型精度問題的一個整體分析,希望對有過此類困惑的朋友有所幫助。