PHPで小数点以下の数値を処理する際、使用するメソッドによってそれぞれ処理の方法が異なるため、きちんと仕様を確認して利用する必要があります。備忘録として以下にまとめます。
round():小数点以下の四捨五入
引数で指定した桁数から小数点第◯位を四捨五入した値を返します。
round(3.56, 2); // 小数点第二位を四捨五入 3.6
round(3.56, 2); // 小数点第一位を四捨五入 4
floor():小数点以下切り捨て
引数の値以下で、計算上の整数と等しい、最大の (正の無限大にもっとも近い) double値を返します。小数点以下切り捨てです。
floor(1.34); // 1.0
ceil():小数点以下の数値切り上げ
引数の値以上で、計算上の整数と等しい、最小の (負の無限大にもっとも近い) double値を返します。小数点以下の数値切り上げです。
ceil(1.34); // 2.0
浮動小数点の精度
これらの関数の処理を踏まえて、以下のコードで出力される数字は何なのか考えてみましょう。
$num = (0.1 + 0.7) * 10;
echo floor($num); // ??
上記のコードで出力されるのは、8...ではなく、7です。
普通に考えて(0.1+0.7)=0.8*10=8
だと思うのですが、なぜこうなってしまうのか。その答えはPHP公式ドキュメントに記載されています。
PHP: 浮動小数点数 - Manual
http://jp2.php.net/manual/ja/language.types.float.php
さらに、十進数では正確な小数で表せる有理数、たとえば 0.1 や 0.7 は、 二進数の浮動小数点数としては正確に表現できません。 これは、仮数部をいくら大きくしても同じです。 したがって、それを内部的な二進数表現に変換する際には、どうしても多少精度が落ちてしまいます。 その結果、不思議な結果を引き起こすことがあります。たとえば、 floor((0.1+0.7)*10) の結果はたいてい 7 となるでしょう。おそらくは 8 を想定していらっしゃるでしょうが、そのようにはなりません。 これは、(この計算結果の) 内部的な値が 7.9999999999999991118... のようになっているからです。
0.1と書いたとしても、正確にその精度を表現することが難しいためこのようなことが起きてしまうそうです。
この問題の対応策としては下記の記事が参考になります。
PHPの少数演算における切り上げ切捨て問題
http://www.psi-net.co.jp/blog/?p=277
round()
で出した値が-0
になる件
この少数演算において、round()
の結果が-0
となっている不思議な箇所を見つけました。
具体的にどのような値を四捨五入しているのか調べてみたところ、下記のようになっていました。
round(-0.1429, 0); // -0
このround()
について調べたところ、負の値を四捨五入すると、負の値に近くなり、正の値を四捨五入すると、正の値に近くなることがわかりました。
どういうことかといいますと、例えば-0.5
や0.5
をround()すると、それぞれ-1
、1
という結果になります。
//四捨五入
echo round(0.5); // => 1
echo round(-0.5); // => -1
この時、裏側でどういう処理が行われてるのかというと、絶対値の0.5
を四捨五入して、その結果にマイナスをつけた-0.5
を返しています。
つまり、下記のような-0.1
というような少数を四捨五入しようとすると、0.1
を四捨五入した結果0
となり、0
にマイナスをつけた-0
が返される……ということでした。
round(-0.1429, 0); // -0
マイナスの値が来たときにどのような挙動をするのかは、下記のページのグラフが参考になりました。
浮動小数点数の丸め処理を見比べてみる
http://d.hatena.ne.jp/hnw/20111029
参考記事
http://fdays.blogspot.jp/2010/05/php_3907.html
http://news.mynavi.jp/articles/2012/10/12/qa_php/002.html