簡體   English   中英

我可以在 C# 中找到 BigInteger 的位數嗎?

[英]Can I find the number of digits of a BigInteger in C#?

我正在解決這個問題,他們要求第一個 1000 位斐波那契數的索引,我的第一個想法類似於:

BigInteger x = 1;
BigInteger y = 1;
BigInteger tmp = 0;

int currentIndex = 2;
while (x.NoOfDigits < 1000)
{
    tmp = x + y;
    y = x;
    x = tmp;
    currentIndex++;
}
return currentIndex;

但是,據我所知,沒有計算 BigInteger 位數的方法。 這是真的? 繞過它的一種方法是使用 BigInteger 的 .ToString().Length 方法,但我被告知字符串處理很慢。

BigInteger 也有一個 .ToByteArray(),我想將 BigInteger 轉換為字節數組並檢查該數組的長度 - 但我不認為這唯一地確定了 BigInteger 中的位數。

對於它的價值,我實現了另一種解決方法,即手動將斐波那契數列存儲在數組中,並且在數組滿時立即停止,我將其與基於 .ToString 的方法進行了比較,后者約為 2.5倍慢,但第一種方法需要 0.1 秒,這似乎也很長。

編輯:我已經測試了以下答案中的兩個建議(一個是 BigInteger.Log,另一個是 MaxLimitMethod)。 我得到以下運行時間:

  • 原始方法:00:00:00.0961957
  • 字符串方法:00:00:00.1535350
  • BigIntegerLogMethod: 00:00:00.0387479
  • MaxLimitMethod: 00:00:00.0019509

程序

using System;
using System.Collections.Generic;
using System.Numerics;
using System.Diagnostics;

class Program
{
    static void Main(string[] args)
    {
        Stopwatch clock = new Stopwatch();
        clock.Start();
        int index1 = Algorithms.IndexOfNDigits(1000);
        clock.Stop();
        var elapsedTime1 = clock.Elapsed;
        Console.WriteLine(index1);
        Console.WriteLine("Original method: {0}",elapsedTime1);
        Console.ReadKey();

        clock.Reset();
        clock.Start();
        int index2 = Algorithms.StringMethod(1000);
        clock.Stop();
        var elapsedTime2 = clock.Elapsed;
        Console.WriteLine(index2);
        Console.WriteLine("StringMethod: {0}", elapsedTime2);
        Console.ReadKey();

        clock.Reset();
        clock.Start();
        int index3 = Algorithms.BigIntegerLogMethod(1000);
        clock.Stop();
        var elapsedTime3 = clock.Elapsed;
        Console.WriteLine(index3);
        Console.WriteLine("BigIntegerLogMethod: {0}", elapsedTime3);
        Console.ReadKey();

        clock.Reset();
        clock.Start();
        int index4 = Algorithms.MaxLimitMethod(1000);
        clock.Stop();
        var elapsedTime4 = clock.Elapsed;
        Console.WriteLine(index4);
        Console.WriteLine("MaxLimitMethod: {0}", elapsedTime4);
        Console.ReadKey();


    }
}

static class Algorithms
{
    //Find the index of the first Fibonacci number of n digits
    public static int IndexOfNDigits(int n)
    {
        if (n == 1) return 1;
        int[] firstNumber = new int[n];
        int[] secondNumber = new int[n];

        firstNumber[0] = 1;
        secondNumber[0] = 1;
        int currentIndex = 2;

        while (firstNumber[n-1] == 0)
        {
            int carry = 0, singleSum = 0;
            int[] tmp = new int[n]; //Placeholder for the sum
            for (int i = 0; i<n; i++)
            {
                singleSum = firstNumber[i] + secondNumber[i];
                if (singleSum >= 10) carry = 1;
                else carry = 0;

                tmp[i] += singleSum % 10;
                if (tmp[i] >= 10)
                {
                    tmp[i] = 0;
                    carry = 1;
                }
                int countCarries = 0;
                while (carry == 1)
                {
                    countCarries++;
                    if (tmp[i + countCarries] == 9)
                    {
                        tmp[i + countCarries] = 0;
                        tmp[i + countCarries + 1] += 1;
                        carry = 1;
                    }
                    else
                    {
                        tmp[i + countCarries] += 1;
                        carry = 0;
                    }
                }
            }

            for (int i = 0; i < n; i++ )
            {
                secondNumber[i] = firstNumber[i];
                firstNumber[i] = tmp[i];
            }
            currentIndex++;
        }
        return currentIndex;
    }

    public static int StringMethod(int n)
    {
        BigInteger x = 1;
        BigInteger y = 1;
        BigInteger tmp = 0;
        int currentIndex = 2;

        while (x.ToString().Length < n)
        {
            tmp = x + y;
            y = x;
            x = tmp;
            currentIndex++;
        }
        return currentIndex;
    }

    public static int BigIntegerLogMethod(int n)
    {
        BigInteger x = 1;
        BigInteger y = 1;
        BigInteger tmp = 0;
        int currentIndex = 2;

        while (Math.Floor(BigInteger.Log10(x) + 1) < n)
        {
            tmp = x + y;
            y = x;
            x = tmp;
            currentIndex++;
        }
        return currentIndex;
    }

    public static int MaxLimitMethod(int n)
    {
        BigInteger maxLimit = BigInteger.Pow(10, n - 1);
        BigInteger x = 1;
        BigInteger y = 1;
        BigInteger tmp = 0;
        int currentIndex = 2;

        while (x.CompareTo(maxLimit) < 0)
        {
            tmp = x + y;
            y = x;
            x = tmp;
            currentIndex++;
        }
        return currentIndex;
    }
}

假設 x > 0

int digits = (int)Math.Floor(BigInteger.Log10(x) + 1);

將得到位數。

出於好奇,我測試了

int digits = x.ToString().Length; 

方法。 對於 100 000 000 次迭代,它比 Log10 解決方案慢 3 倍。

擴展我的評論 - 而不是基於位數進行測試,而是基於超過具有問題上限的常數進行測試:

public static int MaxLimitMethod(int n)
    {
        BigInteger maxLimit = BigInteger.Pow(10, n);
        BigInteger x = 1;
        BigInteger y = 1;
        BigInteger tmp = 0;
        int currentIndex = 2;

        while (x.CompareTo(maxLimit) < 0)
        {
            tmp = x + y;
            y = x;
            x = tmp;
            currentIndex++;
        }
        return currentIndex;
    }

這應該會顯着提高性能。

更新:

這是 .NET 5 上更快的方法(因為需要GetBitLength() ):

private static readonly double exponentConvert = Math.Log10(2);
private static readonly BigInteger _ten = 10;

public static int CountDigits(BigInteger value)
{
    if (value.IsZero)
        return 1;

    value = BigInteger.Abs(value);

    if (value.IsOne)
        return 1;

    long numBits = value.GetBitLength();

    int base10Digits = (int)(numBits * exponentConvert).Dump();
    var reference = BigInteger.Pow(_ten, base10Digits);

    if (value >= reference)
        base10Digits++;

    return base10Digits;
}

對於大值,此算法中最慢的部分是BigInteger.Pow()操作。 我已經優化了CountDigits()的方法Singulink.Numerics.BigIntegerExtensions與持有的10次冪高速緩存,所以檢查出來,如果你有興趣以最快的速度實現。 默認情況下,它緩存功率高達 1023 的指數,但如果您想在更大的值上交換內存使用以獲得更快的性能,您可以通過調用BigIntegerPowCache.GetCache(10, maxSize)其中maxSize = maxExponent + 1來增加最大緩存指數。

在 i7-3770 CPU 上,當數字計數 <= 最大緩存指數時,此庫需要 350 毫秒才能獲得 1000 萬個BigInteger值(單線程)的數字計數。

原始答案:

如評論中所示,接受的答案是不可靠的。 此方法適用於所有數字:

private static int CountDigits(BigInteger value)
{    
    if (value.IsZero)
        return 1;
        
    value = BigInteger.Abs(value);
    
    if (value.IsOne)
        return 1;
    
    int exp = (int)Math.Ceiling(BigInteger.Log10(value));
    var test = BigInteger.Pow(10, exp);
    
    return value >= test ? exp + 1 : exp;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM