3

これまでの回答

というわけでコードの内訳です。

//Time: ~7s (linear loop algorithm)
//100,000! (456,574 decimal digits)
BigInteger bigIntVar = computeFactorial(100000);

//The first three here are just for comparison and are not actually Base 10.
bigIntVar.ToBase64String() //Time: 00.001s | Base 64 | Tetrasexagesimal
bigIntVar.ToString("x")    //Time: 00.016s | Base 16 | Hexadecimal
bigIntVar.ToBinaryString() //Time: 00.026s | Base 02 | Binary
bigIntVar.ToQuickString()  //Time: 11.200s | Base 10 | String Version
bigIntVar.ToQuickString()  //Time: 12.500s | Base 10 | StringBuilder Version
bigIntVar.ToString()       //Time: 13.300s | Base 10 | Original

元の質問のもの

私はこれに多くの時間を費やしたので、あなたの助けが必要です.

これは巨大な階乗を計算するための個人的なプロジェクトです (例: 100,000!)

これが私のコードです:

using (var stream = new StreamWriter(fileName + ".txt", false))
{
    stream.WriteLine(header);

    var timer = new Stopwatch();    
    timer.Restart();
    //This is the huge BigInteger holding the answer to 100,000!
    stream.WriteLine(saveFactorial.Output.ToString());         
    //Let me be clear: ToString() is directly causing the the 13sec time delay.
    //Not the stream.
    timer.Stop();                   
}

time = (timer.ElapsedMilliseconds / 1000.0).ToString() + "s"; 

MessageBox.Show(time);

10万で!私のマシンでは、計算に約7秒かかります(線形ループアルゴリズム)。

しかし、この標準 IO コードでは、保存に 13 秒かかります。

つまり、適度に計算するよりも作業を保存する方が時間がかかります。

だから私は多分私が使うことができると思った:

BigInteger.ToByteArray();

これは非常に高速に実行されますが、読み取り可能なテキストに保存する方法がわかりませんでした。

上記の方法を使用して、この自作の拡張子を持つテキスト ファイルにバイナリ文字列を書き込むことができます。

ToBinaryString

//Usage: string bigIntBinary = bigIntVar.ToBinaryString();
public static string ToBinaryString(this BigInteger source)
{
    //If you lookup the ToByteArray() method...
    //It actually stores the bytes in reverse order.
    var bigIntBytes = source.ToByteArray().Reverse();

    StringBuilder bigIntBinary = new StringBuilder();

    foreach (var bigIntByte in bigIntBytes)
    {
       bigIntBinary.Append(Convert.ToString(bigIntByte, 2).PadLeft(8, '0'));
    }

    return bigIntBinary.ToString();
}

ToBase64String

    ////Usage: string bigIntBase64 = bigIntVar.ToBase64String();
    public static string ToBase64String(this BigInteger source)
    {
        var bigIntBytes = source.ToByteArray().Reverse().ToArray();

        return Convert.ToBase64String(bigIntBytes);
    }

各桁を取得するために数学的な方法(mod 10など...)も試しましたが、ToString()よりもTON時間がかかります。

ここで何が間違っていますか?


このコードは、以下の回答に基づいて思いついたものです。これは ToString() よりも高速ですが、数秒しかかかりません。

ToQuickString

//Usage: string bigIntString = bigIntVar.ToQuickString()
public static String ToQuickString(this BigInteger source)
{
    powersOfTen = new List<BigInteger>();

    powersOfTen.Add(1);

    for (BigInteger i = 10; i < source; i *= i)
    {
        powersOfTen.Add(i);
    }

    return BuildString(source, powersOfTen.Count - 1).ToString().TrimStart('0');
}

private static List<BigInteger> powersOfTen;

private static string BuildString(BigInteger n, int m)
{
    if (m == 0)
        return n.ToString();

    BigInteger remainder;
    BigInteger quotient = BigInteger.DivRem(n, powersOfTen[m], out remainder);

    return BuildString(quotient, m - 1) + BuildString(remainder, m - 1);
}
4

2 に答える 2

2

最初に、10^(2^m)より小さい形式のすべての数値を計算しますn。次にDivRem、これらのうち最大のものを使用して、問題を 2 つの下位問題に分割します。個々の数字になるまで、それを再帰的に繰り返します。

var powersOfTen=new List<BigInteger>();
powersOfTen.Add(1);
for(BigInteger i=10;i<n;i=i*i)
  powersOfTen.Add(i);

string ToString(BigInteger n, int m)
{
  if(m==0)
    return n.ToString();
  quotient = DivRem(n,powersOfTen[m], remainder)
  return ToString(quotient, m-1)+ToString(remainder, m-1)
}

文字配列に直接書き込むことで、文字列連結を完全に最適化することもできます。


または、すべての計算で基数 1000'000'000 を使用することを検討できます。そうすれば、最終的にベース変換はまったく必要ありません。おそらく、階乗計算の方がはるかに高速です。

List<int> multiply(List<int> f1, int f2)
{
  int carry=0;
  for(int i=0;i<f1.Count;i++)
  {
    var product=(Int64)f1[i]*(Int64)f2;
    carry=product/1000000000;
    result.Add(product%1000000000);
  }
  if(carry!=0)
    result.Add(carry);
}

基数 10 の文字列への変換は簡単で安価です。

于 2012-10-31T09:26:38.143 に答える
1

BigInteger データをバイナリまたは 16 進形式で保存します。これは、コンピューターと十分に熱心な人間にとって読み取り可能です。;>

出力を「人間が読める」ものにするために余分な労力を費やすのは、時間の無駄です。基数が 10、基数 16、基数 2、またはその他のものであるかどうかに関係なく、人間は 450,000 桁から意味を成すことができません。

アップデート

Base 10 の変換をもう少し詳しく見てみると、マルチコア システムで複数のスレッドを使用すると、ToString のベースライン パフォーマンスをほぼ半分に削減することができます。主な障害は、10 進化プロセス全体で最大の時間を消費するのは、元の 450k 桁の数値に対する最初の除算操作であることです。

Stats on my quad core P7: 
Generating a 500k digit random number using power and multiply: 5 seconds
Dividing that big number by anything just once: 11 seconds
ToString(): 22 seconds
ToQuickString: 18 seconds
ToStringMT: 12.9 seconds

.

public static class BigIntExtensions
{
    private static List<BigInteger> powersOfTen;

    // Must be called before ToStringMt()
    public static void InitPowersOfTen(BigInteger n)
    {
        powersOfTen = new List<BigInteger>();

        powersOfTen.Add(1);

        for (BigInteger i = 10; i < n; i *= i)
            powersOfTen.Add(i);
    }

    public static string ToStringMT(this BigInteger n)
    {
        // compute the index into the powersOfTen table for the given parameter. This is very fast.
        var m = (int)Math.Ceiling(Math.Log(BigInteger.Log10(n), 2));

        BigInteger r1;
        // the largest amount of execution time happens right here:
        BigInteger q1 = BigInteger.DivRem(n, BigIntExtensions.powersOfTen[m], out r1);

        // split the remaining work across 4 threads - 3 new threads plus the current thread
        var t1 = Task.Factory.StartNew<string>(() =>
        {
            BigInteger r1r2;
            BigInteger r1q2 = BigInteger.DivRem(r1, BigIntExtensions.powersOfTen[m - 1], out r1r2);
            var t2 = Task.Factory.StartNew<string>(() => BuildString(r1r2, m - 2));
            return BuildString(r1q2, m - 2) + t2.Result;
        });
        BigInteger q1r2;
        BigInteger q1q2 = BigInteger.DivRem(q1, BigIntExtensions.powersOfTen[m - 1], out q1r2);
        var t3 = Task.Factory.StartNew<string>(() => BuildString(q1r2, m - 2));
        var sb = new StringBuilder();
        sb.Append(BuildString(q1q2, m - 2));
        sb.Append(t3.Result);
        sb.Append(t1.Result);
        return sb.ToString();
    }

    // same as ToQuickString, but bails out before m == 0 to reduce call overhead.
    // BigInteger.ToString() is faster than DivRem for smallish numbers.
    private static string BuildString(BigInteger n, int m)
    {
        if (m <= 8)
            return n.ToString();

        BigInteger remainder;
        BigInteger quotient = BigInteger.DivRem(n, powersOfTen[m], out remainder);
        return BuildString(quotient, m - 1) + BuildString(remainder, m - 1);
    }
}

ToQuickString() および ToStringMT() の場合、これらの関数を使用する前に、配列の 10 の累乗を初期化する必要があります。配列は後続の呼び出しで再利用できるため、この配列の初期化は関数実行時間の測定に含めるべきではありません。したがって、その初期化コストは、個々の関数呼び出しではなく、プログラムの存続期間にわたって償却されます。

本番システムの場合、クラスの静的コンストラクターで適切な数のエントリを初期化し、ToQuickString() または ToStringMT() をチェックインして、テーブルに処理するのに十分なエントリがあるかどうかを確認するなど、より自動化された初期化をセットアップします。 BigInteger を指定します。そうでない場合は、現在の BigInteger を処理するのに十分なエントリをテーブルに追加してから、操作を続行します。

この ToStringMT 関数はワーカー タスクを手動で構築し、マルチコア CPU で利用可能な実行コアの 4 つのスレッドに残りのワークアウトを分散します。代わりに、元の ToQuickString() 関数を、再帰ごとにその作業の半分を別のスレッドにスピンオフさせることもできますが、これではすぐに多くのタスクが作成され、タスク スケジューリングのオーバーヘッドで行き詰まります。再帰は、個々の 10 進数までドリルダウンします。BigInteger.ToString() は小さい数値の DivRem よりも高速であるため、BuildString() 関数を修正して (m == 0 ではなく m <= 8 に) 早期に救済しました。

ToStringMt() の実行時間の 90% は、最初の DivRem 呼び出しによって占められます。その後は収束が早いのですが、最初のは本当に辛いです。

于 2012-11-02T17:26:32.440 に答える