【问题标题】:Can I find the number of digits of a BigInteger in C#?我可以在 C# 中找到 BigInteger 的位数吗?
【发布时间】:2016-03-07 06:18:45
【问题描述】:

我正在解决this problem,其中他们要求第一个 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
  • StringMethod: 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;
    }
}

【问题讨论】:

  • 你能通过计算log_10(number) 得到以 10 为底的大小并计算结果吗?编辑:我的意思是这样的:stackoverflow.com/a/1489928/5296568。但是,如果您真的想计算大数的log_10,请小心。字符串处理可能更快。做基准来确认这一点。
  • @MaximilianGerhardt 谢谢。我能找到的唯一 Log10 是 Math.Log10,它以 double 作为输入,当我将 BigInteger 转换为 double 时,我似乎遇到了数值问题。也许我可以实现我自己的 Log10。
  • 不,这里有一个静态函数BigInteger.Log(BigInteger num, double base):(msdn.microsoft.com/de-de/library/dd268364%28v=vs.110%29.aspx)。您还应该记下上述问题中发布的其他答案,但在 all 事情之上,您应该做的是 benchmark 一切。结果可能令人惊讶。也许,.ToString().Length最快的方法。
  • @HowDoICSharply 计算位数的简单方法是不断除以 10 直到小于 10。除以的次数 + 1 就是位数。跨度>
  • @Kyle——这听起来效率不高。在 1000 位数字上,您需要 1000 个除法才能找到长度。解决该问题的另一种方法,在我看来最快(尽管需要进行基准测试),将不是检查位数,而是为 10^n 设置一个常数,其中 n 是您的位数(n = 1000),并执行运算直到总和大于该常数。

标签: c# arrays biginteger


【解决方案1】:

假设 x > 0

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

会得到位数。

出于好奇,我测试了

int digits = x.ToString().Length; 

方法。对于 100 000 000 次迭代,它比 Log10 解决方案慢 3 倍。

【讨论】:

  • 谢谢,这正是我想要的。
  • 只有 3 倍的慢?对于必须经常除以 10 的例程来说,这还不错。
  • 警告 - 这种方法并不总是正确的。 (int)Math.Floor(BigInteger.Log10(1000) + 1) 为 3 但预期结果为 4。原因是 BigInteger.Log10 仅提供近似结果。
【解决方案2】:

扩展我的评论——不是基于位数进行测试,而是基于超过具有问题上限的常数进行测试:

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;
    }

这应该会显着提高性能。

【讨论】:

  • 谢谢。我已经测试了你的解决方案。出于某种原因,我没有看到性能有所提高。它似乎与日志方法相当。
  • 等一下,我没有意识到秒表像真正的秒表一样工作。我必须在重新启动之前重置它。你的方法是迄今为止最快的。我编辑了我的帖子以反映新时代。
  • 是的。它的速度要快一个数量级——因为在判断你是否已经走得足够远时不涉及数学。
【解决方案3】:

更新:

这是 .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() 操作。我已经优化了Singulink.Numerics.BigIntegerExtensions 中的CountDigits() 方法,其缓存具有10 的幂,所以如果您对最快的实现感兴趣,请检查一下。默认情况下,它会将功率缓存到 1023 的指数,但如果您想在更大的值上交换内存使用以获得更快的性能,您可以通过调用 BigIntegerPowCache.GetCache(10, maxSize) where maxSize = maxExponent + 1 来增加最大缓存指数。

在 i7-3770 CPU 上,当数字计数 BigInteger 值(单线程)的数字计数。

原始答案:

如 cmets 所示,接受的答案不可靠。此方法适用于所有数字:

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;
}

【讨论】:

    猜你喜欢
    • 2011-06-14
    • 1970-01-01
    • 1970-01-01
    • 2013-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多