[PHP]高精度計算の鍵:gmp_sqrtrem関数完全ガイド – 平方根と剰余の同時取得

PHP

はじめに

高精度な数値計算、特に大きな整数の平方根計算において、PHPの標準関数では精度の限界があります。前回の記事ではgmp_sqrt関数について解説しましたが、今回はその発展形であるgmp_sqrtrem関数について詳しく掘り下げていきます。この関数は平方根だけでなく、その剰余も同時に取得できる強力なツールです。

gmp_sqrtrem関数とは?

gmp_sqrtremは、GMP(GNU Multiple Precision)拡張モジュールが提供する関数で、任意の精度の大きな非負整数の平方根とその剰余を同時に計算します。数学的には、整数nに対して「s²+r=n」となるような整数sとrを求める関数です。

基本構文

gmp_sqrtrem(GMP|string|int $num): array

パラメータ

  • $num: 平方根と剰余を計算したいGMP数値オブジェクト、整数、または数値を表す文字列(負の数は指定できません)

戻り値

  • 長さ2の配列を返します
    • 0番目の要素: 入力された数値の平方根(整数に切り捨て)をGMPオブジェクトとして返します
    • 1番目の要素: 剰余(元の数から平方根の2乗を引いた値)をGMPオブジェクトとして返します
  • つまり、[$s, $r]が返され、$s * $s + $r = $numとなります

基本的な使用例

まずは、gmp_sqrtremの基本的な使い方を見てみましょう:

<?php
// 完全平方数の場合
$perfect = 25;
list($sqrt, $remainder) = gmp_sqrtrem($perfect);
echo "25の平方根: " . gmp_strval($sqrt) . "\n";  // 出力: 5
echo "剰余: " . gmp_strval($remainder) . "\n";    // 出力: 0

// 完全平方数でない場合
$nonPerfect = 10;
list($sqrt, $remainder) = gmp_sqrtrem($nonPerfect);
echo "10の平方根: " . gmp_strval($sqrt) . "\n";   // 出力: 3
echo "剰余: " . gmp_strval($remainder) . "\n";     // 出力: 1
// 3² = 9, 10 - 9 = 1

// 大きな数値の場合
$bigNumber = "12345678901234567890123456789";
list($sqrt, $remainder) = gmp_sqrtrem($bigNumber);
echo $bigNumber . "の平方根: " . gmp_strval($sqrt) . "\n";
echo "剰余: " . gmp_strval($remainder) . "\n";
// 出力:
// 平方根: 3513641828929
// 剰余: 87654323456789
?>

様々な入力形式での使用

gmp_sqrtrem関数は柔軟な入力形式をサポートしています:

<?php
// 整数を直接渡す
list($sqrt, $rem) = gmp_sqrtrem(16);
echo "16の平方根: " . gmp_strval($sqrt) . ", 剰余: " . gmp_strval($rem) . "\n"; 
// 出力: 4, 0

// 文字列形式の数値
list($sqrt, $rem) = gmp_sqrtrem("100");
echo "100の平方根: " . gmp_strval($sqrt) . ", 剰余: " . gmp_strval($rem) . "\n"; 
// 出力: 10, 0

// 16進数表記(プレフィックス「0x」が必要)
list($sqrt, $rem) = gmp_sqrtrem("0x100");
echo "0x100(256)の平方根: " . gmp_strval($sqrt) . ", 剰余: " . gmp_strval($rem) . "\n"; 
// 出力: 16, 0

// GMP数値オブジェクト
$num = gmp_init("10000000001");
list($sqrt, $rem) = gmp_sqrtrem($num);
echo "10000000001の平方根: " . gmp_strval($sqrt) . ", 剰余: " . gmp_strval($rem) . "\n"; 
// 出力: 100000, 1
?>

実践的な応用例

例1: 完全平方数かどうかを判定する関数(改良版)

<?php
function isPerfectSquare($num) {
    // 負の数は完全平方数ではない
    if (gmp_sign($num) < 0) {
        return false;
    }
    
    // 平方根と剰余を計算
    list(, $remainder) = gmp_sqrtrem($num);
    
    // 剰余が0なら完全平方数
    return gmp_cmp($remainder, 0) === 0;
}

// テスト
$testNumbers = [16, 25, 100, 10, 17, 12345654321];
foreach ($testNumbers as $number) {
    echo $number . "は";
    echo isPerfectSquare($number) ? "完全平方数です。\n" : "完全平方数ではありません。\n";
}
?>

例2: 高精度な平方根の近似値計算(gmp_sqrtremを活用)

<?php
function preciseSqrt($num, $precision = 20) {
    // 正の数かチェック
    if (gmp_sign($num) < 0) {
        throw new InvalidArgumentException("負の数の平方根は計算できません");
    }
    
    // 平方根と剰余を計算
    list($intPart, $remainder) = gmp_sqrtrem($num);
    
    // 完全平方数なら小数部分はなし
    if (gmp_cmp($remainder, 0) === 0) {
        return gmp_strval($intPart);
    }
    
    // 小数点以下の計算のためのスケーリング
    $result = gmp_strval($intPart) . ".";
    $currentNum = $num;
    $currentIntPart = $intPart;
    
    // バビロニア法(ニュートン法)による近似計算
    for ($i = 0; $i < $precision; $i++) {
        // 100倍にスケールアップ
        $currentNum = gmp_mul($currentNum, "10000");
        
        // 現在の整数部を100倍
        $currentIntPart = gmp_mul($currentIntPart, "100");
        
        // 次の2桁を求める
        $nextTwoDigits = 0;
        for ($d = 0; $d <= 99; $d++) {
            $test = gmp_add($currentIntPart, $d);
            $squared = gmp_mul($test, $test);
            if (gmp_cmp($squared, $currentNum) > 0) {
                $nextTwoDigits = $d - 1;
                break;
            }
            if ($d == 99) {
                $nextTwoDigits = 99;
            }
        }
        
        // 結果に追加
        $result .= str_pad($nextTwoDigits, 2, "0", STR_PAD_LEFT);
        
        // 次の計算のための準備
        $currentIntPart = gmp_add($currentIntPart, $nextTwoDigits);
        $currentNum = gmp_sub($currentNum, gmp_mul($currentIntPart, $currentIntPart));
    }
    
    return $result;
}

// テスト
$testValues = [2, 3, 10, 12345];
foreach ($testValues as $value) {
    echo $value . "の平方根(高精度近似値): " . preciseSqrt($value, 5) . "\n";
    // PHPの標準関数と比較
    echo "標準関数での計算結果: " . sqrt($value) . "\n\n";
}
?>

例3: 二次無理数の連分数展開

<?php
function continuedFractionSqrt($n, $terms = 10) {
    // 完全平方数の場合は単純な結果
    if (isPerfectSquare($n)) {
        $sqrt = gmp_sqrt($n);
        return [gmp_strval($sqrt)];
    }
    
    // 初項(整数部分)を計算
    list($a0, ) = gmp_sqrtrem($n);
    $result = [gmp_strval($a0)];
    
    // 連分数の各項を計算
    $m = 0;
    $d = 1;
    $a = $a0;
    
    for ($i = 1; $i <= $terms; $i++) {
        // 次の項の計算
        $m = gmp_sub(gmp_mul($a, $d), $m);
        $d = gmp_div_q(gmp_sub($n, gmp_pow($m, 2)), $d);
        $a = gmp_div_q(gmp_add($a0, $m), $d);
        
        $result[] = gmp_strval($a);
        
        // 循環を検出(二次無理数の連分数は必ず循環する)
        if (gmp_cmp($a, gmp_mul($a0, 2)) === 0) {
            $result[] = "..."; // 循環を表す
            break;
        }
    }
    
    return $result;
}

// √2, √3, √5, √7の連分数展開
$numbers = [2, 3, 5, 7, 23];
foreach ($numbers as $n) {
    $cf = continuedFractionSqrt($n);
    echo "√{$n}の連分数展開: [" . implode(", ", $cf) . "]\n";
}
?>

数学的応用:ディオファントス近似

gmp_sqrtremを使って、有理数による無理数の近似(ディオファントス近似)を計算する例:

<?php
function diophantineApproximation($n, $maxDenominator = 1000) {
    // 平方根を計算
    list($intPart, ) = gmp_sqrtrem($n);
    $sqrtValue = gmp_add($intPart, 0); // コピーを作成
    
    // 連分数の計算に必要な変数を初期化
    $m = 0;
    $d = 1;
    $a = $intPart;
    
    // 収束分数の計算に必要な変数
    $p_1 = 1;
    $q_1 = 0;
    $p_2 = gmp_strval($intPart);
    $q_2 = 1;
    
    $bestApproximations = [];
    
    while (gmp_cmp($q_2, $maxDenominator) <= 0) {
        // 次の項の計算
        $m = gmp_sub(gmp_mul($a, $d), $m);
        $d = gmp_div_q(gmp_sub($n, gmp_pow($m, 2)), $d);
        $a = gmp_div_q(gmp_add($intPart, $m), $d);
        
        // 収束分数の計算
        $p = gmp_add(gmp_mul($a, $p_2), $p_1);
        $q = gmp_add(gmp_mul($a, $q_2), $q_1);
        
        // 近似精度を計算
        $p_squared = gmp_pow($p, 2);
        $q_squared = gmp_pow($q, 2);
        $n_times_q_squared = gmp_mul($n, $q_squared);
        
        // |p²/q² - n| = |p² - nq²|/q²
        $error = gmp_abs(gmp_sub($p_squared, $n_times_q_squared));
        $error_div_q_squared = gmp_div_q(
            gmp_mul($error, "1000000000000"), // スケーリングして精度を上げる
            $q_squared
        );
        
        $bestApproximations[] = [
            "p" => gmp_strval($p),
            "q" => gmp_strval($q),
            "fraction" => gmp_strval($p) . "/" . gmp_strval($q),
            "decimal" => bcdiv(gmp_strval($p), gmp_strval($q), 15),
            "error" => gmp_strval($error_div_q_squared)
        ];
        
        // 次の計算のための準備
        $p_1 = $p_2;
        $p_2 = $p;
        $q_1 = $q_2;
        $q_2 = $q;
        
        if (gmp_cmp($q, $maxDenominator) > 0) {
            break;
        }
    }
    
    return $bestApproximations;
}

// √2のディオファントス近似
$n = 2;
$approximations = diophantineApproximation($n, 100);
echo "√{$n}に対する有理数近似:\n";
foreach (array_slice($approximations, 0, 5) as $index => $approx) {
    echo ($index + 1) . ": " . $approx["fraction"] . " ≈ " . $approx["decimal"] . 
         "(誤差: " . $approx["error"] . ")\n";
}
?>

gmp_sqrtrem と関連関数の比較

gmp_sqrtremと他のGMP関数を比較してみましょう:

<?php
$numbers = [10, 16, 25, 100, 12345];

echo "様々な数値の平方根計算の比較:\n";
echo str_pad("数値", 10) . " | " . 
     str_pad("gmp_sqrt", 15) . " | " . 
     str_pad("gmp_sqrtrem[0]", 15) . " | " . 
     str_pad("gmp_sqrtrem[1]", 15) . " | " . 
     "検証\n";
echo str_repeat("-", 70) . "\n";

foreach ($numbers as $num) {
    // gmp_sqrt による計算
    $sqrt = gmp_sqrt($num);
    
    // gmp_sqrtrem による計算
    list($sqrtRem, $remainder) = gmp_sqrtrem($num);
    
    // 検証: sqrt² + remainder = num
    $verification = gmp_add(gmp_pow($sqrtRem, 2), $remainder);
    
    echo str_pad($num, 10) . " | " . 
         str_pad(gmp_strval($sqrt), 15) . " | " . 
         str_pad(gmp_strval($sqrtRem), 15) . " | " . 
         str_pad(gmp_strval($remainder), 15) . " | " . 
         (gmp_cmp($verification, $num) === 0 ? "✓" : "✗") . "\n";
}
?>

性能面での比較

gmp_sqrtgmp_sqrtremのパフォーマンスを比較してみましょう:

<?php
function benchmarkFunctions($numbers, $iterations = 100) {
    $functions = [
        'gmp_sqrt' => function($n) {
            return gmp_sqrt($n);
        },
        'gmp_sqrtrem' => function($n) {
            return gmp_sqrtrem($n);
        }
    ];
    
    $results = [];
    
    foreach ($functions as $name => $func) {
        $start = microtime(true);
        
        for ($i = 0; $i < $iterations; $i++) {
            foreach ($numbers as $num) {
                $func($num);
            }
        }
        
        $end = microtime(true);
        $results[$name] = $end - $start;
    }
    
    return $results;
}

// テストデータの準備
$testNumbers = [
    "100",
    "12345",
    "9999999999",
    "123456789012345678901234567890"
];

// ベンチマーク実行
$benchmarkResults = benchmarkFunctions($testNumbers, 1000);

echo "ベンチマーク結果(1000回の繰り返し):\n";
foreach ($benchmarkResults as $function => $time) {
    echo $function . ": " . number_format($time, 6) . " 秒\n";
}

// 相対パフォーマンスの表示
$fastest = min($benchmarkResults);
echo "\n相対パフォーマンス:\n";
foreach ($benchmarkResults as $function => $time) {
    $relative = $time / $fastest;
    echo $function . ": " . number_format($relative, 2) . "x\n";
}
?>

性能と注意点

性能面での利点

  1. 効率的な計算: gmp_sqrtremは一度の関数呼び出しで平方根と剰余の両方を計算するため、それぞれを別々に計算するよりも効率的です
  2. 高精度: 標準のPHP関数では扱えない巨大な整数に対しても、正確な計算が可能です

注意点

  1. 負の数の扱い: gmp_sqrtremは負の数の平方根を計算できません(実行時エラーになります)
  2. 戻り値の形式: 戻り値は配列なので、list()構文またはインデックスアクセスで各値を取得する必要があります
  3. GMP拡張の必要性: この関数を使用するには、PHPにGMP拡張モジュールがインストールされている必要があります
<?php
// 拡張モジュールがインストールされているか確認
if (extension_loaded('gmp')) {
    echo "GMP拡張モジュールは利用可能です。\n";
} else {
    echo "GMP拡張モジュールがインストールされていません。\n";
}
?>

まとめ

gmp_sqrtrem関数は、PHPでの高精度な平方根計算において強力なツールです。平方根と剰余を同時に取得できるため、完全平方数の判定、数論的な計算、高精度な近似計算などで特に威力を発揮します。

特に数学的アプリケーション、暗号処理、科学計算など、精密な数値計算が必要な場面では、gmp_sqrtremを活用することで計算の精度と効率を大幅に向上させることができます。

GMPライブラリの他の関数と組み合わせることで、PHPでも高度な数値計算が可能になります。大きな整数を扱う必要がある場合は、ぜひGMP拡張モジュールとgmp_sqrtrem関数を活用してみてください。


この記事が、PHPでの高精度な平方根計算に取り組む開発者の方々のお役に立てば幸いです。数値計算の難しい問題も、適切なツールを使えば簡単に解決できることがあります。GMPライブラリの強力な機能を使いこなして、より高度な数値計算を実現しましょう。

タイトルとURLをコピーしました