C#でアプリケーションを開発する際、通常の整数型であるintやlongでは収まりきらない巨大な数値を扱う場面があります。
例えば、天文学的な計算、高度な暗号アルゴリズムの実装、あるいは非常に桁数の多い階乗の計算などが挙げられます。
こうしたニーズに応えるために、.NETには標準でSystem.Numerics名前空間にBigInteger構造体が用意されています。
本記事では、BigIntegerの基本的な使い方から、パフォーマンスを意識した最適化手法まで、実務で役立つ知識を詳しく解説します。
BigIntegerの基本概念と導入方法
BigIntegerは、理論上上限も下限もない整数を表現できる型です。
利用可能なメモリが許す限り、どれほど大きな数値でも正確に保持することができます。
通常のlong型(64ビット整数)の最大値が約922京であるのに対し、BigIntegerは数万桁、数億桁といった数値も扱うことが可能です。
この型を使用するには、コードの冒頭でusing System.Numerics;を宣言する必要があります。
また、BigIntegerは「不変(Immutable)」な構造体であるため、一度作成した値を後から変更することはできず、演算結果は常に新しいインスタンスとして返されます。
主要な数値型との比較
BigIntegerが他の数値型とどのように異なるのか、以下の表にまとめました。
| 型名 | ビット幅 | 最大値(概数値) | 特徴 |
|---|---|---|---|
| int | 32bit | 約21億 | 最も一般的で高速 |
| long | 64bit | 約922京 | 大きな整数の標準 |
| double | 64bit | 約1.8 × 10^308 | 浮動小数点数(精度に限界あり) |
| BigInteger | 可変 | 制限なし(メモリ依存) | 任意の精度を持つ整数 |
実践的なBigIntegerの使い方
ここでは、BigIntegerを用いた具体的なコーディング手法について見ていきましょう。
初期化と型変換
BigIntegerのインスタンスを作成するには、いくつかの方法があります。
最も簡単なのは、既存の整数型から暗黙的に変換する方法や、文字列をパースする方法です。
using System;
using System.Numerics;
class Program
{
static void Main()
{
// long型からの代入
BigInteger numberFromLong = 9223372036854775807L;
// 非常に長い文字列からのパース
BigInteger largeNumber = BigInteger.Parse("1234567890123456789012345678901234567890");
// 16進数文字列からの変換
BigInteger hexNumber = BigInteger.Parse("7FFFFFFFFFFFFFFFFFFF", System.Globalization.NumberStyles.HexNumber);
Console.WriteLine($"文字列から生成: {largeNumber}");
Console.WriteLine($"16進数から生成: {hexNumber}");
}
}
文字列から生成: 1234567890123456789012345678901234567890
16進数から生成: 602214082240514333332031
基本的な算術演算
BigIntegerは演算子オーバーロードをサポートしているため、通常のint型などと同様に+、-、*、/、%といった演算子がそのまま使用できます。
ただし、割り算(/)の結果は整数部のみが残り、小数点以下は切り捨てられる点に注意してください。
BigInteger a = BigInteger.Parse("10000000000000000000000000000");
BigInteger b = 2;
BigInteger sum = a + b;
BigInteger product = a * b;
BigInteger quotient = a / b;
Console.WriteLine($"加算: {sum}");
Console.WriteLine($"乗算: {product}");
Console.WriteLine($"除算: {quotient}");
高度な数学計算とメソッド
BigInteger構造体には、単純な四則演算以外にも、巨大な数値を扱うための静的メソッドが豊富に用意されています。
べき剰余と最大公約数
暗号化アルゴリズムなどで頻繁に使用されるのが、ModPowメソッドとGreatestCommonDivisorメソッドです。
ModPow(value, exponent, modulus)は、「(value ^ exponent) % modulus」を非常に効率的に計算します。
もし自前で「巨大な値を累乗してから剰余を取る」という処理を記述すると、累乗の時点でメモリ不足(OutOfMemoryException)を引き起こす可能性がありますが、ModPowメソッドはこの中間結果を巨大化させずに計算します。
BigInteger baseVal = 123456789;
BigInteger exp = 987654321;
BigInteger mod = 1000000007;
// (baseVal ^ exp) % mod を計算
BigInteger result = BigInteger.ModPow(baseVal, exp, mod);
// 最大公約数を求める
BigInteger gcd = BigInteger.GreatestCommonDivisor(120, 180);
Console.WriteLine($"べき剰余結果: {result}");
Console.WriteLine($"最大公約数: {gcd}");
べき剰余結果: 554316901
最大公約数: 60
パフォーマンスの最適化手法
BigIntegerは非常に強力ですが、無計画に使用するとパフォーマンスの低下を招くことがあります。
特に計算負荷が高い処理や、メモリの割り当てが頻繁に発生するループ内での利用には注意が必要です。
不変性とメモリ割り当ての回避
BigIntegerは不変であるため、演算のたびに新しいオブジェクトがヒープに割り当てられます。
例えば、非常に長いループ内で加算を繰り返す場合、ガベージコレクション(GC)の負荷が増大します。
このような場合、可能であれば計算の回数を減らすアルゴリズムを採用するか、後述するSpan<T>を活用した低レイヤの操作を検討してください。
Span<byte>を活用した効率的な操作
.NET Core以降(および最新の.NET)では、Span<byte>を使用してBigIntegerのバイナリデータを直接扱うことが可能です。
これにより、文字列を経由せずに数値を構築したり、バイト配列のコピーを最小限に抑えたりすることができます。
byte[] buffer = new byte[32];
// 外部ソースやネットワークから取得したバイト列を想定
Random.Shared.NextBytes(buffer);
// ReadOnlySpan<byte>からBigIntegerを作成
BigInteger fastNum = new BigInteger(buffer, isUnsigned: true, isBigEndian: false);
// バイト列への書き戻しも効率的に行える
Span<byte> destination = stackalloc byte[32];
if (fastNum.TryWriteBytes(destination, out int bytesWritten))
{
// 成功時の処理
}
ビット操作による高速化
数値の2倍、4倍、8倍といった計算や、2のべき乗での除算を行う場合、乗算・除算演算子を使うよりもビットシフト演算(<<, >>)を使用する方が高速です。
BigIntegerもビット演算をサポートしており、内部的なビット表現を効率よく操作できます。
実際の活用シーン:暗号化と科学技術計算
BigIntegerが最も活躍する分野の一つが、公開鍵暗号方式(RSAなど)の実装です。
RSA暗号では、数百桁以上の素数同士を掛け合わせた巨大な合成数を使用するため、long型では到底太刀打ちできません。
また、科学技術計算においては、シミュレーションの過程で非常に小さな誤差も許されない状況下で、整数のまま巨大な値を保持し続けるために利用されます。
階乗計算におけるBigIntegerの利用例
100の階乗(100!)は、150桁を超える数値になります。
以下のコードは、BigIntegerを用いて正確に階乗を求める例です。
static BigInteger Factorial(int n)
{
BigInteger result = 1;
for (int i = 2; i <= n; i++)
{
result *= i;
}
return result;
}
// 100! を計算
Console.WriteLine(Factorial(100));
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
まとめ
C#のBigIntegerは、メモリの限界まで数値を扱える強力なツールであり、標準の整数型では対応できない領域をカバーします。
その一方で、不変構造体である特性や、複雑な演算における計算コストを理解しておくことが、高品質なプログラムを書くための鍵となります。
特に、ModPowのような専用メソッドの活用や、Span<T>によるメモリ管理の最適化は、大規模な計算を行う上で欠かせないテクニックです。
まずは基本的な四則演算から使い始め、必要に応じてビット演算や高度なメソッドを取り入れて、巨大な数値計算をマスターしてください。
適切な型選択と最適化を行うことで、C#による数値計算の可能性は大きく広がるはずです。
