四捨五入と偶数丸め (PHP8.4以降)
PHP8.4で、round()関数に丸め方のオプションが追加されました。
端数がちょうど5だった場合の処理が選べるようになります。オプションは
- RoundingMode::HalfAwayFromZero : 0から離れる方向へ丸める
- RoundingMode::HalfTowardsZero : 0に近づく方向へ丸める
- RoundingMode::HalfEven : 偶数側に丸める
- RoundingMode::HalfOdd : 奇数側に丸める
です。未指定だった場合の処理はRoundingMode::HalfAwayFromZeroです。※他のオプションについては補足を参照
偶数丸め(銀行丸め)
単純な四捨五入だと、端数5を切り上げるため、データ全体としては正の方向へわずかにずれます。一方で、偶数丸めだと、端数5は切り上げる場合と切り捨てる場合があり、データ全体の誤差は抑えられます。偶数丸めは銀行丸めとも呼ぶらしいです。
実際に実行してみた
以下のようなコードを実行してみました。
// creates a sample $data 1 - 99
$data = [];
for($i = 1; $i < 100; $i++){
$data[] = $i ;
}
$resultRaw = 0;
$resultHalfAwayFromZero = 0;
$resultHalfEven = 0;
// pickups randomly from $data
$times = 10000;
for($i = 0; $i < $times; $i++){
shuffle($data);
$resultRaw += $data[0];
$resultHalfAwayFromZero += round($data[0],-1, RoundingMode::HalfAwayFromZero);
$resultHalfEven += round($data[0],-1, RoundingMode::HalfEven);
}
echo '元データ: ' . $resultRaw . '<br>' . PHP_EOL;
echo '四捨五入: ' . $resultHalfAwayFromZero . '<br>' . PHP_EOL;
echo '偶数丸め: ' . $resultHalfEven . '<br>' . PHP_EOL;
実行結果は↓ のようになりました。
元データ: 499984
四捨五入: 504890
偶数丸め: 499910
偶数丸めのほうが、元データとの差が少なかったです。
既存システムの変更は必要か?
偶数丸めのほうが誤差が少ないようです。とはいえ、では偶数丸めを採用すればよいか、というと、新規プロダクトなら採用すればよいでしょう。一方で既存システムでは偶数丸めに変更する必要性は低いと思われます。
既存システムが四捨五入だった場合には、もちろん誤算の観点からいえばより良くなります。しかし、いままで四捨五入が受け入れられていたのであれば、四捨五入による誤差も許容されていたわけです。あえて変更しなくても良さそうに思います。
補足
round()にはオプションが他にも用意されています。
- RoundingMode::TowardsZero : 0に近づく方向へ丸める
- RoundingMode::AwayFromZero : 0から離れる方向へ丸める
- RoundingMode::NegativeInfinity : 小さい方に丸める
- RoundingMode::PositiveInfinity : 大きい方に丸める
これらのオプションを指定すると、端数部分が5より大きい・小さいの判定は行わず、端数の丸め処理を行います。切り上げceil()、切り捨てfloor()と同様の操作ですね。
ただ、ceil()、floor()は小数点第何位という指定が無い(PHP8.4時点では無い)ので、端数処理をする桁を指定した上で切り上げ・切り捨てしたい場合には、round()のオプションが役立ちます。


