C #3.0 で多倍長演算 (Microsoft.Dynamic.dll)

C#多倍長整数ライブラリ

 ここ数日 IronPythonC#の連携について調べてたんですが、IronPythonに同梱されてる DLL に多倍長整数クラス Microsoft.Scripting.Math.BigIntegerがあることに気づきました。

 C# というか .net framework 3.5 まで多倍長演算ライブラリがありませんでした。公式ライブラリでの唯一の解決方法は .net framework 2.0 で配布されてた J# の BigInteger の利用でした。
 この J#、 .net framework 3.0 でリストラされ、VS2008には入ってません。まぁ、今でも2.0時代のライブラリは使えるんですが、消えゆくJ#をいつまでも使うのはどこか抵抗がある気がします。

 その代替手段として、こんな方法もありなんじゃないかと。

サンプルコード

 VisualC# 2008 Express と IronPython 2.6.1 で動作確認。

 IronPythonここからダウンロードできます。
 インストーラー起動するのが嫌な人は、download の下にある 「More View all downloads」からIronPython-XXX-Bin.zip を落とすといいでしょう。必要なのはその中にある「Microsoft.Dynamic.dll」だけです。

 Consoleアプリのプロジェクトを作って参照設定でMicrosoft.Dynamic.dll を参照。プロジェクト名は SampleBigIntegerとしました。

テスト実行クラス
//////////////////////////////////////////////////////
// Program.cs
//////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Scripting.Math;

namespace SampleBigInteger
{
    class Program
    {
        static void Main(string[] args)
        {
            // 2の1000乗を求める。
            BigInteger a = 2;
            Console.WriteLine("2^1000 = {0}", a.Power(1000));

            // 1000の階乗を求める。
            Console.WriteLine("1000! = {0}", BigIntegerHelper.Factorial(1000));

            // --------------------------------------------
            // 文字列からBigIntegerを生成して計算
            // --------------------------------------------
            BigInteger n1 = BigIntegerHelper.CreateFromString("11111111111111111111111111111111111111111111111111");
            BigInteger n2 = BigIntegerHelper.CreateFromString("777777777777777777777777777777777777");

            // 和
            BigInteger n3 = n1 + n2;
            Console.WriteLine("plus  : {0}", n3);

            // 積
            BigInteger n4 = n1 * n2;
            Console.WriteLine("multi : {0}", n4);

            // 商
            BigInteger n5 = n1 / n2;
            Console.WriteLine("div   : {0}", n5);

            // 剰余
            BigInteger n6 = n1 % n2;
            Console.WriteLine("mod   : {0}", n6);

            //他にもMicrosoft.Scripting.Math.BigIntegerには、
            // Power, Square, Log などのメソッドがあります。
        }
    }
}
BigIntegerのヘルパー
//////////////////////////////////////////////////////
// BigIntegerHelper.cs
//////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Scripting.Math;

namespace Microsoft.Scripting.Math
{
    class BigIntegerHelper
    {
        /// <summary>
        /// 十進数文字列チェック用正規表現オブジェクト
        /// </summary>
        private static readonly Regex digitRegex = new Regex(@"^-?\d+$");

        /// <summary>
        /// 文字列を分割処理するときの分割長
        /// </summary>
        private static readonly int splitLength = int.MaxValue.ToString().Length - 1;

        /// <summary>
        /// 文字列を何進数として扱うか
        /// </summary>
        private static readonly int numberBase = (int)System.Math.Pow(10, splitLength);

        /// <summary>
        /// 文字列からBigIntegerのインスタンスを生成。
        /// 十進数のみサポート。radixを指定できるようにしたらもっといいかも。
        /// </summary>
        /// <param name="number">0〜9のみで構成された十進数をあらわす文字列。先頭文字に限って'-'を許容する</param>
        /// <returns>生成したBigIntegerオブジェクト</returns>
        public static BigInteger CreateFromString(string number)
        {
            if (!digitRegex.IsMatch(number))
            {
                throw new ArgumentException();
            }

            BigInteger result = 0;
            int startpos = 0;

            int sign = 1;
            if (number.StartsWith("-"))
            {
                sign = -1;
                startpos++;
            }

            int rest = number.Length % splitLength;
            if (rest != 0)
            {
                string subst = number.Substring(0, rest);
                result = uint.Parse(subst);
                startpos += rest;
            }

            for (; startpos < number.Length; startpos += splitLength)
            {
                string substr = number.Substring(startpos, splitLength);
                result = result * numberBase + uint.Parse(substr);
            }

            return result * sign;
        }

        /// <summary>
        /// 階乗計算
        /// </summary>
        /// <param name="number">階乗を求める数</param>
        /// <returns>計算結果</returns>
        public static BigInteger Factorial(uint number)
        {
            BigInteger result = 1;
            for (; number > 1; number--)
            {
                result *= number;
            }
            return result;
        }

        /// <summary>
        /// 階乗計算
        /// </summary>
        /// <param name="number">階乗を求める数</param>
        /// <returns>計算結果</returns>
        public static BigInteger Factorial(BigInteger number)
        {
            if (number.IsNegative())
            {
                throw new ArgumentException();
            }

            return Factorial((uint)number);
        }
    }
}

 J#のBigIntegerと比べると、演算子オーバーロードのおかげで四則演算が組み込み整数同様に行えます。

 ちなみに .net framework 4.0 では晴れて多倍長ライブラリが標準に入ってくるらしいです。そうなったらこんなことする必要もなくなります。