Jak mogę obliczyć całkowitą liczbę cyfr w liczbie?

Odpowiedzi:

175

Bez konwersji na łańcuch możesz spróbować:

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

Korekta po komentarzu ysap:

Math.Floor(Math.Log10(n) + 1);
Steve
źródło
10
Obawiam się, że ceil (log10 (10)) = ceil (1) = 1, a nie 2, jak powinno być w przypadku tego pytania!
ysap
3
Dzięki, to niezła metoda. Chociaż nie jest szybsza niż int count = 0; do {count ++; } while ((i / = 10)> = 1); :(
Puterdo Borato
3
Jeśli twój zakres liczb zawiera liczby ujemne, musisz użyć Math.Floor (Math.Log10 (Math.Abs ​​(n)) + 1);
mrcrowl
1
Cóż, jeśli nto 0może po prostu zwrócić 1:) Wartości ujemne zbytnio obsłużą, po prostu zastąp nje Math.Abs(n).
Umair
3
@Puterdo Borato: mój test wydajności faktycznie wykazał, że twoja metoda jest szybsza, gdy liczba cyfr jest <5. Pomiń, Math.floor Steve'a jest szybszy.
stack247
83

Spróbuj tego:

myint.ToString().Length

Czy to działa ?

Andiih
źródło
25
Warto zauważyć, że prawdopodobnie napotkasz problemy z tą metodą, jeśli masz do czynienia z liczbami ujemnymi. (I oczywiście ułamki dziesiętne, ale w przykładzie użyto znaku int, więc zakładam, że to nie problem.)
Cody Gray
2
@Krythic przydzielanie ciągów znaków to nowy szał w świecie .NET.
nawfal
1
Nowy? Ledwie. W 2010 roku rażąco przydzielałem ciągi znaków. Co za wyznacznik trendów. Lol. Masz jednak rację. To jest brudne!
Andiih,
3
@Krythic To nie lata 80., Twój komputer ma wystarczającą ilość pamięci RAM, aby zapisać w pamięci ciąg 10 znaków na czas jednej operacji.
MrLore
2
@MrLore W prostych aplikacjach może to być prawda, ale w świecie tworzenia gier to zupełnie inna bestia.
Krythic
48

Rozwiązanie

Dowolna z poniższych metod rozszerzających wykona zadanie. Wszystkie traktują znak minus jako cyfrę i działają poprawnie dla wszystkich możliwych wartości wejściowych. Działają również dla .NET Framework i .NET Core. Istnieją jednak istotne różnice w wydajności (omówione poniżej), w zależności od wybranej platformy / struktury.

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

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

Dyskusja

Odpowiedź ta obejmuje testy wykonywane zarówno Int32i Int64rodzaju, z wykorzystaniem tablicy 100.000.000losowo wybranych int/ longnumerów. Losowy zestaw danych jest wstępnie przetwarzany na tablicę przed wykonaniem testów.

Testy spójności między 4 różnymi metodami również zostały wykonane, na MinValue, negatywnych przypadkach granicznych -1, 0, 1, pozytywne przypadki graniczne, MaxValuei również dla całego zestawu danych losowych. Żadne testy spójności nie kończą się niepowodzeniem w przypadku powyższych metod, Z WYJĄTKIEM metody LOG10 (zostanie to omówione później).

Testy zostały wykonane na .NET Framework 4.7.2i .NET Core 2.2; dla platform x86i x64platform, na komputerze z 64-bitowym procesorem Intel, zi Windows 10z VS2017 v.15.9.17. Poniższe 4 przypadki mają taki sam wpływ na wyniki wydajności:

.NET Framework (x86)

  • Platform = x86

  • Platform = AnyCPU, Prefer 32-bitjest zaznaczone w ustawieniach projektu

.NET Framework (x64)

  • Platform = x64

  • Platform = AnyCPU, Prefer 32-bitnie jest zaznaczone w ustawieniach projektu

.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

Wyniki

Poniższe testy wydajności dają jednolity rozkład wartości w szerokim zakresie wartości, jakie może przyjąć liczba całkowita. Oznacza to, że istnieje znacznie większa szansa na testowanie wartości z dużą liczbą cyfr. W rzeczywistych scenariuszach większość wartości może być małych, więc IF-CHAIN ​​powinien działać jeszcze lepiej. Ponadto procesor będzie buforował i optymalizował decyzje IF-CHAIN ​​zgodnie z Twoim zbiorem danych.

Jak @AlanSingfield wskazał w sekcji komentarzy, metoda LOG10 musiała zostać naprawiona z rzutowaniem do doublewewnątrz Math.Abs()w przypadku, gdy wartością wejściową jest int.MinValuelub long.MinValue.

Jeśli chodzi o wczesne testy wydajności, które zaimplementowałem przed edycją tego pytania (musiało być już edytowane milion razy), @ GyörgyKőszeg wskazał konkretny przypadek , w którym metoda IF-CHAIN ​​działa wolniej niż metoda LOG10.

Dzieje się tak nadal, chociaż skala różnicy stała się znacznie mniejsza po rozwiązaniu problemu wskazanego przez @AlanSingfield . Ta poprawka (dodanie rzutowania double) powoduje błąd obliczeniowy, gdy wartość wejściowa jest dokładnie -999999999999999999: metoda LOG10 zwraca 20zamiast 19. Metoda LOG10 musi również mieć ifochronę na wypadek, gdy wartość wejściowa wynosi zero.

Metoda LOG10 jest dość trudna do uzyskania działania dla wszystkich wartości, co oznacza, że ​​należy jej unikać. Jeśli ktoś znajdzie sposób na poprawne działanie wszystkich poniższych testów spójności, napisz komentarz!

Metoda WHILE również otrzymała ostatnio refaktoryzowaną wersję, która jest szybsza, ale nadal działa wolno Platform = x86(do tej pory nie mogłem znaleźć powodu dlaczego).

Metoda STRING jest konsekwentnie powolna: łapczywie przydziela zbyt dużo pamięci na nic. Co ciekawe, w .NET Core alokacja ciągów wydaje się być znacznie szybsza niż w .NET Framework. Dobrze wiedzieć.

Metoda IF-CHAIN ​​powinna przewyższać wszystkie inne metody w 99,99% przypadków; i, moim zdaniem, jest to najlepszy wybór (biorąc pod uwagę wszystkie poprawki niezbędne do prawidłowego działania metody LOG10 oraz słabe działanie pozostałych dwóch metod).

Ostatecznie wyniki są następujące:

wprowadź opis obrazu tutaj

Ponieważ wyniki te są zależne od sprzętu, mimo wszystko zalecam przeprowadzenie poniższych testów wydajności na własnym komputerze, jeśli naprawdę potrzebujesz 100% pewności w swoim konkretnym przypadku.

Kod testowy

Poniżej znajduje się kod testu wydajności, a także testu spójności. Ten sam kod jest używany zarówno dla .NET Framework, jak i .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");
        }
    }
}
sɐunıɔ ןɐ qɐp
źródło
4
Podoba mi się to rozwiązanie, jest dużo bardziej czytelne niż sztuczki matematyczne, a szybkość mówi sama za siebie, chwała.
MrLore,
3
Dlaczego nie jest to oznaczone jako rozwiązanie? Wydajność ma znaczenie i wydaje się, że jest to najbardziej obszerna odpowiedź.
Martien de Jong
Co ciekawe, otrzymuję różne wyniki . Dla wartości losowych Log10 i brutalna siła są prawie takie same, ale dla long.MaxValueLog10 jest znacznie lepsze. A może tylko w .NET Core?
György Kőszeg
@ GyörgyKőszeg: Dodałem testy dla Int64. Należy pamiętać, że testy Int32i Int64generują różne zestawy danych, co może wyjaśniać, dlaczego Int64stało się to szybciej niż Int32w niektórych przypadkach. Chociaż w ramach Int32testu i w ramach Int64testu zbiory danych nie ulegają zmianie podczas testowania różnych metod obliczeniowych. Jeśli chodzi o .NET Core, wątpię, czy jest jakaś magiczna optymalizacja w bibliotece Math, która zmieniłaby te wyniki, ale chciałbym usłyszeć więcej na ten temat (moja odpowiedź jest już ogromna, prawdopodobnie jedna z największych w SO ;-)
sɐunıɔ ןɐ qɐp
@ GyörgyKőszeg: Również pomiary wydajności na niskim poziomie są bardzo trudne. I zazwyczaj wolą zachować kod jak najprostsze (wolę proste forpętle nad enumerations, I pre-process losowe zestawy danych i unikanie stosowania leków generycznych, zadania Function<>, Action<>lub jakiegokolwiek czarnym-box ramach pomiaru). Podsumowując, nie komplikuj. Zabijam też wszystkie niepotrzebne aplikacje (Skype, Windows Defender, wyłączam antywirusa, Chrome, pamięć podręczną Microsoft Office itp.).
sɐunıɔ ןɐ qɐp
13

Nie bezpośrednio C #, ale formuła jest taka: n = floor(log10(x)+1)

ysap
źródło
2
log10 (0) to -infinity
Alex Klaus
2
@Klaus - log10 (0) jest faktycznie niezdefiniowane. Ale masz rację, ponieważ jest to szczególny przypadek, który należy zbadać i potraktować oddzielnie. Dotyczy to również wszystkich nie dodatnich liczb całkowitych. Zobacz komentarze do odpowiedzi Steve'a.
ysap
@ysap: Log10 jest dość trudny do poprawnego działania. Czy masz pomysł, jak go poprawnie zaimplementować dla wszystkich możliwych wartości wejściowych?
sɐunıɔ ןɐ qɐp
@ sɐunıɔ ןɐ qɐp - log10w większości przypadków jest funkcją biblioteczną. Dlaczego miałbyś chcieć to wdrożyć samodzielnie i jakie problemy napotykasz? log10(x) = log2(x) / log2(10)lub ogólnie logA(x) = logB(x) / logB(A).
ysap
Nie chciałem ponownie zaimplementować Log10, mam na myśli Log10(0)to-nieskończoność. Log10 nie może służyć do obliczania liczby cyfr liczb ujemnych, chyba że użyjesz go Math.Abs()przed przekazaniem wartości do Log10. Ale potem Math.Abs(int.MinValue)zgłasza wyjątek ( long.MinValuetakże w przypadku Int64). Jeśli -999999999999999999rzucimy liczbę na podwojenie przed przekazaniem jej do Log10, to działa ona dla prawie wszystkich liczb z wyjątkiem (w przypadku Int64). Czy znasz jakąś formułę do obliczania liczby cyfr, która używa log10 i akceptuje dowolną wartość int32 lub int64 jako dane wejściowe i wyświetla tylko prawidłowe wartości?
sɐunıɔ ןɐ qɐp
9

Odpowiedzi już tutaj działają dla liczb całkowitych bez znaku, ale nie znalazłem dobrego rozwiązania, aby uzyskać liczbę cyfr z liczb dziesiętnych i podwójnych.

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

Możesz zmienić typ danych wejściowych z doublena, decimaljeśli liczy się precyzja, ale liczba dziesiętna również ma limit.

nawfal
źródło
7

Odpowiedź Steve'a jest poprawna , ale nie działa dla liczb całkowitych mniejszych niż 1.

Oto zaktualizowana wersja, która działa w przypadku negatywów:

int digits = n == 0 ? 1 : Math.Floor(Math.Log10(Math.Abs(n)) + 1)
Patrick Hofman
źródło
Brakuje Ci castingu do int:digits = n == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(n)) + 1);
sɐunıɔ ןɐ qɐp
Zrobiłem to bez instrukcji if: digits = (int) Math.Floor (Math.Abs ​​(Math.Log10 (Math.Abs ​​(n))) + 1)
KOLRH
Powoduje to wyjątek, gdy n = int.MinValue.
sɐunıɔ ןɐ qɐp
5

Korzystanie z rekursji (czasami zadawane podczas wywiadów)

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

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

źródło
1
Powoduje to wyjątek, gdy number = int.MinValue.
sɐunıɔ ןɐ qɐp
4
static void Main(string[] args)
{
    long blah = 20948230498204;
    Console.WriteLine(blah.ToString().Length);
}
weloytty
źródło
2
Uważaj na negatywy: -1= 2
MrLore
2

Oto implementacja wykorzystująca wyszukiwanie binarne. Wygląda na to, że jak dotąd jest najszybszy na int32.

Implementację Int64 pozostawiono jako ćwiczenie dla czytelnika (!)

Próbowałem użyć Array.BinarySearch zamiast kodowania drzewa na stałe, ale to było o połowę wolniejsze.

EDYCJA: Tabela przeglądowa jest znacznie szybsza niż wyszukiwanie binarne kosztem użycia większej ilości pamięci. Realistycznie rzecz biorąc, prawdopodobnie użyłbym wyszukiwania binarnego w środowisku produkcyjnym, tabela wyszukiwania jest bardzo złożona, jeśli chodzi o wzrost szybkości, który prawdopodobnie zostanie przyćmiony przez inne części oprogramowania.

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

Wersja tabeli przeglądowej:

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

Wersja wyszukiwania binarnego

        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");
Alan Singfield
źródło
Bardzo ciekawe podejście. Jest rzeczywiście szybszy niż metody „Log10”, „string.Length” i „While” dla równomiernie rozłożonych wartości całkowitych. W rzeczywistych przypadkach rozkład wartości całkowitych musi być zawsze brany pod uwagę w rozwiązaniach typu if-chain. +1
sɐunıɔ ןɐ qɐp
Podejście LookUpTable wydaje się być bardzo szybkie w scenariuszach, w których dostęp do pamięci nie jest wąskim gardłem. Jestem głęboko przekonany, że w scenariuszach z częstym dostępem do pamięci LookUpTable działa wolniej niż metody łańcuchowe typu if, takie jak zasugerowana przez BinSearch. Przy okazji, czy masz Int64implementację LookUpTable? A może uważasz, że wdrożenie go jest zbyt skomplikowane? Chciałbym przeprowadzić testy wydajności później na całym zestawie.
sɐunıɔ ןɐ qɐp
Hej, nie dotarłem aż do wersji 64-bitowej. Zasada musiałaby być nieco inna, ponieważ potrzebujesz 4x poziomów, a nie tylko hiword i loword. Zdecydowanie zgadzam się, że w prawdziwym świecie pamięć podręczna procesora będzie miała wiele innych konkurencyjnych potrzeb w zakresie przestrzeni i jest dużo miejsca na poprawę w zmniejszaniu rozmiaru wyszukiwania (>> 1 wtedy przychodzą na myśl tylko liczby parzyste) . Wyszukiwanie binarne można poprawić, odchylając w kierunku 9,10,8 cyfr zamiast 1,2,3,4 - biorąc pod uwagę rozkład losowego zestawu danych.
Alan Singfield
1

podzielenie liczby przez 10 da ci ostatnią cyfrę z lewej strony, a następnie wykonanie mod 10 na liczbie da liczbę bez pierwszej cyfry i powtórz to, aż będziesz miał wszystkie cyfry

romejoe
źródło
0
int i = 855865264;
int NumLen = i.ToString().Length;
Javed Akram
źródło
2
nie powiedzie się dla ujemnej liczby całkowitej i dla liczb takich jak 23,00. Zrób string.TrimStart('-')lepiej
nawfal
0

Utwórz metodę, która zwraca wszystkie cyfry i inną, która je zlicza:

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

To było dla mnie bardziej intuicyjne podejście do rozwiązywania tego problemu. Log10Najpierw wypróbowałem tę metodę ze względu na jej pozorną prostotę, ale ma ona niesamowitą liczbę przypadków narożnych i problemy z precyzją.

Również if-chain zaproponowany w drugiej odpowiedzi uznałem za nieco brzydki.

Wiem, że nie jest to najbardziej wydajna metoda, ale daje ci drugie rozszerzenie do zwracania cyfr, a także do innych zastosowań (możesz je po prostu zaznaczyć, privatejeśli nie musisz używać go poza klasą).

Pamiętaj, że nie traktuje znaku minus jako cyfry.

julealgon
źródło
-2

zamień na ciąg, a następnie możesz policzyć liczbę cyfr według metody .length. Lubić:

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

źródło
1
Przydzielanie łańcucha jest całkowicie niepotrzebne.
Krythic
-2

Zależy, co dokładnie chcesz zrobić z cyframi. Możesz iterować po cyfrach, zaczynając od ostatniej do pierwszej, w ten sposób:

int tmp = number;
int lastDigit = 0;
do
{
    lastDigit = tmp / 10;
    doSomethingWithDigit(lastDigit);
    tmp %= 10;
} while (tmp != 0);
Mihran Hovsepyan
źródło
1
Twoja logika jest odwrócona. Musisz użyć, %aby uzyskać cyfrę, a następnie /=ją wyciąć.
julealgon
-3

Jeśli służy to tylko do walidacji, możesz zrobić: 887979789 > 99999999

Johan van der Slikke
źródło
-3

Zakładając, że twoje pytanie odnosiło się do int, poniższe działa również dla wartości ujemnych / pozytywnych i zerowych:

Math.Floor((decimal) Math.Abs(n)).ToString().Length
Petru Zaharia
źródło