我可以在 C# 中找到 BigInteger 的位数吗?
Can I find the number of digits of a BigInteger in C#?
我正在求解 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 转换为字节数组并检查该数组的长度 - 但我认为这不能唯一地确定数组中的位数大整数。
为了它的价值,我实现了另一种解决它的方法,即手动将斐波那契数存储在数组中,并在数组已满时立即停止,我将其与基于 .ToString 的方法进行了比较,慢了2.5倍左右,但是第一种方法用了0.1秒,也好像很长。
编辑:我已经测试了下面答案中的两个建议(一个带有 BigInteger.Log 和一个带有 MaxLimitMethod)。我得到以下 运行 次:
- 原方法:00:00:00.0961957
- 字符串方法:00:00:00.1535350
- BigIntegerLogMethod: 00:00:00.0387479
- 最大限制方法: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()
操作。我已经使用包含 10 次方的缓存优化了 Singulink.Numerics.BigIntegerExtensions 中的 CountDigits()
方法,所以如果您对尽可能快的实现感兴趣,请检查一下。默认情况下,它会缓存高达 1023 的指数,但是如果你想在更大的值上以内存使用换取更快的性能,你可以通过调用 BigIntegerPowCache.GetCache(10, maxSize)
where maxSize = maxExponent + 1
.[=19= 来增加最大缓存指数]
在 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;
}
我正在求解 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 转换为字节数组并检查该数组的长度 - 但我认为这不能唯一地确定数组中的位数大整数。
为了它的价值,我实现了另一种解决它的方法,即手动将斐波那契数存储在数组中,并在数组已满时立即停止,我将其与基于 .ToString 的方法进行了比较,慢了2.5倍左右,但是第一种方法用了0.1秒,也好像很长。
编辑:我已经测试了下面答案中的两个建议(一个带有 BigInteger.Log 和一个带有 MaxLimitMethod)。我得到以下 运行 次:
- 原方法:00:00:00.0961957
- 字符串方法:00:00:00.1535350
- BigIntegerLogMethod: 00:00:00.0387479
- 最大限制方法: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()
操作。我已经使用包含 10 次方的缓存优化了 Singulink.Numerics.BigIntegerExtensions 中的 CountDigits()
方法,所以如果您对尽可能快的实现感兴趣,请检查一下。默认情况下,它会缓存高达 1023 的指数,但是如果你想在更大的值上以内存使用换取更快的性能,你可以通过调用 BigIntegerPowCache.GetCache(10, maxSize)
where maxSize = maxExponent + 1
.[=19= 来增加最大缓存指数]
在 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;
}