[英]Is there an efficient way to get the length of a BigInteger without calling ToString?
考慮以下代碼:
BigInteger value = BigInteger.Parse("9876543210987654321098765432109876543210");
Console.WriteLine(value.ToString().Length); // 40
有沒有更有效的方法來獲取數字的長度而不調用ToString
?
每當有關於“性能”的問題時,總是有必要對這些方法進行基准測試。 正如 madreflection 指出的那樣,需要考慮 cpu 和 memory 成本,因此我使用 memory 診斷器運行了針對 .NET 6 的基准測試。 結果令人着迷,與高度優化的ToString()
和BigInteger
的Log10
方法相比,Joel 的答案表現非常差。
基准代碼:
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class BigIntegerBenchmarks
{
private static BigInteger _value = BigInteger.Parse("9876543210987654321098765432109876543210");
[Benchmark(Baseline = true)]
public int ToStringBenchmark()
{
return _value.ToString().Length;
}
[Benchmark]
public int GetLengthDivisionBenchmark()
{
return GetLengthDivision(_value);
}
[Benchmark]
public int GetLengthMultiplicationBenchmark()
{
return GetLengthMultiplication(_value);
}
[Benchmark]
public int GetLengthAbsPowBenchmark()
{
return GetLengthAbsPow(_value);
}
[Benchmark]
public int BigIntegerLog()
{
return (int)Math.Round(BigInteger.Log10(_value), MidpointRounding.AwayFromZero);
}
[Benchmark]
public int CountDigitsBenchmark()
{
return CountDigits(_value);
}
static int GetLengthDivision(BigInteger value)
{
int result = 1;
while (value >= 10)
{
result++;
value /= 10;
}
return result;
}
static int GetLengthMultiplication(BigInteger value)
{
BigInteger power = 10;
int result = 1;
while (power < value)
{
result++;
power *= 10;
}
return result;
}
static int GetLengthAbsPow(BigInteger value)
{
int result = 1;
BigInteger ten = new(10);
BigInteger absValue = BigInteger.Abs(value);
while (BigInteger.Pow(ten, result) < absValue)
{
result++;
}
return result;
}
internal static int CountDigits(BigInteger value)
{
int num = 1;
BigInteger num2;
if (value >= 10000000)
{
if (value >= 100000000000000L)
{
num2 = value / 100000000000000;
num += 14;
while (num2 >= 10)
{
num++;
num2 /= 10;
}
}
else
{
num2 = value / 10000000;
num += 7;
}
}
else
{
num2 = (uint)value;
}
if (num2 >= 10)
{
num = ((num2 < 100) ? (num + 1) : ((num2 < 1000) ? (num + 2) : ((num2 < 10000) ? (num + 3) : ((num2 < 100000) ? (num + 4) : ((num2 >= 1000000) ? (num + 6) : (num + 5))))));
}
return num;
}
}
結果:
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1826 (21H1/May2021Update)
Intel Core i7-8565U CPU 1.80GHz (Whiskey Lake), 1 CPU, 8 logical and 4 physical cores
.NET SDK=7.0.100-preview.4.22252.9
[Host] : .NET Core 3.1.24 (CoreCLR 4.700.22.16002, CoreFX 4.700.22.17909), X64 RyuJIT
.NET 6.0 : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT
Job=.NET 6.0 Runtime=.NET 6.0
方法 | 意思是 | 錯誤 | 標准差 | 比率 | 比率標准差 | 0代 | 已分配 |
---|---|---|---|---|---|---|---|
ToStringBenchmark | 160.96 納秒 | 3.200 納秒 | 2.837 納秒 | 1.00 | 0.00 | 0.0668 | 280乙 |
GetLengthDivisionBenchmark | 1,939.93 納秒 | 35.299 納秒 | 31.292 納秒 | 12.06 | 0.28 | 0.3052 | 1,288 乙 |
GetLengthMultiplicationBenchmark | 1,330.02 納秒 | 21.522 納秒 | 20.131 納秒 | 8.26 | 0.17 | 0.5569 | 2,336 乙 |
GetLengthAbsPowBenchmark | 10,928.05 納秒 | 123.547 納秒 | 115.566 納秒 | 67.89 | 1.25 | 3.4637 | 14,520 乙 |
大整數日志 | 58.34 納秒 | 1.179 納秒 | 0.985 納秒 | 0.36 | 0.01 | - | - |
CountDigitsBenchmark | 1,018.92 納秒 | 9.688 納秒 | 8.589 納秒 | 6.33 | 0.12 | 0.1774 | 744乙 |
備注:
結論:
BigInteger.Log10()
似乎是迄今為止最快的方法,並導致零 memory 分配,盡管我需要舍入。 您必須確定該舍入方法是否可以為您所接受並值得冒險。
此外,值得注意的是,dotnet 核心團隊和開源社區繼續對框架 api 和運行時進行大量優化。 在推出自己的解決方案之前,通過基准測試證明他們確實有一個緩慢的算法是至關重要的。
在 Theodor Zoulias 看來,針對單一價值的基准是不完整的。
因此,我針對 10 位到 200 位的 BigInteger 值運行了額外的基准測試。 在所有情況下,獲勝者始終是Log10
實現。 對於非常小的數字(10 位), CountDigits
實現優於ToString
但不是Log10
。
基准代碼:
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class BigIntegerBenchmarks
{
public IEnumerable<BigInteger> Values()
{
// 10
yield return BigInteger.Parse("1234512345");
// 20
yield return BigInteger.Parse("12345123451234512345");
// 30
yield return BigInteger.Parse("123451234512345123451234512345");
// 40
yield return BigInteger.Parse("1234512345123451234512345123451234512345");
// 50
yield return BigInteger.Parse("12345123451234512345123451234512345123451234512345");
// 75
yield return BigInteger.Parse("123451234512345123451234512345123451234512345123451234512345123451234512345");
// 100
yield return BigInteger.Parse("1234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345");
// 150
yield return BigInteger.Parse("123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345");
// 200
yield return BigInteger.Parse("12345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345");
}
[Benchmark(Baseline = true)]
[ArgumentsSource(nameof(Values))]
public int ToStringBenchmark(BigInteger value)
{
return value.ToString().Length;
}
[Benchmark]
[ArgumentsSource(nameof(Values))]
public int GetLengthDivisionBenchmark(BigInteger value)
{
return GetLengthDivision(value);
}
[Benchmark]
[ArgumentsSource(nameof(Values))]
public int GetLengthMultiplicationBenchmark(BigInteger value)
{
return GetLengthMultiplication(value);
}
[Benchmark]
[ArgumentsSource(nameof(Values))]
public int GetLengthAbsPowBenchmark(BigInteger value)
{
return GetLengthAbsPow(value);
}
[Benchmark]
[ArgumentsSource(nameof(Values))]
public int BigIntegerLog(BigInteger value)
{
return (int)Math.Floor(BigInteger.Log10(value) + 1);
}
[Benchmark]
[ArgumentsSource(nameof(Values))]
public int CountDigitsBenchmark(BigInteger value)
{
return CountDigits(value);
}
static int GetLengthDivision(BigInteger value)
{
int result = 1;
while (value >= 10)
{
result++;
value /= 10;
}
return result;
}
static int GetLengthMultiplication(BigInteger value)
{
BigInteger power = 10;
int result = 1;
while (power < value)
{
result++;
power *= 10;
}
return result;
}
static int GetLengthAbsPow(BigInteger value)
{
int result = 1;
BigInteger ten = new(10);
BigInteger absValue = BigInteger.Abs(value);
while (BigInteger.Pow(ten, result) < absValue)
{
result++;
}
return result;
}
internal static int CountDigits(BigInteger value)
{
int num = 1;
BigInteger num2;
if (value >= 10000000)
{
if (value >= 100000000000000L)
{
num2 = value / 100000000000000;
num += 14;
while (num2 >= 10)
{
num++;
num2 /= 10;
}
}
else
{
num2 = value / 10000000;
num += 7;
}
}
else
{
num2 = (uint)value;
}
if (num2 >= 10)
{
num = ((num2 < 100) ? (num + 1) : ((num2 < 1000) ? (num + 2) : ((num2 < 10000) ? (num + 3) : ((num2 < 100000) ? (num + 4) : ((num2 >= 1000000) ? (num + 6) : (num + 5))))));
}
return num;
}
}
結果:
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1826 (21H1/May2021Update)
Intel Core i7-8565U CPU 1.80GHz (Whiskey Lake), 1 CPU, 8 logical and 4 physical cores
.NET SDK=7.0.100-preview.4.22252.9
[Host] : .NET Core 3.1.24 (CoreCLR 4.700.22.16002, CoreFX 4.700.22.17909), X64 RyuJIT
.NET 6.0 : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT
Job=.NET 6.0 Runtime=.NET 6.0
方法 | 價值 | 意思是 | 錯誤 | 標准差 | 中位數 | 比率 | 比率標准差 | 0代 | 已分配 |
---|---|---|---|---|---|---|---|---|---|
ToStringBenchmark | 1234512345 | 53.48 納秒 | 1.085 納秒 | 1.333 納秒 | 53.10 納秒 | 1.00 | 0.00 | 0.0114 | 48乙 |
GetLengthDivisionBenchmark | 1234512345 | 160.13 納秒 | 2.436 納秒 | 2.991 納秒 | 159.68 納秒 | 3.00 | 0.09 | - | - |
GetLengthMultiplicationBenchmark | 1234512345 | 130.25 納秒 | 2.035 納秒 | 1.904 納秒 | 129.94 納秒 | 2.42 | 0.07 | 0.0076 | 32乙 |
GetLengthAbsPowBenchmark | 1234512345 | 1,602.62 納秒 | 14.585 納秒 | 13.643 納秒 | 1,603.14 納秒 | 29.79 | 0.76 | 0.3567 | 1,496 乙 |
大整數日志 | 1234512345 | 23.52 納秒 | 0.148 納秒 | 0.123 納秒 | 23.53 納秒 | 0.44 | 0.01 | - | - |
CountDigitsBenchmark | 1234512345 | 30.82 納秒 | 0.523 納秒 | 1.274 納秒 | 30.50 納秒 | 0.60 | 0.03 | - | - |
ToStringBenchmark | 12345123451234512345 | 96.01 納秒 | 1.962 納秒 | 1.927 納秒 | 95.17 納秒 | 1.00 | 0.00 | 0.0440 | 184乙 |
GetLengthDivisionBenchmark | 12345123451234512345 | 609.47 納秒 | 11.889 納秒 | 9.928 納秒 | 608.20 納秒 | 6.32 | 0.13 | 0.0763 | 320乙 |
GetLengthMultiplicationBenchmark | 12345123451234512345 | 568.80 納秒 | 8.009 納秒 | 7.100 納秒 | 566.88 納秒 | 5.91 | 0.15 | 0.1717 | 720乙 |
GetLengthAbsPowBenchmark | 12345123451234512345 | 4,795.50 納秒 | 74.831 納秒 | 62.487 納秒 | 4,796.57 納秒 | 49.75 | 1.01 | 1.0910 | 4,584 乙 |
大整數日志 | 12345123451234512345 | 43.85 納秒 | 0.565 納秒 | 0.441 納秒 | 43.79 納秒 | 0.46 | 0.01 | - | - |
CountDigitsBenchmark | 12345123451234512345 | 156.70 納秒 | 1.055 納秒 | 0.881 納秒 | 156.89 納秒 | 1.63 | 0.04 | 0.0153 | 64乙 |
ToStringBenchmark | 12345(...)12345 [30] | 130.06 納秒 | 0.931 納秒 | 0.825 納秒 | 130.38 納秒 | 1.00 | 0.00 | 0.0572 | 240乙 |
GetLengthDivisionBenchmark | 12345(...)12345 [30] | 1,196.23 納秒 | 8.965 納秒 | 7.486 納秒 | 1,199.23 納秒 | 9.20 | 0.08 | 0.1888 | 792乙 |
GetLengthMultiplicationBenchmark | 12345(...)12345 [30] | 926.82 納秒 | 13.441 納秒 | 11.915 納秒 | 927.25 納秒 | 7.13 | 0.12 | 0.3557 | 1,488 乙 |
GetLengthAbsPowBenchmark | 12345(...)12345 [30] | 9,131.78 納秒 | 324.146 納秒 | 955.750 納秒 | 8,980.04 納秒 | 61.96 | 2.28 | 2.1362 | 8,944 乙 |
大整數日志 | 12345(...)12345 [30] | 43.73 納秒 | 0.673 納秒 | 0.597 納秒 | 43.61 納秒 | 0.34 | 0.01 | - | - |
CountDigitsBenchmark | 12345(...)12345 [30] | 546.94 納秒 | 10.446 納秒 | 11.611 納秒 | 544.76 納秒 | 4.22 | 0.08 | 0.0706 | 296乙 |
ToStringBenchmark | 12345(...)12345 [40] | 161.58 納秒 | 1.509 納秒 | 1.338 納秒 | 161.86 納秒 | 1.00 | 0.00 | 0.0668 | 280乙 |
GetLengthDivisionBenchmark | 12345(...)12345 [40] | 1,900.12 納秒 | 37.730 納秒 | 33.446 納秒 | 1,896.37 納秒 | 11.76 | 0.24 | 0.2956 | 1,240 乙 |
GetLengthMultiplicationBenchmark | 12345(...)12345 [40] | 1,399.98 納秒 | 27.838 納秒 | 77.602 納秒 | 1,370.01 納秒 | 9.27 | 0.28 | 0.5569 | 2,336 乙 |
GetLengthAbsPowBenchmark | 12345(...)12345 [40] | 12,315.16 納秒 | 143.022 納秒 | 111.662 納秒 | 12,321.32 納秒 | 76.22 | 0.98 | 3.4637 | 14,520 乙 |
大整數日志 | 12345(...)12345 [40] | 59.83 納秒 | 2.957 納秒 | 8.625 納秒 | 57.83 納秒 | 0.34 | 0.02 | - | - |
CountDigitsBenchmark | 12345(...)12345 [40] | 1,087.25 納秒 | 18.482 納秒 | 17.288 納秒 | 1,086.55 納秒 | 6.74 | 0.11 | 0.1678 | 704乙 |
ToStringBenchmark | 12345(...)12345 [50] | 197.47 納秒 | 1.815 納秒 | 1.417 納秒 | 197.59 納秒 | 1.00 | 0.00 | 0.0763 | 320乙 |
GetLengthDivisionBenchmark | 12345(...)12345 [50] | 2,666.97 納秒 | 22.475 納秒 | 21.023 納秒 | 2,665.74 納秒 | 13.51 | 0.16 | 0.4196 | 1,768 乙 |
GetLengthMultiplicationBenchmark | 12345(...)12345 [50] | 1,750.60 納秒 | 17.269 納秒 | 13.483 納秒 | 1,749.85 納秒 | 8.87 | 0.11 | 0.7782 | 3,256 乙 |
GetLengthAbsPowBenchmark | 12345(...)12345 [50] | 15,840.75 納秒 | 315.818 納秒 | 569.485 納秒 | 15,549.61 納秒 | 79.38 | 2.94 | 5.0964 | 21,360 乙 |
大整數日志 | 12345(...)12345 [50] | 44.87 納秒 | 0.809 納秒 | 0.756 納秒 | 45.09 納秒 | 0.23 | 0.00 | - | - |
CountDigitsBenchmark | 12345(...)12345 [50] | 1,626.71 納秒 | 27.600 納秒 | 23.047 納秒 | 1,623.62 納秒 | 8.24 | 0.14 | 0.2747 | 1,152 乙 |
ToStringBenchmark | 12345(...)12345 [75] | 306.08 納秒 | 4.371 納秒 | 3.650 納秒 | 305.57 納秒 | 1.00 | 0.00 | 0.1030 | 432乙 |
GetLengthDivisionBenchmark | 12345(...)12345 [75] | 5,192.75 納秒 | 34.686 納秒 | 30.749 納秒 | 5,195.60 納秒 | 16.96 | 0.17 | 0.7629 | 3,208 乙 |
GetLengthMultiplicationBenchmark | 12345(...)12345 [75] | 3,988.09 納秒 | 271.305 納秒 | 799.948 納秒 | 3,794.99 納秒 | 10.89 | 2.50 | 1.4191 | 5,944 乙 |
GetLengthAbsPowBenchmark | 12345(...)12345 [75] | 33,948.30 納秒 | 1,493.620 納秒 | 4,403.973 納秒 | 32,639.63 納秒 | 132.92 | 8.45 | 10.4370 | 43,848 乙 |
大整數日志 | 12345(...)12345 [75] | 43.95 納秒 | 0.908 納秒 | 1.360 納秒 | 43.35 納秒 | 0.14 | 0.00 | - | - |
CountDigitsBenchmark | 12345(...)12345 [75] | 3,529.34 納秒 | 68.576 納秒 | 60.791 納秒 | 3,531.11 納秒 | 11.54 | 0.20 | 0.5836 | 2,456 乙 |
ToStringBenchmark | 1234(...)2345 [100] | 552.12 納秒 | 32.468 納秒 | 95.732 納秒 | 533.47 納秒 | 1.00 | 0.00 | 0.1316 | 552乙 |
GetLengthDivisionBenchmark | 1234(...)2345 [100] | 9,353.61 納秒 | 278.178 納秒 | 820.215 納秒 | 9,213.45 納秒 | 17.62 | 4.21 | 1.1902 | 5,000 乙 |
GetLengthMultiplicationBenchmark | 1234(...)2345 [100] | 3,949.08 納秒 | 49.055 納秒 | 45.886 納秒 | 3,945.01 納秒 | 8.32 | 1.34 | 2.1667 | 9,064 乙 |
GetLengthAbsPowBenchmark | 1234(...)2345 [100] | 40,856.02 納秒 | 442.861 納秒 | 414.252 納秒 | 40,816.28 納秒 | 86.03 | 13.74 | 17.7002 | 74,120 乙 |
大整數日志 | 1234(...)2345 [100] | 42.35 納秒 | 0.558 納秒 | 0.436 納秒 | 42.26 納秒 | 0.09 | 0.02 | - | - |
CountDigitsBenchmark | 1234(...)2345 [100] | 6,511.54 納秒 | 60.303 納秒 | 56.407 納秒 | 6,513.08 納秒 | 13.70 | 2.11 | 0.9766 | 4,112 乙 |
ToStringBenchmark | 1234(...)2345 [150] | 734.17 納秒 | 11.671 納秒 | 9.746 納秒 | 735.48 納秒 | 1.00 | 0.00 | 0.1831 | 768乙 |
GetLengthDivisionBenchmark | 1234(...)2345 [150] | 18,963.26 納秒 | 291.798 納秒 | 272.948 納秒 | 18,927.82 納秒 | 25.76 | 0.43 | 2.2278 | 9,384 乙 |
GetLengthMultiplicationBenchmark | 1234(...)2345 [150] | 7,402.45 納秒 | 146.064 納秒 | 326.694 納秒 | 7,373.84 納秒 | 9.63 | 0.46 | 4.0283 | 16,848 乙 |
GetLengthAbsPowBenchmark | 1234(...)2345 [150] | 96,553.68 納秒 | 3,203.319 納秒 | 9,344.232 納秒 | 94,618.21 納秒 | 124.75 | 3.00 | 37.7197 | 157,928 乙 |
大整數日志 | 1234(...)2345 [150] | 43.73 納秒 | 0.847 納秒 | 0.792 納秒 | 43.57 納秒 | 0.06 | 0.00 | - | - |
CountDigitsBenchmark | 1234(...)2345 [150] | 13,799.37 納秒 | 181.226 納秒 | 169.519 納秒 | 13,831.45 納秒 | 18.75 | 0.27 | 1.9531 | 8,184 乙 |
ToStringBenchmark | 1234(...)2345 [200] | 1,170.38 納秒 | 22.340 納秒 | 18.655 納秒 | 1,167.00 納秒 | 1.00 | 0.00 | 0.2365 | 992乙 |
GetLengthDivisionBenchmark | 1234(...)2345 [200] | 30,656.86 納秒 | 493.053 納秒 | 437.079 納秒 | 30,659.95 納秒 | 26.21 | 0.70 | 3.5400 | 14,896 乙 |
GetLengthMultiplicationBenchmark | 1234(...)2345 [200] | 9,459.47 納秒 | 115.420 納秒 | 102.317 納秒 | 9,419.28 納秒 | 8.09 | 0.17 | 6.3477 | 26,600 乙 |
GetLengthAbsPowBenchmark | 1234(...)2345 [200] | 124,083.76 納秒 | 2,093.310 納秒 | 1,748.008 納秒 | 124,681.07 納秒 | 106.03 | 1.39 | 65.1855 | 272,776 乙 |
大整數日志 | 1234(...)2345 [200] | 50.15 納秒 | 1.029 納秒 | 1.338 納秒 | 49.84 納秒 | 0.04 | 0.00 | - | - |
CountDigitsBenchmark | 1234(...)2345 [200] | 26,047.64 納秒 | 520.981 納秒 | 1,452.286 納秒 | 25,753.68 納秒 | 24.32 | 0.90 | 3.2043 | 13,416 乙 |
有這個:
static int GetLength(this BigInteger value)
{
int result = 1;
while (value >= 10)
{
result++;
value /= 10;
}
return result;
}
我知道循環似乎很慢,但它仍然可能比ToString()
快,由於本地化/文化問題,這可能比您預期的要慢很多。
或者我們可以反轉它以使用乘法和向上計數,這可能會更快,具體取決於計算機的指令集,即使它需要一個額外的變量:
static int GetLength(this BigInteger value)
{
BigInteger power = 10;
int result = 1;
while (power < value)
{
result++;
power *= 10;
}
return result;
}
檢查@David L 生成的基准——這是表現最差的。
非常感謝@Joel Coehoorn,我從他的回答中得出了這個結論:
static int GetLength(BigInteger value)
{
int result = 1;
BigInteger ten = new(10);
BigInteger absValue = BigInteger.Abs(value);
while(BigInteger.Pow(ten, result) < absValue)
{
result++;
}
return result;
}
將其留在這里以獲取反饋(同樣,不要使用它!)
由於Log10
似乎是最快的解決方案,但需要四舍五入,我已經測試了超過 50,000 個 BigInteger 值的以下解決方案:
static int GetLength(BigInteger value)
{
double log10 = BigInteger.Log10(value);
return (int) Math.Ceiling(log10) + 1;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.