Làm thế nào để tôi có thể đếm được tổng số các chữ số trong một số?


114

Làm cách nào tôi có thể đếm tổng số chữ số của một số trong C #? Ví dụ, số 887979789 có 9 chữ số.


6
hãy thử sử dụng .Length nếu nó không làm việc chuyển đổi nó thành một chuỗi đầu tiên
Breezer

Giả sử x = 887979789; x.ToString (). Count (); sẽ cung cấp cho bạn điều đó.
nPcomp

Câu trả lời:


174

Nếu không chuyển đổi thành một chuỗi, bạn có thể thử:

Math.Ceiling(Math.Log10(n));

Chỉnh sửa sau nhận xét của ysap:

Math.Floor(Math.Log10(n) + 1);

10
Tôi e rằng ceil (log10 (10)) = ceil (1) = 1, và không phải 2 như nó phải có cho câu hỏi này!
ysap 19/12/10

3
Cảm ơn, đó là một phương pháp hay. Mặc dù nó không nhanh hơn int count = 0; làm {count ++; } while ((i / = 10)> = 1); :(
Puterdo Borato

3
Nếu phạm vi số của bạn bao gồm các phủ định, bạn sẽ cần sử dụng Math.Floor (Math.Log10 (Math.Abs ​​(n)) + 1);
mrcrowl

1
Vâng nếu n0có thể chỉ trả về 1:) Quá xử lý các giá trị âm chỉ cần thay thế nbằng Math.Abs(n).
Umair

3
@Puterdo Borato: thử nghiệm hiệu suất của tôi thực sự cho thấy rằng phương pháp của bạn nhanh hơn khi số chữ số <5. Vượt qua điều đó, Steve's Math.floor nhanh hơn.
stack247

83

Thử cái này:

myint.ToString().Length

Điều đó có hiệu quả không?


25
Cần chỉ ra rằng bạn có thể gặp phải vấn đề với phương pháp này nếu bạn đang xử lý các số âm. (Và rõ ràng là số thập phân, nhưng ví dụ sử dụng dấu int, vì vậy tôi cho rằng đó không phải là vấn đề.)
Cody Grey

2
Cấp phát chuỗi @Krythic là cơn sốt mới trong thế giới .NET.
nawfal

1
Mới? Khó khăn. Tôi đã phân bổ các chuỗi vào năm 2010. Thật là một người thiết lập xu hướng. Cười lớn. Bạn đúng mặc dù. Thật là bẩn thỉu!
Andiih 10/12/16

3
@Krythic Không phải là những năm 1980, máy tính của bạn có đủ RAM để lưu chuỗi 10 ký tự vào bộ nhớ trong suốt thời gian thực hiện một thao tác.
MrLore

2
@MrLore Trong các ứng dụng đơn giản, điều này có thể đúng, nhưng trong thế giới phát triển trò chơi, đó là một con thú hoàn toàn khác.
Krythic

48

Giải pháp

Bất kỳ phương thức mở rộng nào sau đây sẽ thực hiện công việc. Tất cả chúng đều coi dấu trừ là một chữ số và hoạt động chính xác với tất cả các giá trị đầu vào có thể có. Chúng cũng hoạt động cho .NET Framework và .NET Core. Tuy nhiên, có sự khác biệt về hiệu suất có liên quan (được thảo luận bên dưới), tùy thuộc vào lựa chọn Nền tảng / Khung của bạn.

Phiên bản Int32:

public static class Int32Extensions
{
    // IF-CHAIN:
    public static int Digits_IfChain(this int n)
    {
        if (n >= 0)
        {
            if (n < 10) return 1;
            if (n < 100) return 2;
            if (n < 1000) return 3;
            if (n < 10000) return 4;
            if (n < 100000) return 5;
            if (n < 1000000) return 6;
            if (n < 10000000) return 7;
            if (n < 100000000) return 8;
            if (n < 1000000000) return 9;
            return 10;
        }
        else
        {
            if (n > -10) return 2;
            if (n > -100) return 3;
            if (n > -1000) return 4;
            if (n > -10000) return 5;
            if (n > -100000) return 6;
            if (n > -1000000) return 7;
            if (n > -10000000) return 8;
            if (n > -100000000) return 9;
            if (n > -1000000000) return 10;
            return 11;
        }
    }

    // USING LOG10:
    public static int Digits_Log10(this int n) =>
        n == 0 ? 1 : (n > 0 ? 1 : 2) + (int)Math.Log10(Math.Abs((double)n));

    // WHILE LOOP:
    public static int Digits_While(this int n)
    {
        int digits = n < 0 ? 2 : 1;
        while ((n /= 10) != 0) ++digits;
        return digits;
    }

    // STRING CONVERSION:
    public static int Digits_String(this int n) =>
        n.ToString().Length;
}

Phiên bản Int64:

public static class Int64Extensions
{
    // IF-CHAIN:
    public static int Digits_IfChain(this long n)
    {
        if (n >= 0)
        {
            if (n < 10L) return 1;
            if (n < 100L) return 2;
            if (n < 1000L) return 3;
            if (n < 10000L) return 4;
            if (n < 100000L) return 5;
            if (n < 1000000L) return 6;
            if (n < 10000000L) return 7;
            if (n < 100000000L) return 8;
            if (n < 1000000000L) return 9;
            if (n < 10000000000L) return 10;
            if (n < 100000000000L) return 11;
            if (n < 1000000000000L) return 12;
            if (n < 10000000000000L) return 13;
            if (n < 100000000000000L) return 14;
            if (n < 1000000000000000L) return 15;
            if (n < 10000000000000000L) return 16;
            if (n < 100000000000000000L) return 17;
            if (n < 1000000000000000000L) return 18;
            return 19;
        }
        else
        {
            if (n > -10L) return 2;
            if (n > -100L) return 3;
            if (n > -1000L) return 4;
            if (n > -10000L) return 5;
            if (n > -100000L) return 6;
            if (n > -1000000L) return 7;
            if (n > -10000000L) return 8;
            if (n > -100000000L) return 9;
            if (n > -1000000000L) return 10;
            if (n > -10000000000L) return 11;
            if (n > -100000000000L) return 12;
            if (n > -1000000000000L) return 13;
            if (n > -10000000000000L) return 14;
            if (n > -100000000000000L) return 15;
            if (n > -1000000000000000L) return 16;
            if (n > -10000000000000000L) return 17;
            if (n > -100000000000000000L) return 18;
            if (n > -1000000000000000000L) return 19;
            return 20;
        }
    }

    // USING LOG10:
    public static int Digits_Log10(this long n) =>
        n == 0L ? 1 : (n > 0L ? 1 : 2) + (int)Math.Log10(Math.Abs((double)n));

    // WHILE LOOP:
    public static int Digits_While(this long n)
    {
        int digits = n < 0 ? 2 : 1;
        while ((n /= 10L) != 0L) ++digits;
        return digits;
    }

    // STRING CONVERSION:
    public static int Digits_String(this long n) =>
        n.ToString().Length;
}

Thảo luận

Câu trả lời này bao gồm các bài kiểm tra được thực hiện cho cả hai loại Int32Int64các loại, sử dụng một mảng / số được 100.000.000lấy mẫu ngẫu nhiên . Tập dữ liệu ngẫu nhiên được xử lý trước thành một mảng trước khi thực hiện các bài kiểm tra.intlong

Kiểm tra tính nhất quán trong 4 phương pháp khác nhau cũng đã được thực hiện, cho MinValue, trường hợp biên giới tiêu cực, -1, 0, 1, trường hợp biên giới tích cực, MaxValuevà cũng cho cả dữ liệu ngẫu nhiên. Không có thử nghiệm nhất quán nào không thành công đối với các phương pháp được cung cấp ở trên, NGOẠI TRỪ cho phương pháp LOG10 (điều này sẽ được thảo luận sau).

Các thử nghiệm được thực hiện trên .NET Framework 4.7.2.NET Core 2.2; cho x86x64nền tảng, trên máy Bộ xử lý Intel 64-bit, với Windows 10và với VS2017 v.15.9.17. 4 trường hợp sau đây có cùng ảnh hưởng đến kết quả hoạt động:

.NET Framework (x86)

  • Platform = x86

  • Platform = AnyCPU, Prefer 32-bitđược chọn trong cài đặt dự án

.NET Framework (x64)

  • Platform = x64

  • Platform = AnyCPU, Prefer 32-bitkhông được chọn trong cài đặt dự án

.NET Core (x86)

  • "C:\Program Files (x86)\dotnet\dotnet.exe" bin\Release\netcoreapp2.2\ConsoleApp.dll

  • "C:\Program Files (x86)\dotnet\dotnet.exe" bin\x86\Release\netcoreapp2.2\ConsoleApp.dll

.NET Core (x64)

  • "C:\Program Files\dotnet\dotnet.exe" bin\Release\netcoreapp2.2\ConsoleApp.dll

  • "C:\Program Files\dotnet\dotnet.exe" bin\x64\Release\netcoreapp2.2\ConsoleApp.dll

Các kết quả

Các bài kiểm tra hiệu suất dưới đây tạo ra sự phân bố đồng đều các giá trị trong phạm vi rộng các giá trị mà một số nguyên có thể giả định. Điều này có nghĩa là có nhiều cơ hội kiểm tra các giá trị với số lượng lớn các chữ số. Trong các tình huống thực tế, hầu hết các giá trị có thể nhỏ, vì vậy IF-CHAIN ​​sẽ hoạt động tốt hơn nữa. Hơn nữa, bộ xử lý sẽ lưu vào bộ nhớ cache và tối ưu hóa các quyết định IF-CHAIN ​​theo tập dữ liệu của bạn.

Như @AlanSingfield đã chỉ ra trong phần nhận xét, phương thức LOG10 phải được sửa bằng cách ép kiểu vào doublebên trong Math.Abs()đối với trường hợp giá trị đầu vào là int.MinValuehoặc long.MinValue.

Về các bài kiểm tra hiệu suất ban đầu mà tôi đã thực hiện trước khi chỉnh sửa câu hỏi này (nó đã phải được chỉnh sửa hàng triệu lần rồi), có một trường hợp cụ thể được chỉ ra bởi @ GyörgyKőszeg , trong đó phương pháp IF-CHAIN ​​hoạt động chậm hơn phương pháp LOG10.

Điều này vẫn xảy ra, mặc dù mức độ khác biệt đã giảm đi nhiều sau khi khắc phục sự cố được chỉ ra bởi @AlanSingfield . Bản sửa lỗi này (thêm một kiểu vào double) gây ra lỗi tính toán khi giá trị đầu vào là chính xác -999999999999999999: phương thức LOG10 trả về 20thay vì 19. Phương thức LOG10 cũng phải có bộ ifphận bảo vệ trong trường hợp giá trị đầu vào bằng 0.

Phương thức LOG10 khá phức tạp để làm việc cho tất cả các giá trị, có nghĩa là bạn nên tránh nó. Nếu ai đó tìm ra cách làm cho nó hoạt động chính xác cho tất cả các bài kiểm tra tính nhất quán bên dưới, vui lòng gửi nhận xét!

Phương thức WHILE cũng có phiên bản được cấu trúc lại gần đây nhanh hơn, nhưng nó vẫn còn chậm Platform = x86(cho đến bây giờ tôi không thể tìm ra lý do tại sao).

Phương thức STRING luôn chậm: nó phân bổ quá nhiều bộ nhớ mà không có gì. Điều thú vị là trong .NET Core, phân bổ chuỗi dường như nhanh hơn nhiều so với .NET Framework. Tốt để biết.

Phương pháp IF-CHAIN ​​sẽ hoạt động tốt hơn tất cả các phương pháp khác trong 99,99% trường hợp; và, theo ý kiến ​​cá nhân của tôi, là lựa chọn tốt nhất của bạn (xem xét tất cả các điều chỉnh cần thiết để làm cho phương pháp LOG10 hoạt động chính xác và hiệu suất kém của hai phương pháp còn lại).

Cuối cùng, kết quả là:

nhập mô tả hình ảnh ở đây

Vì các kết quả này phụ thuộc vào phần cứng, tôi khuyên bạn nên chạy các bài kiểm tra hiệu suất bên dưới trên máy tính của chính mình nếu bạn thực sự cần chắc chắn 100% trong trường hợp cụ thể của mình.

Mã kiểm tra

Dưới đây là mã cho kiểm tra hiệu suất và kiểm tra tính nhất quán. Mã giống nhau được sử dụng cho cả .NET Framework và .NET Core.

using System;
using System.Diagnostics;

namespace NumberOfDigits
{
    // Performance Tests:
    class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("\r\n.NET Core");

            RunTests_Int32();
            RunTests_Int64();
        }

        // Int32 Performance Tests:
        private static void RunTests_Int32()
        {
            Console.WriteLine("\r\nInt32");

            const int size = 100000000;
            int[] samples = new int[size];
            Random random = new Random((int)DateTime.Now.Ticks);
            for (int i = 0; i < size; ++i)
                samples[i] = random.Next(int.MinValue, int.MaxValue);

            Stopwatch sw1 = new Stopwatch();
            sw1.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_IfChain();
            sw1.Stop();
            Console.WriteLine($"IfChain: {sw1.ElapsedMilliseconds} ms");

            Stopwatch sw2 = new Stopwatch();
            sw2.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_Log10();
            sw2.Stop();
            Console.WriteLine($"Log10: {sw2.ElapsedMilliseconds} ms");

            Stopwatch sw3 = new Stopwatch();
            sw3.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_While();
            sw3.Stop();
            Console.WriteLine($"While: {sw3.ElapsedMilliseconds} ms");

            Stopwatch sw4 = new Stopwatch();
            sw4.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_String();
            sw4.Stop();
            Console.WriteLine($"String: {sw4.ElapsedMilliseconds} ms");


            // Start of consistency tests:
            Console.WriteLine("Running consistency tests...");
            bool isConsistent = true;

            // Consistency test on random set:
            for (int i = 0; i < samples.Length; ++i)
            {
                int s = samples[i];
                int a = s.Digits_IfChain();
                int b = s.Digits_Log10();
                int c = s.Digits_While();
                int d = s.Digits_String();
                if (a != b || c != d || a != c)
                {
                    Console.WriteLine($"Digits({s}): IfChain={a} Log10={b} While={c} String={d}");
                    isConsistent = false;
                    break;
                }
            }

            // Consistency test of special values:
            samples = new int[]
            {
                0,
                int.MinValue, -1000000000, -999999999, -100000000, -99999999, -10000000, -9999999, -1000000, -999999, -100000, -99999, -10000, -9999, -1000, -999, -100, -99, -10, -9, - 1,
                int.MaxValue, 1000000000, 999999999, 100000000, 99999999, 10000000, 9999999, 1000000, 999999, 100000, 99999, 10000, 9999, 1000, 999, 100, 99, 10, 9,  1,
            };
            for (int i = 0; i < samples.Length; ++i)
            {
                int s = samples[i];
                int a = s.Digits_IfChain();
                int b = s.Digits_Log10();
                int c = s.Digits_While();
                int d = s.Digits_String();
                if (a != b || c != d || a != c)
                {
                    Console.WriteLine($"Digits({s}): IfChain={a} Log10={b} While={c} String={d}");
                    isConsistent = false;
                    break;
                }
            }

            // Consistency test result:
            if (isConsistent)
                Console.WriteLine("Consistency tests are OK");
        }

        // Int64 Performance Tests:
        private static void RunTests_Int64()
        {
            Console.WriteLine("\r\nInt64");

            const int size = 100000000;
            long[] samples = new long[size];
            Random random = new Random((int)DateTime.Now.Ticks);
            for (int i = 0; i < size; ++i)
                samples[i] = Math.Sign(random.Next(-1, 1)) * (long)(random.NextDouble() * long.MaxValue);

            Stopwatch sw1 = new Stopwatch();
            sw1.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_IfChain();
            sw1.Stop();
            Console.WriteLine($"IfChain: {sw1.ElapsedMilliseconds} ms");

            Stopwatch sw2 = new Stopwatch();
            sw2.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_Log10();
            sw2.Stop();
            Console.WriteLine($"Log10: {sw2.ElapsedMilliseconds} ms");

            Stopwatch sw3 = new Stopwatch();
            sw3.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_While();
            sw3.Stop();
            Console.WriteLine($"While: {sw3.ElapsedMilliseconds} ms");

            Stopwatch sw4 = new Stopwatch();
            sw4.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_String();
            sw4.Stop();
            Console.WriteLine($"String: {sw4.ElapsedMilliseconds} ms");

            // Start of consistency tests:
            Console.WriteLine("Running consistency tests...");
            bool isConsistent = true;

            // Consistency test on random set:
            for (int i = 0; i < samples.Length; ++i)
            {
                long s = samples[i];
                int a = s.Digits_IfChain();
                int b = s.Digits_Log10();
                int c = s.Digits_While();
                int d = s.Digits_String();
                if (a != b || c != d || a != c)
                {
                    Console.WriteLine($"Digits({s}): IfChain={a} Log10={b} While={c} String={d}");
                    isConsistent = false;
                    break;
                }
            }

            // Consistency test of special values:
            samples = new long[] 
            {
                0,
                long.MinValue, -1000000000000000000, -999999999999999999, -100000000000000000, -99999999999999999, -10000000000000000, -9999999999999999, -1000000000000000, -999999999999999, -100000000000000, -99999999999999, -10000000000000, -9999999999999, -1000000000000, -999999999999, -100000000000, -99999999999, -10000000000, -9999999999, -1000000000, -999999999, -100000000, -99999999, -10000000, -9999999, -1000000, -999999, -100000, -99999, -10000, -9999, -1000, -999, -100, -99, -10, -9, - 1,
                long.MaxValue, 1000000000000000000, 999999999999999999, 100000000000000000, 99999999999999999, 10000000000000000, 9999999999999999, 1000000000000000, 999999999999999, 100000000000000, 99999999999999, 10000000000000, 9999999999999, 1000000000000, 999999999999, 100000000000, 99999999999, 10000000000, 9999999999, 1000000000, 999999999, 100000000, 99999999, 10000000, 9999999, 1000000, 999999, 100000, 99999, 10000, 9999, 1000, 999, 100, 99, 10, 9,  1,
            };
            for (int i = 0; i < samples.Length; ++i)
            {
                long s = samples[i];
                int a = s.Digits_IfChain();
                int b = s.Digits_Log10();
                int c = s.Digits_While();
                int d = s.Digits_String();
                if (a != b || c != d || a != c)
                {
                    Console.WriteLine($"Digits({s}): IfChain={a} Log10={b} While={c} String={d}");
                    isConsistent = false;
                    break;
                }
            }

            // Consistency test result:
            if (isConsistent)
                Console.WriteLine("Consistency tests are OK");
        }
    }
}

4
Tôi thích giải pháp này, nó dễ đọc hơn nhiều so với các thủ thuật toán học và tốc độ nói lên chính nó, kudo.
MrLore 17/07/18

3
Tại sao điều này không được đánh dấu là giải pháp? Vấn đề về hiệu suất và đây dường như là câu trả lời rộng rãi nhất.
Martien de Jong

Thật thú vị, tôi nhận được kết quả khác nhau . Đối với các giá trị ngẫu nhiên Log10 và brute force gần như giống nhau nhưng đối với long.MaxValueLog10 thì tốt hơn đáng kể. Hay chỉ là trong .NET Core?
György Kőszeg

@ GyörgyKőszeg: Tôi đã thêm các thử nghiệm cho Int64. Xin lưu ý rằng các bài kiểm tra Int32Int64tạo các bộ dữ liệu khác nhau, có thể giải thích tại sao Int64nhanh hơn Int32trong một số trường hợp. Mặc dù, trong Int32thử nghiệm và trong Int64thử nghiệm, các bộ dữ liệu không bị thay đổi khi thử nghiệm các phương pháp tính toán khác nhau. Bây giờ liên quan đến .NET Core, tôi nghi ngờ rằng có bất kỳ tối ưu hóa kỳ diệu nào trong thư viện Math sẽ thay đổi những kết quả này, nhưng tôi rất muốn nghe thêm về điều đó (câu trả lời của tôi đã rất lớn, có lẽ là một trong những điều lớn nhất trong SO ;-)
sɐunıɔ ןɐ qɐp

@ GyörgyKőszeg: Ngoài ra, việc đo lường hiệu suất cấp thấp rất phức tạp. Tôi thường thích giữ các mã như đơn giản càng tốt (tôi thích đơn giản forvòng qua enumerations, tôi trước quá trình tập hợp dữ liệu ngẫu nhiên, và tránh việc sử dụng Generics, Tasks, Function<>, Action<>, hoặc bất kỳ khuôn khổ đo đen đóng hộp). Tóm lại, hãy giữ nó đơn giản. Tôi cũng giết tất cả các ứng dụng không cần thiết (Skype, Windows Defender, vô hiệu hóa Anti-Virus, Chrome, Microsoft Office cache, v.v.).
sɐunıɔ ןɐ qɐp

13

Không trực tiếp C #, nhưng công thức là: n = floor(log10(x)+1)


2
log10 (0) là -infinity
Alex Klaus

2
@Klaus - log10 (0) thực sự không được xác định. Tuy nhiên, bạn nói đúng rằng đó là một trường hợp đặc biệt cần được xét nghiệm và điều trị riêng. Điều này cũng đúng với bất kỳ số nguyên dương nào. Xem bình luận cho câu trả lời của Steve.
ysap

@ysap: Log10 khá phức tạp để hoạt động chính xác. Bạn có bất kỳ ý tưởng nào về cách triển khai nó một cách chính xác cho tất cả các phạm vi giá trị đầu vào có thể có không?
sɐunıɔ ןɐ qɐp

@ sɐunıɔ ןɐ qɐp - log10trong hầu hết các trường hợp là một hàm thư viện. Tại sao bạn muốn tự mình thực hiện và bạn gặp phải những vấn đề gì? log10(x) = log2(x) / log2(10), hoặc nói chung logA(x) = logB(x) / logB(A).
ysap

Tôi không có ý thực hiện lại Log10, ý tôi Log10(0)là -infinity. Log10 không thể được sử dụng để tính toán số chữ số của số âm trừ khi bạn sử dụng Math.Abs()trước khi chuyển giá trị cho Log10. Nhưng sau đó Math.Abs(int.MinValue)ném một Ngoại lệ ( long.MinValuecũng như trong trường hợp Int64). Nếu chúng ta ép số lên gấp đôi trước khi chuyển nó đến Log10, thì nó hoạt động với hầu hết tất cả các số ngoại trừ -999999999999999999(trong trường hợp Int64). Bạn có biết bất kỳ công thức nào để tính số chữ số sử dụng log10 và chấp nhận bất kỳ giá trị int32 hoặc int64 nào làm đầu vào và đầu ra chỉ các giá trị hợp lệ không?
sɐunıɔ ןɐ qɐp

9

Các câu trả lời đã có ở đây hoạt động đối với số nguyên không dấu, nhưng tôi chưa tìm thấy giải pháp tốt để nhận số chữ số từ số thập phân và nhân đôi.

public static int Length(double number)
{
    number = Math.Abs(number);
    int length = 1;
    while ((number /= 10) >= 1)
        length++;
    return length;
}
//number of digits in 0 = 1,
//number of digits in 22.1 = 2,
//number of digits in -23 = 2

Bạn có thể thay đổi loại đầu vào từ doubleđể decimalnếu chính xác vấn đề, nhưng số thập phân có một giới hạn quá.


7

Câu trả lời của Steve là đúng , nhưng nó không hiệu quả với các số nguyên nhỏ hơn 1.

Đây là một phiên bản cập nhật hoạt động cho phủ định:

int digits = n == 0 ? 1 : Math.Floor(Math.Log10(Math.Abs(n)) + 1)

Bạn đang thiếu một int castingto:digits = n == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(n)) + 1);
sɐunıɔ ןɐ qɐp

Tôi đã làm điều đó mà không có câu lệnh if: digit = (int) Math.Floor (Math.Abs ​​(Math.Log10 (Math.Abs ​​(n))) + 1)
KOLRH

Điều này ném ra một Ngoại lệ khi n = int.MinValue.
sɐunıɔ ןɐ qɐp

5

Sử dụng đệ quy (đôi khi được hỏi khi phỏng vấn)

public int CountDigits(int number)
{
    // In case of negative numbers
    number = Math.Abs(number);

    if (number >= 10)
        return CountDigits(number / 10) + 1;
    return 1;
 }

1
Điều này ném ra một Ngoại lệ khi number = int.MinValue.
sɐunıɔ ןɐ qɐp

4
static void Main(string[] args)
{
    long blah = 20948230498204;
    Console.WriteLine(blah.ToString().Length);
}

2
Cảnh giác với những âm: -1= 2
MrLore

2

Đây là cách triển khai sử dụng tìm kiếm nhị phân. Có vẻ là nhanh nhất cho đến nay trên int32.

Việc triển khai Int64 được để lại như một bài tập cho người đọc (!)

Tôi đã thử sử dụng Array.BinarySearch thay vì mã hóa cứng cây, nhưng tốc độ đó chỉ bằng một nửa.

CHỈNH SỬA: Bảng tra cứu nhanh hơn nhiều so với tìm kiếm nhị phân, với chi phí sử dụng nhiều bộ nhớ hơn. Thực tế, tôi có thể sử dụng tìm kiếm nhị phân trong sản xuất, bảng tra cứu rất phức tạp để tăng tốc độ có thể bị lu mờ bởi các phần khác của phần mềm.

Lookup-Table: 439 ms
Binary-Search: 1069 ms
If-Chain: 1409 ms
Log10: 1145 ms
While: 1768 ms
String: 5153 ms

Phiên bản bảng tra cứu:

static byte[] _0000llll = new byte[0x10000];
static byte[] _FFFFllll = new byte[0x10001];
static sbyte[] _hhhhXXXXdigits = new sbyte[0x10000];

// Special cases where the high DWORD is not enough information to find out how
// many digits.
static ushort[] _lowordSplits = new ushort[12];
static sbyte[] _lowordSplitDigitsLT = new sbyte[12];
static sbyte[] _lowordSplitDigitsGE = new sbyte[12];

static Int32Extensions()
{
    // Simple lookup tables for number of digits where value is 
    //    0000xxxx (0 .. 65535)
    // or FFFFxxxx (-1 .. -65536)
    precomputePositiveLo16();
    precomputeNegativeLo16();

    // Hiword is a little more complex
    precomputeHiwordDigits();
}

private static void precomputeHiwordDigits()
{
    int b = 0;

    for(int hhhh = 0; hhhh <= 0xFFFF; hhhh++)
    {
        // For hiword hhhh, calculate integer value for loword of 0000 and FFFF.
        int hhhh0000 = (unchecked(hhhh * 0x10000));  // wrap around on negatives
        int hhhhFFFF = hhhh0000 + 0xFFFF;

        // How many decimal digits for each?
        int digits0000 = hhhh0000.Digits_IfChain();
        int digitsFFFF = hhhhFFFF.Digits_IfChain();

        // If same number of decimal digits, we know that when we see that hiword
        // we don't have to look at the loword to know the right answer.
        if(digits0000 == digitsFFFF)
        {
            _hhhhXXXXdigits[hhhh] = (sbyte)digits0000;
        }
        else
        {
            bool negative = hhhh >= 0x8000;

            // Calculate 10, 100, 1000, 10000 etc
            int tenToThePower = (int)Math.Pow(10, (negative ? digits0000 : digitsFFFF) - 1);

            // Calculate the loword of the 10^n value.
            ushort lowordSplit = unchecked((ushort)tenToThePower);
            if(negative)
                lowordSplit = unchecked((ushort)(2 + (ushort)~lowordSplit));

            // Store the split point and digits into these arrays
            _lowordSplits[b] = lowordSplit;
            _lowordSplitDigitsLT[b] = (sbyte)digits0000;
            _lowordSplitDigitsGE[b] = (sbyte)digitsFFFF;

            // Store the minus of the array index into the digits lookup. We look for
            // minus values and use these to trigger using the split points logic.
            _hhhhXXXXdigits[hhhh] = (sbyte)(-b);
            b++;
        }
    }
}

private static void precomputePositiveLo16()
{
    for(int i = 0; i <= 9; i++)
        _0000llll[i] = 1;

    for(int i = 10; i <= 99; i++)
        _0000llll[i] = 2;

    for(int i = 100; i <= 999; i++)
        _0000llll[i] = 3;

    for(int i = 1000; i <= 9999; i++)
        _0000llll[i] = 4;

    for(int i = 10000; i <= 65535; i++)
        _0000llll[i] = 5;
}

private static void precomputeNegativeLo16()
{
    for(int i = 0; i <= 9; i++)
        _FFFFllll[65536 - i] = 1;

    for(int i = 10; i <= 99; i++)
        _FFFFllll[65536 - i] = 2;

    for(int i = 100; i <= 999; i++)
        _FFFFllll[65536 - i] = 3;

    for(int i = 1000; i <= 9999; i++)
        _FFFFllll[65536 - i] = 4;

    for(int i = 10000; i <= 65535; i++)
        _FFFFllll[65536 - i] = 5;
}



public static int Digits_LookupTable(this int n)
{
    // Split input into low word and high word.
    ushort l = unchecked((ushort)n);
    ushort h = unchecked((ushort)(n >> 16));

    // If the hiword is 0000 or FFFF we have precomputed tables for these.
    if(h == 0x0000)
    {
        return _0000llll[l];
    }
    else if(h == 0xFFFF)
    {
        return _FFFFllll[l];
    }

    // In most cases the hiword will tell us the number of decimal digits.
    sbyte digits = _hhhhXXXXdigits[h];

    // We put a positive number in this lookup table when
    // hhhh0000 .. hhhhFFFF all have the same number of decimal digits.
    if(digits > 0)
        return digits;

    // Where the answer is different for hhhh0000 to hhhhFFFF, we need to
    // look up in a separate array to tell us at what loword the change occurs.
    var splitIndex = (sbyte)(-digits);

    ushort lowordSplit = _lowordSplits[splitIndex];

    // Pick the correct answer from the relevant array, depending whether
    // our loword is lower than the split point or greater/equal. Note that for
    // negative numbers, the loword is LOWER for MORE decimal digits.
    if(l < lowordSplit)
        return _lowordSplitDigitsLT[splitIndex];
    else
        return _lowordSplitDigitsGE[splitIndex];
}

Phiên bản tìm kiếm nhị phân

        public static int Digits_BinarySearch(this int n)
        {
            if(n >= 0)
            {
                if(n <= 9999) // 0 .. 9999
                {
                    if(n <= 99) // 0 .. 99
                    {
                        return (n <= 9) ? 1 : 2;
                    }
                    else // 100 .. 9999
                    {
                        return (n <= 999) ? 3 : 4;
                    }
                }
                else // 10000 .. int.MaxValue
                {
                    if(n <= 9_999_999) // 10000 .. 9,999,999
                    {
                        if(n <= 99_999)
                            return 5;
                        else if(n <= 999_999)
                            return 6;
                        else
                            return 7;
                    }
                    else // 10,000,000 .. int.MaxValue
                    {
                        if(n <= 99_999_999)
                            return 8;
                        else if(n <= 999_999_999)
                            return 9;
                        else
                            return 10;
                    }
                }
            }
            else
            {
                if(n >= -9999) // -9999 .. -1
                {
                    if(n >= -99) // -99 .. -1
                    {
                        return (n >= -9) ? 1 : 2;
                    }
                    else // -9999 .. -100
                    {
                        return (n >= -999) ? 3 : 4;
                    }
                }
                else // int.MinValue .. -10000
                {
                    if(n >= -9_999_999) // -9,999,999 .. -10000
                    {
                        if(n >= -99_999)
                            return 5;
                        else if(n >= -999_999)
                            return 6;
                        else
                            return 7;
                    }
                    else // int.MinValue .. -10,000,000 
                    {
                        if(n >= -99_999_999)
                            return 8;
                        else if(n >= -999_999_999)
                            return 9;
                        else
                            return 10;
                    }
                }
            }
        }

        Stopwatch sw0 = new Stopwatch();
        sw0.Start();
        for(int i = 0; i < size; ++i) samples[i].Digits_BinarySearch();
        sw0.Stop();
        Console.WriteLine($"Binary-Search: {sw0.ElapsedMilliseconds} ms");

Cách tiếp cận rất thú vị. Nó thực sự nhanh hơn các phương thức "Log10", "string.Length" và "While" cho các giá trị số nguyên được phân phối đồng đều. Trong các kịch bản trường hợp thực, việc phân phối các giá trị nguyên phải luôn được xem xét trên các giải pháp giống chuỗi if. +1
sɐunıɔ ןɐ qɐp

Phương pháp LookUpTable dường như là siêu nhanh đối với các trường hợp mà truy cập bộ nhớ không phải là nút cổ chai. Tôi thực sự tin rằng đối với các tình huống có truy cập bộ nhớ thường xuyên, LookUpTable sẽ chậm hơn so với các phương thức if-chain, như BinSearch mà bạn đã đề xuất. Nhân tiện, bạn có Int64triển khai cho LookUpTable không? Hay bạn nghĩ rằng nó quá phức tạp để thực hiện nó? Tôi muốn chạy các bài kiểm tra hiệu suất sau trên toàn bộ.
sɐunıɔ ןɐ qɐp

Này, không đi xa như 64-bit. Nguyên tắc sẽ phải hơi khác ở chỗ bạn cần cấp 4x thay vì chỉ hiword và loword. Chắc chắn đồng ý rằng trong thế giới thực, bộ nhớ cache CPU của bạn sẽ có rất nhiều nhu cầu cạnh tranh khác về không gian và có rất nhiều chỗ để cải thiện trong việc giảm kích thước tra cứu (>> 1 thì chỉ nghĩ đến số chẵn) . Tìm kiếm nhị phân có thể được cải thiện bằng cách thiên về 9,10,8 chữ số thay vì 1,2,3,4 - dựa trên sự phân phối của tập dữ liệu ngẫu nhiên của bạn.
Alan Singfield

1

chia một số cho 10 sẽ cho bạn chữ số gần nhất bên trái, sau đó thực hiện mod 10 trên số đó sẽ cho số không có chữ số đầu tiên và lặp lại điều đó cho đến khi bạn có tất cả các chữ số


0
int i = 855865264;
int NumLen = i.ToString().Length;

2
không thành công cho số nguyên âm và cho các số như 23,00. Làm string.TrimStart('-')tốt hơn
nawfal 14/12/12

0

Tạo một phương thức trả về tất cả các chữ số và một phương thức khác đếm chúng:

public static int GetNumberOfDigits(this long value)
{
    return value.GetDigits().Count();
}

public static IEnumerable<int> GetDigits(this long value)
{
    do
    {
        yield return (int)(value % 10);
        value /= 10;
    } while (value != 0);
}

Đây là cách tiếp cận trực quan hơn đối với tôi khi giải quyết vấn đề này. Tôi đã thử Log10phương pháp này trước tiên do tính đơn giản rõ ràng của nó, nhưng nó có một số lượng lớn các trường hợp góc và các vấn đề về độ chính xác.

Tôi cũng thấy if-chain được đề xuất trong câu trả lời khác hơi xấu xí khi nhìn vào.

Tôi biết đây không phải là phương pháp hiệu quả nhất, nhưng nó cung cấp cho bạn phần mở rộng khác để trả về các chữ số cũng như cho các mục đích sử dụng khác (bạn chỉ có thể đánh dấu nó privatenếu bạn không cần sử dụng nó bên ngoài lớp học).

Hãy nhớ rằng nó không coi dấu âm là một chữ số.


-2

chuyển đổi thành chuỗi và sau đó bạn có thể đếm không có chữ số bằng phương thức .length. Giống:

String numberString = "855865264".toString();
int NumLen = numberString .Length;

1
Phân bổ một chuỗi là hoàn toàn không cần thiết.
Krythic

-2

Nó phụ thuộc chính xác những gì bạn muốn làm với các chữ số. Bạn có thể lặp lại các chữ số bắt đầu từ chữ số cuối cùng đến chữ số đầu tiên như sau:

int tmp = number;
int lastDigit = 0;
do
{
    lastDigit = tmp / 10;
    doSomethingWithDigit(lastDigit);
    tmp %= 10;
} while (tmp != 0);

1
Logic của bạn bị đảo ngược. Bạn cần sử dụng %để lấy chữ số, và sau đó /=cắt nó xuống.
julealgon


-3

Giả sử câu hỏi của bạn đề cập đến một số nguyên, thì những điều sau đây cũng hoạt động cho âm / dương và 0:

Math.Floor((decimal) Math.Abs(n)).ToString().Length
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.