星期五, 四月 18, 2008

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 的狀況.

相關標籤

this is comment icon 浮點數比較 [回覆]

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

Comment by gwlin (04/18/2008 17:22)

this is comment icon 回 gwlin [回覆]

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

Comment by Tsung (04/18/2008 17:56)

this is comment icon [回覆]

兄台:看了你的code后,我test下面的code: 11.00何解?
Comment by blanse (05/26/2008 20:02)

this is comment icon 回 blanse [回覆]

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

Comment by Tsung (05/28/2008 12:53)
Add this page to del.icio.us Add this page to Yahoo Taiwan's bookmark Add this page to MyShare

發表迴響

標題

內容 (限制 1000 字)

暱稱

電子郵件

個人網頁


 authimage


PS: 若無法留言, 請先確認是否有打開 JavaScript, 造成您的困擾, 實在萬分對不起 Orz...(如果無法留言, 勞煩可以發信給我好嗎? 謝謝.)
PS2: 若您的留言被誤判, 我都會再自行看過, 不需要一直重覆張貼~