どもです。
ウェブシステム上でお金の計算をしたい。
となれば、厳密な数値を算出する必要があります。
が、一般的にコンピュータ計算では浮動小数点による小数計算誤差問題がつきものです。
これの対策をしないと、切り捨てや切り上げの段階で最下桁が狂う可能性があります。
MySQLなど他言語では、DECIMAL型という桁数を厳密に指定して誤差を回避するデータ型があります。
が、PHPやJSは浮動小数点型のfloatやdoubleしかありません。さて、どうしよう。
PHPの場合 BCMath(任意精度数学関数)を使う
$t1 = 1800 * 1.08;
var_dump($t1); //float(1944)
var_dump(rtrim(sprintf('%.40f', $t1), '0')); //string(45) "1944.0000000000002273736754432320594787597656"
$t2 = bcmul(1800, 1.08, 2);
var_dump($t2); //string(7) "1944.00"
BCMath関数を利用すると、結果を文字列で取得します。
PHPは数字の文字列を数値扱いでしてくれますので、そのまま切り上げ切り捨てできますし、結果も狂いません。
ちなみに、BCMath関数は有効桁数未満を「切り捨て」します。
これが切り上げや四捨五入だと、丸めたい桁数+2桁まで取らなければいけませんが、切り捨てなので+1桁を第三引数に指定しましょう。
なお、小数計算に対応した計算関数ではgmp関数もあるのですが、こっちのがマイナーっぽいです。
両方ともバニラのPHPには存在しないため、別途インストールが必要。でもBCMathは標準装備している会社が多いみたいです。
僕のケースもBCMathは実装済みでした。
参照:[PHP マニュアル]BCMath 任意精度数学関数
参考:[Qiita]浮動小数点数の乗算でハマった話
最終手段はString型変換
var_dump((string)(1800 * 1.08)); //string(4) "1944"
関数がインストールされていなくて、かつインストールできない場合は、計算結果を文字列に変換してしまいましょう。
だいたいのケースはこれで上手くいきます。(100%保障はありません)
先の例で、厳密には小数点以下の数値を持っていた$t1が、var_dumpではfloat(1944)と出力しました。
String型に変換すると、この丸められた「1944」を文字列にするので、結果としてBCMathと同じ結果になります。
繰り返しますが、100%保障はありません。
たまに小数点第一位くらいから狂う計算とかあります。その場合はString取得では狂います。
基本的にはBCMathを使うのがベストです。
JSの場合 Ajaxで投げる or 計算用関数を自作 or ライブラリを使う。
さて、問題はJSの方です。
実はDECIMAL型も無ければ、PHPのような計算用関数もありません。
マジで無いです。
仕方ないからString(1800 * 1.08)しようか…
>> 1944.0000000000002 <<
PHPみたいに表示用に丸めてくれないので! 文字列にしても無駄です!!
JSで実現しようとすると、かなり面倒な関数を書かなきゃならないようですね。
こちらでやり方が解説されています。
自分で関数をゴリゴリ書くか、世にある小数点計算用ライブラリを使うか。
僕はもうAjaxで投げてPHPに処理させるで良いかな! と思います!
JSで計算する必要があるときって、基本的に、一度に一つや二つの計算しかしないので効率度外視。
場合によってはPHP側の計算用関数と同じ処理を通せるので、メンテ性も高まって一石二鳥です。