PHP 浮點數運算後的比對陷阱

PHP 於浮點數比對都是正常的, 但是如果其中一個浮點數有做過運算, 比對起來就都會是 False.

版本:

  • PHP 5.2.5-3 with Suhosin-Patch 0.9.6.2 (cli)
  • PHP 5.1.6 (cli)
  • 目前測試於此二個版本, 都有此狀況.

詳可見下範例:

<?php
$a = 43.31;
$b = 7.51 + 35.80;

/* 第一次ooxx */
echo ($a == $b) ? "ok\n" : "no\n"; // no

/* 先強迫轉一次浮點數 */
$a = floatval($a);
$b = floatval($b);

/* 再確認一次型態都相同 */
echo gettype($a) . "\n"; // double
echo gettype($b) . "\n"; // double
echo gettype(43.31) . "\n"; // double

/* 第二次ooxx, $b != 43.31 */
echo ($a == 43.31) ? "a=43.31\n" : "a!=43.31\n"; // a=43.31
echo ($b == 43.31) ? "b=43.31\n" : "b!=43.31\n"; // b!=43.31

/* 再確認一次型態, 值 都相同 */
var_dump($a); // float(43.31)
var_dump($b); // float(43.31)

echo $a . "\n"; // 43.31
echo $b . "\n"; // 43.31

/* 第三次ooxx, $a != $b => 43.31 != 43.31? */
echo ($a == $b) ? "ok\n" : "no\n"; // no

/* 轉換成字串來比對 */
$a = strval($a);
$b = strval($b);
echo $a . "\n"; // 43.31
echo $b . "\n"; // 43.31
echo ($a == $b) ? "ok\n" : "no\n"; // ok

/* 試試後面多 0 的狀況 */
$a = 43.31;
$b = 7.51 + 35.800;
// 保險點可以先轉 float 後, 再轉 str
// ex: $a = strval(floatval($a));
$a = strval($a);
$b = strval($b);
echo $a . "\n"; // 43.31
echo $b . "\n"; // 43.31
echo ($a == $b) ? "ok\n" : "no\n"; // ok
?>

上述例子遇到的狀況就是, $a = 43.31, $b = 7.51 + 35.80, 明明都是 43.31, 但是比對判斷 $a 就是都不等於 $b, 不知道是踩到地雷, 還是這樣子的寫法本來就不對, 還請有經驗的人指導. Orz.

最後想到的解法是, 轉成字串再來比對就通過了....

其它解法

感謝 Jace 的指點: 電腦就是差分器, 它的小數是用 1/2 + 1/4 + 1/8 .... + 1/n 這樣的做法, 所以有出現 000001 的差距(雖然我也是猜想這樣, 但是印不出 000001 就是覺得怪怪的), 0000001 這種是不會被表示出來的.

參考 小數點不準 這篇文章的內容, 看起來是用 bcadd 來解決, 詳如下:(範例有另外使用 bccomp 來做比對動作)

<?php
$a = 43.31;
$b = bcadd(7.51, 35.8, 2);
/* 下述兩者比對方式都可以 */
echo ($a == $b) ? "ok\n" : "no\n"; // ok
var_dump((0 == bccomp($a, $b, 2))); // true
?>

註: bcadd() 的方式是, 前面兩個參數相加, 第三個參數是要取幾位, 此範例是要取兩位, 所以寫 2, 超過小數 2位的會自動被移除, 所以值就會相等就不會有 000001 的狀況.


關於 Tsung

對新奇的事物都很有興趣, 喜歡簡單的東西, 過簡單的生活.
本篇發表於 My_Note-Programming。將永久鏈結加入書籤。

PHP 浮點數運算後的比對陷阱 有 7 則回應

  1. gwlin 說道:

    二進位的浮點數轉成十進位顯示後,可能把一些很小的誤差捨棄,所以顯示看起來一樣,但在電腦裡的數值是不相等的,即使只有 0.00000001 的誤差也是不相等。

  2. Tsung 說道:

    嗯嗯, 所以最後就採用將小數2 位以後捨棄調的方法來解決囉~ 🙂

  3. blanse 說道:

    兄台:看了你的code后,我test下面的code:

    11.00何解?

  4. Tsung 說道:

    11.00 有什麼問題嗎? 直接比對不會有問題呀.. @.@a..

  5. 說道:

    php, perl ... 等等的程式,

    在比較的時候都會有一個自動轉換型態的動作

    就是把兩個比較東西轉換成同型態的變數 再做比較,

    也因此它們不用宣告變數型態

    somehow, 在某些時刻自動轉型並不會如預期一樣

    在做不同型態的變數比較時, 最好還是手動去強制系統

    轉換為同型態, 比較不會出問題

  6. Tsung 說道:

    嗯, 可能還不只相同型態的問題, 上面我也測到, 相同都是 double 的型態, 比對起來還是有錯誤, 比對符點數還是要多注意一下. 🙂

  7. walile 說道:

    floating point Orz..
    其實有同事現在碰到,小數到二位數還可能會發生。
    (in perl)

發表迴響