C#のBigInteger型で超巨大な数字を扱う

C#の組み込み型で最も大きい値を扱える型は、double型です。最大値は、1.7x 10の308乗とありますが、浮動小数点の精度の関係で実際扱える有効桁数が17桁程度なので極大の数値に対し有効桁外の演算を行うと計算結果に反映されない…みたいな状態になります。

従って、より大きい値を扱う場合 decimal という型を使用しますが、こちらは ±1.0 x 1028 程度が扱えます。decimal 型は有効桁数 28桁で最大値が、79,228,162,514,264,337,593,543,950,335(7穰9228秭1625垓1426京4337兆5935億4395万0335)です。

ulong の最大値の10億倍(10の9乗)もあるので十分大きいため、余程の事が無い限り困らないのですが、これより大きな値を扱う場合、任意長整数型型の「BigInteger」という型があります。このクラスは整数限定ですが(計算コストを無視すれば)事実上ほぼ無限の大きさの「整数」を扱うことができます。

今回はこの、BigInteger の簡単な使い方を紹介します。

使用方法

早速、使用方法を見ていきたいと思います。

古いバージョンの.NETの場合、使用するには、まず、System.Numeric.dllをプロジェクトのアセンブリ参照へ追加してください(新しいバージョンでは不要です)

using System.Numerics; // ★追加

public static void Main(string[] args)
{
    // 先ほどの最大値をコンストラクタの指定して初期化
    var big = new BigInteger(decimal.MaxValue);
    // 最大値にさらに1を加える!
    big = big + 1;

    Console.WriteLine(big);
    // > 79228162514264337593543950336 ← decimal.MaxValue + 1 になってる
}

BigDecimalを宣言するときにコンストラクタにint, uint, long, ulong, float, double, decimal が指定できます。

また、それより大きい数字を最初から作りたい場合 Parseメソッドを使用します。

var big = BigInteger.Parse("79228162514264337593543950336"/*decimal.MaxValue + 1*/);
Console.WriteLine(big);
// > 79228162514264337593543950336

var big2 = BigInteger.Parse("99999999999999999999999999999999999999999999999");
Console.WriteLine(big2);
// > 99999999999999999999999999999999999999999999999

またint型などに使用できるすべての演算子が使用できるため、加算、減算、比較などは通常の組み込み型と同じように使用できます。

// BigInteger同士で演算子の使用
var b1 = new BigInteger(decimal.MaxValue);
var b2 = new BigInteger(decimal.MinValue);
BigInteger b3 = b1 * b2;
Console.WriteLine(b3);
// 普通に結果が出る(マイナスもいける)
// > -6277101735386680763835789423049210091073826769276946612225

BigInteger b4 = b3 - 100; // int型との演算もできる
Console.WriteLine(b4);
// >-6277101735386680763835789423049210091073826769276946612325

BigInteger b5 = b4 << 10; // シフト演算もできる
Console.WriteLine(b5);
// > -6427752177035961102167848369202391133259598611739593331020800

また、(リファレンスでは)宣言が以下のように、SerializableAttribute属性がついていてシリアライズが可能らしく、この型を直接シリアライズすることも可能なようです。(マニュアルだけ見て書いてます。

[SerializableAttribute]
public struct BigInteger : ...

また、以下のように、意味がほぼ無いですが演算子が定義されている関係で、for文にも使用できます。(自分が寿命であの世に行くより圧倒的に時間がかかりそうです)

// for文にも使える
var bmax = BigInteger.Parse("10000000000000000000000000000000000000000");
for(var b = new BigInteger(0); b < bmax; b++)
{
    Console.WriteLine(b);
}

他にもPowやLog、Log10関数があるのである程度数学的にも使える?と思います。ここら辺は微妙ですね。

パフォーマンスはどうか?

で、気になるのがパフォーマンスですが、10100000まで計測してみました。

public static void Main(string[] args)
{
    // 計測対象の変数
    var b = new BigInteger(10);
    // 1回の計算にかかる時間の計測用
    var w = new Stopwatch();
    // 操作の合計時間の計測用
    var wtotal = new Stopwatch();

    // 結果をCSVに書き出す
    using (StreamWriter sw = new StreamWriter(File.Open(@"d:\log.csv", FileMode.Create)))
    {
        for (int i = 0; i < 100000; i++)
        {
            w.Start();
            wtotal.Start();

            b *= 10; // 10倍ずつしていく

            w.Stop();
            wtotal.Stop();

            sw.WriteLine($"{i}, {w.ElapsedMilliseconds}");
            w.Reset();
        }
    }

    Console.WriteLine(b);
    Console.WriteLine($"Total={wtotal.ElapsedMilliseconds}");
    // > Total=1795

    Console.ReadLine();
}

上記の結果ですがデバッグモードで実行したのですが、中ほどの「sw.WriteLine($"{i}, {w.ElapsedMilliseconds}");」は出力がで出力する行はすべてゼロ(1ミリ秒未満で処理が完了)で全体では1.795秒かかりました。個別の計算に時間がかかるという事はこの程度の数値の規模では全くないようです。従って、1秒間に百回程度の計算では全く問題ないかと思います。

最後に:どんなところで使用するか?

最近のモバイルゲームで、クリッカーゲームというジャンルがありますが、それに使えそうな雰囲気かなと思います。Unityにはクラスがないとかだと笑いますが、、、ありますよね?数値も10100当たりの数字が普通に使えるので問題ないのではないでしょうか?