プログラミング中で用いられる数値には上限と下限が決まっている言語は多く、PHP もそのひとつです(例えばPythonは上限を定めておらず、計算資源が許す限り大きな数字を扱えます)。よくある PHP の使用方法でこの上限が問題になることはまずありませんが、この限界を突破する例、PHP_INT_MAXを突破した加算の例を紹介します。
素の PHP で問題となるのは次の状態です。
<?php var_dump( PHP_INT_MAX );// int(9223372036854775807) var_dump( (PHP_INT_MAX + 1) ); // float(9.2233720368548E+18) var_dump( (PHP_INT_MAX + 1 - PHP_INT_MAX) );// float(0) var_dump( (PHP_INT_MAX - PHP_INT_MAX + 1) );// int(1) var_dump( (PHP_INT_MAX + PHP_INT_MAX) );// float(1.844674407371E+19)
PHP の整数値の限界は組み込み定数の PHP_INT_MAX で表現されています。計算結果がこの値を超えると値は自動で浮動小数点数となり、その計算には誤差が出てきます。この浮動小数点数の状態にならず整数として加算を行います。これを実装したコードが次です。
<?php
/**
* 符号なしの整数(0以上の値)を PHP_INT_MAX を超えて足し合わせる
* @param int $a
* @param int $b
* @return string
*/
function unsignedUnlimitedAdd(int $a, int $b): string
{
// [1の位の値, 10の位の値, ...] と並んだ配列にする
$aNumList = array_reverse(str_split((string)$a));
$bNumList = array_reverse(str_split((string)$b));
// ループ回数を決定する
$maxLoop = max(count($aNumList), count($bNumList));
$carry = 0;// 繰り上がり
$result = [];// 計算結果を格納
// 各位の数字を一つずつ計算する
for($i = 0; $i < $maxLoop; $i++) {
// 今参照している位の値と繰り上がりを足し合わせる
$sum = (int)($aNumList[$i] ?? 0) + (int)($bNumList[$i] ?? 0) + $carry;
// 足し合わせた結果を [1の位の値, 10の位の値, ...] と並んだ配列にする
$sumStr = array_reverse(str_split((string)$sum));
// このループの位の足し合わせの結果の 1 の位はそのまま最終結果に格納
$result[$i] = $sumStr[0];
// 10 の位は次ループの繰り上がり
$carry = $sumStr[1] ?? 0;
}
// 最後に繰り上がりが残っていた場合、それを付け足し
if($carry) {
$result[] = $carry;
}
// 全て連結する。ここで int にして返そうとして int 限界を超えると誤差が現れて台無しになる
return implode("", array_reverse($result));
}
var_dump(unsignedUnlimitedAdd(PHP_INT_MAX, PHP_INT_MAX));
// string(20) "18446744073709551614"
数値型から離れて単に文字列として扱い、一桁ずつ計算していきます。こうすると int 型の限界に触れることなく加算ができます。例は 2 つの int 型の加算という限定的なものですが、同様の考えでより拡張できます。また文字列として一桁ずつ計算する、という方法は誤差を起こさない計算方法であり浮動小数点数について誤差をなるべく起こさず取り扱いたい時にも使えます。