2023-08-23|閱讀時間 ‧ 約 5 分鐘

PHP浮點數精度問題

先前在處理公司額度計算的開發時,遇到了PHP浮點數的資料處理會出現精度丟失的問題,還好google發現大家都有碰過這個坑?

根據PHP官方說明,浮點數的精度有限。儘管取決於系統,PHP 通常使用 IEEE 754 雙精度格式,則由於取整而導致的最大相對誤差為 1.11e-16。非基本數學運算可能會給出更大誤差,並且要考慮到進行複合運算時的誤差傳遞。此外,以十進制能夠精確表示的有理數如 0.1 或 0.7,無論有多少尾數都不能被內部所使用的二進制精確表示,因此不能在不丟失一點點精度的情況下轉換為二進制的格式。這就會造成混亂的結果:例如,floor((0.1+0.7)*10) 通常會返回 7 而不是預期中的 8,因為該結果內部的表示其實是類似 7.9999999999999991118...。

範例參考:

首先先將浮點數轉換為二進制,並透過兩數二進制加總,並轉回十進制即可得出精度丟失的結果

//浮點數 2.4 轉為二進制
2.4 = 10.011001100110011001100110011001100110011001100110011;

0.8 = 0.1100110011001100110011001100110011001100110011001101;

// 2.4 的二進制與 0.8 的二進制結果加總
10.011001100110011001100110011001100110011001100110011
+ 0.1100110011001100110011001100110011001100110011001101;
--------------------------------------------------------------------------------
11.0011001100110011001100110011001100110011001100110011
//將計算結果 11.0011001100110011001100110011001100110011001100110011
//轉為十進制
11.0011001100110011001100110011001100110011001100110011 = 3.19999999999999996

根據上述計算,我們可以看出來在電腦執行後十進制為3.19999999999999996 而非我們認為的2.4+0.8=3.2,電腦對於浮點數的處理,通過轉為二進制運算後,在轉換為十進制,就會造成精度丟失的問題。

而根據PHP處理浮點數造成精度丟失的問題,可以解決的辦法大概就是先小數轉為整數去計算,最後在對運算結果進行計算後再轉回小數,但還好大多數人都踩過這個坑,而PHP官方也針對這個問題提供了高精度計算的函數庫,就是為了解決這個問題的處理,而目前使用Laravel框架就有支持囉,直接函數處理計算

bcadd — 將兩個高精度數字相加

bccomp — 比較兩個高精度數字,返回-1, 0, 1

bcdiv — 將兩個高精度數字相除

bcmod — 求高精度數字餘數

bcmul — 將兩個高精度數字相乘

bcpow — 求高精度數字乘方

bcpowmod — 求高精度數字乘方求模,數論裡非常常用

bcscale — 配置默認小數點位數,相當於就是Linux bc中的”scale=

bcsqrt — 求高精度數字平方根

bcsub — 將兩個高精度數字相減

註:根據官網說明文件以bcadd 函數為例,我們可以看到,這邊作為兩個數字的加總,我們必須使用的是字串類型的格式,故在執行高精度數字運算加總時,要先將取得金額數字轉成string的格式後,在使用bacdd進行兩個數字的相加處理

//float to string
$amount = number_format($amount, 4, '.', '');

bcadd($num1, $amount, 4)

總結:還好小菜雞撰寫的功能在dev交由測試的時候,測試人員努力測爆時有發現尾數加減的問題,因為我也是第一次遇到,算是一個難得的經驗累積,特此紀錄~供大家參考~

參考

https://www.php.net/manual/zh/language.types.float.php

https://www.php.net/manual/zh/book.bc.php


分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.