對於這個問題的發現源於一個計時器程序,先看下這段代碼吧。php
<?php $s=gettime(); usleep(10000); $e=gettime(); var_dump($s); var_dump($e); echo $e-$s."\n"; function gettime(){ list($sec,$usec)=explode(" ",microtime()); return $sec+$usec; } ?>
輸出結果爲:算法
float(1296098836.2033)
float(1296098836.2135)
0.010126113891602函數
有沒有發現一個很詭異的現象?被減數和減數整數部分是相同的,小數點後都只是四位,可是這兩個數相減以後得出的結果,小數點後的位數卻要大於4。其實想解釋也不難,由於被減數和減數的精度都爲14,因此爲不形成精度損失,因此結果的精度也必須是14位的。測試
但出現這種現象的根本緣由在哪?code
因此我去網上逛了逛,看了些東西后,獲得了一些啓發。因此又寫了一段測試代碼:ci
<?php ini_set('precision',14); $a=0.3; $b=0.1+0.2; var_dump($a); var_dump($b); $a==$b?print "equals\n":print "not equals\n"; ?>
輸出的結果是:字符串
float(0.3);get
float(0.3);io
not equalsfunction
你們是否是感受很奇怪,兩個變量都是浮點型的,都是0.3,爲何不相等呢?
使用序列化函數serialize查看一下兩個數的實際值:
<?php echo serialze(0.3),"\n"; echo serialize(0.1+0.2),"\n"; ?>
輸出結果:
d:0.299999999999999988897769753748434595763683319091796875;
d:0.3000000000000000444089209850062616169452667236328125;
你會發現這兩個數實際上都不是真正的0.3,爲何這樣呢?
其實這個問題要追溯到微機原理(也有多是計算機組成原理,記不清楚是哪一本書了),這裏面講了計算機是如何用二進制來存儲定點小數。大體是這樣 的:若是用一個字節的長度來表示一個定點小數,第一位表示小數的符號,0爲正,1爲負;後面7位表示小數的值,第2位至第8位的位權分別是 1/2,1/4,1/8,1/16,1/32,1/64,1/128,而後用這些權值的和來表示全部的小數。如何表示0.625呢?
這個有固定的算法:首先,將小數點左側的整數部分變換爲其二進制形式,處理小數部分的算法是將咱們的小數部分乘以基數 2,記錄乘積結果的整數部分,接着將結果的小數部分繼續乘以 2,並不斷繼續該過程。
0.625 x 2 = 1.25 1
0.25 x 2 = 0.5 0
0.5 x 2 = 1 1
當最後的結果爲1時,結束這個過程。這時右側的一列數字就是咱們所需的二進制小數部分,即 0.101。這樣,咱們就獲得了完整的二進制形式 0.101 ,按階展開:(1*(1/2))+(0*(1/4))+(1*(1/8))=0.5+0.125=0.625。
咱們上面先的例子比較特殊,這個數只須要三次運算就可以結束。那會不會有沒法結束的狀況,不難想象,不少小數根本不能通過有限次這樣的過程而獲得結 果(好比最簡單的 0.1)。但浮點數尾數域的位數是有限的,爲此,浮點數的處理辦法是持續該過程直到由此獲得的尾數足以填滿尾數域,以後對多餘的位進行舍入。也就是說,十 進制到二進制的變換也並不能保證老是精確的,而只能是近似值。事實上,只有不多一部分十進制小數具備精確的二進制浮點數表達。再加上浮點數運算過程當中的誤 差累積,結果是不少咱們看來很是簡單的十進制運算在計算機上卻每每出人意料。這就是最多見的浮點運算的"不許確"問題。
因此,在計算機裏表示的浮點數只是一個近似的數,並非像表示整數那樣精確沒有誤差。既然它只是一個約數,那麼你用精確的==來比較兩位不精確的約數就沒有太大意義了。若是必定要比較兩個浮點數,能夠考慮先轉換成字符串,而後再去比較。
我在Linux下使用c編寫了一段相似的浮點數比較的代碼,結果也是不相等的。實際上在全部的語言裏都會存在此問題,由於這是計算機原理所決定的。但也不排除一些語言作了些後期處理,能夠直接比較兩個浮點數。
以上僅是我的理解,不保證絕對正確,有錯誤還請多多諒解。