【PHP】小数の指定桁数で四捨五入・切り捨て・切り上げする方法

更新日:2023/03/24

PHPで小数の桁数を指定して、四捨五入や切り捨て、切り上げをおこなう方法を紹介します。
簡単そうな気がしますが、意外と難しいですよ。

 

小数の指定桁数で四捨五入する方法

指定桁数で四捨五入するときは、round()を使用します。

round( 値, 桁数 = 0 , モード = PHP_ROUND_HALF_UP)

桁数が0のとき、小数第一位が四捨五入されて、整数が返ります。
1のときは、小数第二位が四捨五入されて、小数第一位までの値が返ります。

使用例

$value = 12.3456;

echo round( $value , 1 )."\n"; // 12.3
echo round( $value , 2 )."\n"; // 12.35

モードは丸め方を指定します。

モード端数5の時の動作
PHP_ROUND_HALF_UPゼロから離れる方向に丸める2.35 → 2.4 、 -2.35 → -2.4
PHP_ROUND_HALF_DOWNもっとも近い偶数に丸める2.35 → 2.3 、 -2.35 → -2.3
PHP_ROUND_HALF_EVENゼロに近づく方向に丸める2.25 → 2.2 、 -2.25 → -2.2
2.35 → 2.4 、 -2.35 → -2.4
PHP_ROUND_HALF_ODDゼロに近づく方向に丸める2.25 → 2.3 、 -2.25 → -2.3
2.35 → 2.3 、 -2.35 → -2.3

四捨五入のときはPHP_ROUND_HALF_UPで良さそうですね。
これは規定値なので、省略できます。

 

小数の指定桁数で切り捨てる方法

小数を切り捨てる関数は、floor()です。
次のような、構文になっています。

floor( 値 )

桁数の指定ができないので、指定桁数までを整数部分まで移動してfloor()で小数を切り捨てた後、元の桁数に戻します。

ただしfloor()は、指定した数値よりも小さい方向に丸めます。
そのため、マイナス値を指定すると-1を加算して結果になってしまいます。

-2.5 → -2になってほしい → 結果は-3

そこで、マイナス値はプラス値に変換した後に切り捨てを行い、最後に-1を掛けます。

以上をコードにすると、次のようになります。

function round_down($value,$number_of_digits)
{
    $sign = $value >= 0 ? 1 : -1;
    $multiple1 = 10 ** ($number_of_digits+1); // 10 の$number_of_digits+1乗
    $multiple2 = 10 ** ($number_of_digits); // 10 の$number_of_digits乗

    return floor( (abs($value) * $multiple1 ) / 10 ) / $multiple2 * $sign;
}

abs()は、符号を取って絶対値を返す関数です。
プラス値はプラス値のままで、マイナス値はプラス値で返すということです。

上記のコードをよく見ると、floor()の中で、余計に10倍したあと10分の一しています。
しかもn乗計算を2回もしています。少し考えれば一回で済むはずです。
これらは、一応ですが誤差対策です。
詳しくは、このページの誤差対策についてで解説しています。

作成した関数round_down()は、次のように使用します。

$value1 = 2.345;
$value2 = -2.345;

echo round_down( $value1 , 0 )."\n"; // 2
echo round_down( $value2 , 0 )."\n"; // -2
echo round_down( $value1 , 2 )."\n"; // 2.34
echo round_down( $value2 , 2 )."\n"; // -2.34

 

小数の指定桁数で切り上げる方法

小数を切り上げる関数は、ceil()です。
次のような、構文になっています。

ceil( 値 )

こちらも、floor()と同じような問題があります。
問題を加味すると、次のようなコードになります。

function round_up($value,$number_of_digits)
{
    $sign = $value >= 0 ? 1 : -1;
    $multiple = 10 ** $number_of_digits; // 10 の$number_of_digits乗

    return ceil( abs($value) * $multiple ) / $multiple * $sign;
}

小数の指定桁数で切り捨てる方法のコードを、floor()からceil()に変更しただけです。

作成した関数round_up()は、次のように使用します。

$value1 = 2.345;
$value2 = -2.345;

echo round_up( $value1 , 0 )."\n"; // 3
echo round_up( $value2 , 0 )."\n"; // -3
echo round_up( $value1 , 2 )."\n"; // 2.35
echo round_up( $value2 , 2 )."\n"; // -2.35

 

誤差対策について

PHPの数値データは浮動小数点形式で扱われています。
この形式は小数を扱うのが苦手で、実際の数値と誤差があります。
その結果、様々な問題に直面します。

次の計算式は、数値を10倍して小数を切り捨てた後、10分の一にしています。

$f = floor( (0.1 + 0.7) * 10 ) / 10;
echo $f."\n"; // 0.7

結果は0.8になるはずですが、0.7になってしまいました。
理由は0.1と0.7を合計した結果を小数第40位まで確認してみるとわかります。

var_dump(sprintf('%.40f', 0.1 + 0.7))."\n"; // string(42) "0.7999999999999999333866185224906075745821"
var_dump(sprintf('%.40f', (0.1 + 0.7)*10))."\n"; // string(42) "7.9999999999999991118215802998747676610947"

0.8ではなくて、0.79999... になっています。
これを10倍すると7.9999... なので小数以下を切り捨てると7になります。
困ったものです。

しかし、100倍すると80になります。

var_dump(sprintf('%.40f', (0.1 + 0.7)*100))."\n"; // string(43) "80.0000000000000000000000000000000000000000"

この状態から10分の一すれば、最終的に0.8という結果を得られます。

ただし、10 * 10 倍はNGです。

var_dump(sprintf('%.40f', (0.1 + 0.7)*10*10))."\n"; // string(43) "79.9999999999999857891452847979962825775146"

以上から、最初のコードを次のように変更すると想定した結果を求めることができます。

$f = floor( ( (0.1 + 0.7) * 100 / 10 ) ) / 10;
echo $f."\n";
var_dump(sprintf('%.40f', $f))."\n";

なお、0.1 + 0.7のケースのみ確認しています。
そのため、たまたま上手くいっただけかもしれません。

気になる人は、浮動小数点について調べてください。

 

最後に

floor()とceil()は桁数を指定できないばかりか、マイナス値の結果がイメージしたものと異なります。
その点を加味する必要がありますが、なかなか気が付かないですね。

さらに、誤差の問題まであります。
めんどくさいですね。

なお、本来のfloor()とceil()の結果が必要な時は、abs()と符号の掛け算を除去してください。

更新日:2023/03/24

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

このサイトは、リンクフリーです。大歓迎です。