php踩過的那些坑(5)浮點數計算

2022-06-09 20:00:18 字數 2175 閱讀 8937

一、前方有坑

php在使用加減乘除等運算子計算浮點數的時候,經常會出現意想不到的結果,特別是關於財務資料方面的計算,給不少工程師惹了很多的麻煩。比如今天工作終於到的乙個案例:

$a = 2586;

$b = 2585.98;

var_dump($a-$b);

期望的結果是:float(0.02)

實際結果:

float(0.019999999999982)

人生有坑,處處提防

二、防坑攻略:

1、通過乘100的方式轉化為整數加減,然後在除以100轉化回來……

2、使用number_format轉化成字串,然後在使用(float)強轉回來……

3、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的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。這也就解釋了為什麼浮點型減法出現了"減不盡"的精度丟失問題。

換句話說:我們看到十進位制小數,在計算機內儲存的不是乙個精確的數字,也不可能精確。所以在數字加減乘除後出現意想不到的結果。

四、防坑提示

基於以上原因,所以永遠不要相信浮點數結果精確到了最後一位,也永遠不要比較兩個浮點數是否相等。如果確實需要更高的精度,應該使用任意精度數學函式或者 gmp 函式

php浮點數計算問題

如果用php的 計算浮點數的時候,可能會遇到一些計算結果錯誤的問題,比如echo intval 0.58 100 會列印57,而不是58,這個其實是計算機底層二進位制無法精確表示浮點數的乙個bug 是跨語言的,我用python也遇到這個問題。所以基本上大部分語言都提供了精準計算的類庫或函式庫,比如p...

PHP浮點數的精確計算BCMath

php bcmath bc是binary calculator的縮寫。bc 函式的引數都是運算元加上乙個可選的 int scale 比如string bcadd string lef tope rand str ingleftoperand,string right operand int scal...

js浮點數的計算

js在計算浮點數時可能不夠準確,會產生捨入誤差的問題,這是使用基於ieee745數值的浮點計算的通病,並非ecmascript一家,其他使用相同數值格式的語言也存在這個問題。這裡講一下js浮點數加 減 乘 除的正確做法。整數的乘法運算是準確的,這裡我們將浮點數的乘法運算轉化為整數乘法,然後除以10的...