Bir sayıdaki toplam basamak sayısını nasıl alabilirim?


114

C # 'da bir sayının toplam basamak sayısını nasıl alabilirim? Örneğin, 887979789 numarasının 9 hanesi vardır.


6
.Length kullanmayı deneyin, işe yaramazsa önce onu bir dizeye dönüştürün
Breezer

Diyelim ki x = 887979789; x.ToString () (Sayı).; sana bunu verecektir.
nPcomp

Yanıtlar:


175

Bir dizeye dönüştürmeden şunları deneyebilirsiniz:

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

Ysap'ın yorumundan sonraki düzeltme:

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

10
Korkarım ceil (log10 (10)) = ceil (1) = 1 ve bu soru için olması gerektiği gibi 2 değil!
ysap

3
Teşekkürler, bu güzel bir yöntem. İnt count = 0'dan daha hızlı olmasa da; {count ++; } süre ((i / = 10)> = 1); :(
Puterdo Borato

3
Sayı aralığınız negatifler içeriyorsa, Math.Floor (Math.Log10 (Math.Abs ​​(n)) + 1) kullanmanız gerekir;
mrcrowl

1
Eh, eğer nis 0sadece dönebilirsiniz 1:) Çok kolu negatif değerler sadece yerini nile Math.Abs(n).
Umair

3
@Puterdo Borato: Benim performans testim, hane sayısı <5 olduğunda yönteminizin daha hızlı olduğunu gösterdi. Bunu geçin, Steve'in Math.floor'u daha hızlı.
stack247

83

Bunu dene:

myint.ToString().Length

İşe yarıyor mu ?


25
Negatif sayılarla uğraşıyorsanız, muhtemelen bu yöntemle sorun yaşayacağınızı belirtmekte fayda var. (Açıkçası ondalık sayılar, ancak örnek bir kullanıyor int, bu yüzden bunun bir sorun olmadığını varsayıyorum.)
Cody Gray

2
@Krythic dize tahsisi, .NET dünyasındaki yeni çılgınlıktır.
nawfal

1
yeni? Zorlukla. 2010'da fena halde dizeleri tahsis ediyordum. Ne kadar trend belirleyici. Lol. Yine de haklısın. Bu kirli!
Andiih

3
@Krythic 1980'ler değil, bilgisayarınızda bir işlem süresince 10 karakterlik bir diziyi belleğe kaydetmek için yeterli RAM var.
MrLore

2
@MrLore Basit uygulamalarda bu doğru olabilir, ancak oyun geliştirme dünyasında tamamen farklı bir canavar.
Krythic

48

Çözüm

Aşağıdaki uzatma yöntemlerinden herhangi biri işi yapacaktır. Hepsi eksi işaretini bir rakam olarak kabul eder ve tüm olası giriş değerleri için doğru şekilde çalışır. Ayrıca .NET Framework ve .NET Core için de çalışırlar. Bununla birlikte, Platform / Çerçeve seçiminize bağlı olarak ilgili performans farklılıkları (aşağıda tartışılmıştır) vardır.

Int32 sürümü:

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

Int64 sürümü:

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

Tartışma

Bu yanıt, her ikisi için yapılan testleri içermektedir Int32ve Int64bir dizi kullanarak türleri, 100.000.000rasgele numune int/ longsayı. Rastgele veri kümesi, testler yürütülmeden önce bir diziye önceden işlenir.

4 farklı yöntemleri arasında tutarlılık testleri de idam edildi için MinValuenegatif sınır durumlarda, -1, 0, 1, pozitif sınır vakalar, MaxValueve aynı zamanda bütün rasgele veri kümesi için. LOG10 yöntemi HARİÇ yukarıda sağlanan yöntemler için tutarlılık testi başarısız olur (bu daha sonra tartışılacaktır).

Testler yapıldı .NET Framework 4.7.2ve .NET Core 2.2; için x86ve x64platformlar, 64-bit Intel İşlemci makinede ile Windows 10ve ile VS2017 v.15.9.17. Aşağıdaki 4 durum, performans sonuçları üzerinde aynı etkiye sahiptir:

.NET Framework (x86)

  • Platform = x86

  • Platform = AnyCPU, Prefer 32-bitproje ayarlarında kontrol edilir

.NET Framework (x64)

  • Platform = x64

  • Platform = AnyCPU, Prefer 32-bitProje ayarlarında işaretlenmemiş olduğundan

.NET Çekirdeği (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 Çekirdeği (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

Sonuçlar

Aşağıdaki performans testleri, bir tamsayının alabileceği geniş değer aralığı arasında eşit bir değer dağılımı üretir. Bu, çok sayıda basamaklı değerleri test etme şansının çok daha yüksek olduğu anlamına gelir. Gerçek hayat senaryolarında çoğu değer küçük olabilir, bu nedenle IF-CHAIN ​​daha da iyi performans göstermelidir. Ayrıca işlemci, veri kümenize göre IF-CHAIN ​​kararlarını önbelleğe alır ve optimize eder.

Şöyle @AlanSingfield yorum bölümünde işaret LOG10 usul bir döküm ile sabit zorunda doubleMath.Abs()giriş değeri olduğunda durum için int.MinValueveya long.MinValue.

Bu soruyu düzenlemeden önce uyguladığım erken performans testleri ile ilgili olarak (zaten milyonlarca kez düzenlenmesi gerekiyordu), IF-CHAIN ​​yönteminin LOG10 yönteminden daha yavaş çalıştığı @ GyörgyKőszeg tarafından belirtilen özel bir durum vardı .

@ AlanSingfield tarafından belirtilen sorunun düzeltilmesinden sonra farkın büyüklüğü çok daha düşük olmasına rağmen, bu yine de oluyor . Bu düzeltme (atama ekleme double), girdi değeri tam olarak olduğunda bir hesaplama hatasına neden olur -999999999999999999: 20bunun yerine LOG10 yöntemi geri döner 19. LOG10 yöntemi ayrıca ifgiriş değerinin sıfır olduğu durum için bir korumaya sahip olmalıdır .

LOG10 yöntemi, tüm değerler için çalışmak oldukça zordur, bu da bundan kaçınmanız gerektiği anlamına gelir. Birisi aşağıdaki tüm tutarlılık testleri için doğru çalışmasını sağlamanın bir yolunu bulursa, lütfen bir yorum gönderin!

WHILE yöntemi aynı zamanda daha hızlı, ancak yine de yavaş olan yeni bir yeniden düzenlenmiş sürümü aldı Platform = x86(şimdiye kadar nedenini bulamadım).

STRING yöntemi sürekli olarak yavaştır: iştahla boşuna çok fazla bellek ayırır. İlginç bir şekilde, .NET Core'da dize ayırma, .NET Framework'tekinden çok daha hızlı görünüyor. Bunu bildiğim iyi oldu.

IF-CHAIN ​​yöntemi, vakaların% 99,99'unda diğer tüm yöntemlerden daha iyi performans göstermelidir; ve benim kişisel görüşüme göre, en iyi seçiminiz (LOG10 yönteminin doğru çalışması için gerekli tüm ayarlamaları ve diğer iki yöntemin kötü performansını göz önünde bulundurarak).

Son olarak sonuçlar:

görüntü açıklamasını buraya girin

Bu sonuçlar donanıma bağlı olduğundan, özel durumunuzda% 100 emin olmanız gerekiyorsa, yine de aşağıdaki performans testlerini kendi bilgisayarınızda çalıştırmanızı tavsiye ederim.

Test Kodu

Aşağıda performans testinin kodu ve tutarlılık testi bulunmaktadır. Aynı kod hem .NET Framework hem de .NET Core için kullanılır.

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
Bu çözümü seviyorum, matematik numaralarından çok daha okunaklı ve hız kendisi için konuşuyor, tebrikler.
MrLore

3
Bu neden çözüm olarak işaretlenmedi? Performans önemlidir ve bu en kapsamlı cevap gibi görünüyor.
Martien de Jong

İlginç, farklı sonuçlar alıyorum . Rasgele değerler için Log10 ve kaba kuvvet neredeyse aynıdır, ancak long.MaxValueLog10 için önemli ölçüde daha iyidir. Yoksa sadece .NET Core'da mı?
György Kőszeg

@ GyörgyKőszeg: Int64 için testler ekledim. Lütfen farklı veri kümeleri için yapılan testlerin Int32ve Int64ürettiğinin farkında olun; bu , neden bazı durumlarda Int64olduğundan daha hızlı olduğunu açıklayabilir Int32. Bununla birlikte, Int32test içinde ve test içinde Int64, farklı hesaplama yöntemlerini test ederken veri kümeleri değiştirilmez. Şimdi .NET Core ile ilgili olarak, Matematik kütüphanesinde bu sonuçları değiştirecek sihirli bir optimizasyon olduğundan şüpheliyim, ancak bunun hakkında daha fazlasını duymak isterim (cevabım zaten çok büyük, muhtemelen SO'daki en büyük cevaplardan biri ;-)
sɐunıɔ ןɐ qɐp

@ GyörgyKőszeg: Ayrıca, düşük seviyeli performans ölçümleri çok zordur. Genellikle mümkün olduğunca basit olarak kod tutmayı tercih (Basit tercih forüzerinde döngüler enumerations, ben ön işlem rasgele veri kümeleri ve jenerik, Görevler, kullanımını önlemek Function<>, Action<>ya da herhangi bir siyah-kutulu ölçüm çerçevesi). Özetle, basit tutun. Ayrıca tüm gereksiz uygulamaları da (Skype, Windows Defender, Anti-Virüs'ü devre dışı bırak, Chrome, Microsoft Office önbelleği vb.) Öldürürüm.
sɐunıɔ ןɐ qɐp

13

Doğrudan C # değil, ancak formül şudur: n = floor(log10(x)+1)


2
log10 (0) is -infinity
Alex Klaus

2
@Klaus - log10 (0) aslında tanımsızdır. Ancak, ayrı ayrı test edilmesi ve ele alınması gereken özel bir durum olduğu konusunda haklısınız. Bu, pozitif olmayan herhangi bir tam sayı için de geçerlidir. Steve'in cevabına yapılan yorumları görün.
ysap

@ysap: Log10 doğru şekilde çalışmak için oldukça zor. Tüm olası giriş değerleri aralığı için nasıl doğru bir şekilde uygulanacağı konusunda herhangi bir fikriniz var mı?
sɐunıɔ ןɐ qɐp

@ sɐunıɔ ןɐ qɐp - log10çoğu durumda bir kitaplık işlevidir. Neden bunu kendiniz uygulamak istiyorsunuz ve hangi sorunlarla karşılaşıyorsunuz? log10(x) = log2(x) / log2(10)veya genel olarak logA(x) = logB(x) / logB(A).
ysap

Log10(0)Log10'u tekrar uygulamak istemedim, yani -infinity. Log10 Math.Abs(), değeri Log10'a geçirmeden önce kullanmadığınız sürece, negatif sayıların basamak sayısını hesaplamak için kullanılamaz . Ancak daha sonra Math.Abs(int.MinValue)bir İstisna atar ( long.MinValueInt64 durumunda da). Sayıyı Log10'a geçmeden önce ikiye katlarsak, -999999999999999999(Int64 durumunda) hariç hemen hemen tüm sayılar için çalışır . Log10 kullanan ve herhangi bir int32 veya int64 değerini girdi olarak kabul eden ve yalnızca geçerli değerleri çıkaran basamak sayısını hesaplamak için herhangi bir formül biliyor musunuz?
sɐunıɔ ןɐ qɐp

9

Cevaplar zaten burada işaretsiz tamsayılar için işe yarıyor, ancak ondalıklardan ve çiftlerden basamak sayısı elde etmek için iyi çözümler bulamadım.

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

Sen giriş tipini değiştirebilir doubleiçin decimalhassas konularda, fakat ondalık de bir sınırı vardır.


7

Steve'in cevabı doğrudur , ancak 1'den küçük tamsayılar için işe yaramaz.

İşte negatifler için çalışan güncellenmiş bir sürüm:

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

İnt için bir döküm eksik:digits = n == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(n)) + 1);
sɐunıɔ ןɐ qɐp

İf ifadesi olmadan yaptım: digits = (int) Math.Floor (Math.Abs ​​(Math.Log10 (Math.Abs ​​(n))) + 1)
KOLRH

Bu, ne zaman bir İstisna oluşturur n = int.MinValue.
sɐunıɔ ןɐ qɐp

5

Özyineleme kullanma (bazen röportajlarda sorulur)

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
Bu, ne zaman bir İstisna oluşturur number = int.MinValue.
sɐunıɔ ןɐ qɐp

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

2
Negatiflere dikkat edin: -1= 2
MrLore

2

İşte ikili arama kullanan bir uygulama. Int32'de şimdiye kadarki en hızlısı gibi görünüyor.

Int64 uygulaması okuyucu için bir alıştırma olarak bırakılmıştır (!)

Ağacı sabit kodlamak yerine Array.BinarySearch kullanmayı denedim, ancak bu hızın yarısı kadardı.

DÜZENLEME: Bir arama tablosu, daha fazla bellek kullanma pahasına, ikili aramadan çok daha hızlıdır. Gerçekçi bir şekilde, muhtemelen üretimde ikili aramayı kullanırdım, arama tablosu, yazılımın diğer bölümleri tarafından gölgede bırakılması muhtemel bir hız kazancı için çok karmaşıktır.

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

Arama tablosu sürümü:

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

İkili arama sürümü

        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");

Çok ilginç bir yaklaşım. Tekdüze dağıtılmış tamsayı değerleri için "Log10", "string.Length" ve "While" yöntemlerinden daha hızlıdır. Gerçek durum senaryolarında, tam sayı değerlerinin dağılımı her zaman if-zinciri benzeri çözümlerde dikkate alınmalıdır. +1
sɐunıɔ ןɐ qɐp

LookUpTable yaklaşımı, bellek erişiminin darboğaz olmadığı senaryolar için süper hızlı görünüyor. Sık bellek erişimi olan senaryolar için, LookUpTable'ın önerdiğiniz BinSearch gibi if-chain benzeri yöntemlerden daha yavaş olduğuna inanıyorum. Bu arada, Int64LookUpTable için uygulamanız var mı? Yoksa bunu uygulamanın çok mu karmaşık olduğunu mu düşünüyorsunuz? Performans testlerini daha sonra tüm sette çalıştırmak istiyorum.
sɐunıɔ ןɐ qɐp

Hey, 64 bitlik olana kadar gitmedim. Sadece hiword ve loword yerine 4x seviyelerine ihtiyacınız olacağı için ilke biraz farklı olmalıdır. Gerçek dünyada, CPU önbelleğinizin alan için diğer birçok rekabet ihtiyacına sahip olacağını ve aramanın boyutunu azaltmada iyileştirme için çok yer olduğunu kesinlikle kabul edin (>> 1 o zaman akla bile sayılar gelir) . İkili arama, rastgele veri kümenizin dağılımı göz önüne alındığında 1,2,3,4 yerine 9,10,8 basamağa doğru önyargılı olarak geliştirilebilir.
Alan Singfield

1

bir sayıyı 10'a bölmek size en soldaki basamağı verir, ardından sayı üzerinde 10 mod yapmak, sayıyı ilk basamaksız verir ve tüm basamakları elde edene kadar tekrarlayın


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

2
negatif int için ve 23.00 gibi sayılar için başarısız olur. Daha string.TrimStart('-')iyisini yap
nawfal

0

Tüm basamakları döndüren ve onları sayan bir yöntem oluşturun:

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

Bu sorunla uğraşırken bu bana daha sezgisel bir yaklaşım gibi geldi. Log10Yöntemi ilk önce görünürdeki basitliği nedeniyle denedim , ancak çok fazla köşe durumu ve hassaslık sorunları var.

Ayrıca ifdiğer cevapta önerilen-zinciri bakılması biraz çirkin buldum .

Bunun en verimli yöntem olmadığını biliyorum, ancak diğer kullanımlar için de rakamları döndürmek için size diğer uzantıyı verir ( privatesınıfın dışında kullanmanız gerekmiyorsa işaretleyebilirsiniz ).

Negatif işareti rakam olarak kabul etmediğini unutmayın.


-2

dizgeye dönüştürün ve sonra .length yöntemi ile rakamların sayısını sayabilirsiniz. Sevmek:

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

1
Bir dizge ayırmak tamamen gereksizdir.
Krythic

-2

Rakamlarla tam olarak ne yapmak istediğinize bağlı. Sonuncudan ilkine kadar basamakları şu şekilde yineleyebilirsiniz:

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

1
Mantığınız tersine döndü. Basamağı %almak için kullanmalı ve sonra /=kesmelisiniz.
julealgon


-3

Sorunuzun bir int ile ilgili olduğunu varsayarsak, aşağıdakiler negatif / pozitif ve sıfır için de işe yarar:

Math.Floor((decimal) Math.Abs(n)).ToString().Length
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.