1、前方有坑php
php在使用加減乘除等運算符計算浮點數的時候,常常會出現意想不到的結果,特別是關於財務數據方面的計算,給很多工程師惹了不少的麻煩。好比今天工做終於到的一個案例:算法
$a = 2586;函數
$b = 2585.98;測試
var_dump($a-$b);網站
指望的結果是:float(0.02)spa
實際結果:orm
float(0.019999999999982)內存
人生有坑,到處提防字符串
2、防坑攻略:數學
一、經過乘100的方式轉化爲整數加減,而後在除以100轉化回來……
二、使用number_format轉化成字符串,而後在使用(float)強轉回來……
三、php提供了高精度計算的函數庫,實際上就是爲了解決這個浮點數計算問題而生的。
主要函數有:
bcadd — 將兩個高精度數字相加
bccomp — 比較兩個高精度數字,返回-1, 0, 1
bcdiv — 將兩個高精度數字相除
bcmod — 求高精度數字餘數
bcmul — 將兩個高精度數字相乘
bcpow — 求高精度數字乘方
bcpowmod — 求高精度數字乘方求模,數論裏很是經常使用
bcscale — 配置默認小數點位數,至關於就是Linux bc中的」scale=」
bcsqrt — 求高精度數字平方根
bcsub — 將兩個高精度數字相減
前兩種流氓的辦法就不測試了,使用bcsub測試第三種兩數相減的例子,
先看bcsub用法(來自官網)
string bcsub ( string $left_operand , string $right_operand [, int $scale = int ] )
參數
left_operand 字符串類型的左操做數.
right_operand 字符串類型的右操做數.
scale 此可選參數用於設置結果中小數點後的小數位數。也可經過使用 bcscale() 來設置全局默認的小數位數,用於全部函數。
返回值 返回減法以後結果爲字符串類型.
測試代碼:
var_dump(bcsub($a,$b,2));
結果
0.02
其餘的函數請參考PHP官方網站
3、爲啥有坑:
php的bug?不是,這是全部語言基本上都會遇到的問題,因此基本上大部分語言都提供了精準計算的類庫或函數庫。
要搞明白這個緣由, 首先咱們要知道浮點數的表示(IEEE 754):
浮點數, 以64位的長度(雙精度)爲例, 會採用1位符號位(E), 11指數位(Q), 52位尾數(M)表示(一共64位).
符號位:最高位表示數據的正負,0表示正數,1表示負數。
指數位:表示數據以2爲底的冪,指數採用偏移碼錶示
尾數:表示數據小數點後的有效數字.
這裏的關鍵點就在於, 小數在二進制的表示, 小數如何轉化爲二進制呢?
算法是乘以2直到沒有了小數爲止。這裏舉個例子,0.9表示成二進制數
0.9*2=1.8 取整數部分 1
0.8(1.8的小數部分)*2=1.6 取整數部分 1
0.6*2=1.2 取整數部分 1
0.2*2=0.4 取整數部分 0
0.4*2=0.8 取整數部分 0
0.8*2=1.6 取整數部分 1
0.6*2=1.2 取整數部分 0
.........
0.9二進制表示爲(從上往下): 1100100100100......
注意:上面的計算過程循環了,也就是說*2永遠不可能消滅小數部分,這樣算法將無限下去。很顯然,小數的二進制表示有時是不可能精確的 。其實道理很簡單,十進制系統中能不能準確表示出1/3呢?一樣二進制系統也沒法準確表示1/10。這也就解釋了爲何浮點型減法出現了"減不盡"的精度丟失問題。
換句話說:咱們看到十進制小數,在計算機內存儲的不是一個精確的數字,也不可能精確。因此在數字加減乘除後出現意想不到的結果。
4、防坑提示
基於以上緣由,因此永遠不要相信浮點數結果精確到了最後一位,也永遠不要比較兩個浮點數是否相等。若是確實須要更高的精度,應該使用任意精度數學函數或者 gmp 函數