簡體   English   中英

有沒有一種有效的方法來獲取 BigInteger 的長度而不調用 ToString?

[英]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()BigIntegerLog10方法相比,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乙

在此處輸入圖像描述

備注

  • CountDigits 是 System.Buffers.Text.FormattingHelpers 中我為 BigInteger 改編的一種巧妙方法。 它的性能明顯優於 Joel 的方法,但比 ToString 或 Log10 差得多。
  • Matthew 采用的方法比 Joel 的方法差 5-10 倍,這表明基准代碼在選擇算法時的重要性。

結論

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.

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