Як я можу отримати підрахунок загальної кількості цифр у числі?


114

Як я можу отримати підрахунок загальної кількості цифр числа в C #? Наприклад, число 887979789 має 9 цифр.


6
спробуйте скористатися .Length, якщо це не працює, перетворіть його спочатку в рядок
Breezer

Скажімо, x = 887979789; x.ToString (). Count (); дасть тобі це.
nPcomp

Відповіді:


175

Без перетворення в рядок ви можете спробувати:

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

Виправлення після коментаря ysap:

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

10
Боюся ceil (log10 (10)) = ceil (1) = 1, а не 2, як це має бути для цього питання!
ysap

3
Дякую, це приємний метод. Хоча це не швидше, ніж int count = 0; do {count ++; } while ((i / = 10)> = 1); :(
Путердо Борато

3
Якщо ваш діапазон номерів містить мінуси, вам потрібно буде використовувати Math.Floor (Math.Log10 (Math.Abs ​​(n)) + 1);
mrcrowl

1
Добре, якщо nце 0може просто повернутися 1:) Занадто обробляти негативні значення просто замінити nна Math.Abs(n).
Умаїр

3
@Puterdo Borato: мій тест на ефективність насправді показав, що ваш метод швидший, коли кількість цифр становить <5. Передайте це, Math.floor Стіва швидше.
stack247

83

Спробуйте це:

myint.ToString().Length

Це працює?


25
Варто зазначити, що, швидше за все, у вас виникнуть проблеми з цим методом, якщо ви матимете справу з негативними цифрами. (І, очевидно, децималі, але в прикладі використовується знак int, тому я припускаю, що це не проблема.)
Коді Грей

2
@Krythic string виділення - це новий манія у світі .NET.
nawfal

1
новий? Навряд чи. Я дуже бурхливо виділяв рядки ще в 2010 році. Який визначальний тренд. Лол. Ти прав, хоча. Це брудно!
Andiih

3
@Krythic Це не 1980-ті роки, на вашому комп'ютері достатньо оперативної пам’яті, щоб зберегти рядок у 10 символів у пам’яті протягом однієї операції.
MrLore

2
@MrLore У простих програмах це може бути правдою, але в світі розвитку ігор це зовсім інший звір.
Критичний

48

Рішення

Будь-який із наведених нижче способів розширення зробить цю роботу. Усі вони розглядають знак мінус як цифру і працюють правильно для всіх можливих вхідних значень. Вони також працюють для .NET Framework і для .NET Core. Однак існують відповідні відмінності в продуктивності (обговорюються нижче), залежно від вибору платформи / рамки.

Версія 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;
}

Версія 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;
}

Обговорення

Ця відповідь включає тести, виконані для обох Int32і Int64типів, використовуючи масив 100.000.000випадкових вибірок int/ longчисел. Випадковий набір даних попередньо обробляється в масив перед виконанням тестів.

Послідовність випробування серед 4 -х різних методів були також виконані, для MinValue, негативних випадків прикордонних -1, 0, 1, позитивні прикордонні випадки, MaxValue, а також для всіх випадкового набору даних. Немає тестів на узгодженість для вищезазначених методів, ВКЛЮЧИТЬ для методу LOG10 (це обговорюється пізніше).

Випробування виконувались на .NET Framework 4.7.2та .NET Core 2.2; для x86та x64платформ, на 64-розрядній машині Intel Processor, з Windows 10та зVS2017 v.15.9.17 . Наступні 4 випадки мають однаковий вплив на результати діяльності:

.NET Framework (x86)

  • Platform = x86

  • Platform = AnyCPU, Prefer 32-bitперевіряється в налаштуваннях проекту

.NET Framework (x64)

  • Platform = x64

  • Platform = AnyCPU, Prefer 32-bitне встановлено прапорець у налаштуваннях проекту

.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

Результати

Нижче наведені тести на працездатність дають рівномірний розподіл значень серед широкого діапазону значень, яке може вважати ціле число. Це означає, що набагато більший шанс тестування значень з великою кількістю цифр. У сценаріях реального життя більшість значень може бути невеликим, тому IF-CHAIN ​​повинен працювати ще краще. Крім того, процесор буде кешувати та оптимізувати рішення IF-CHAIN ​​відповідно до вашого набору даних.

Як зазначав @AlanSingfield у розділі коментарів, метод LOG10 повинен був бути зафіксований за допомогою кастингу doubleвсередину Math.Abs()для випадку, коли значенням введення є int.MinValueабоlong.MinValue .

Що стосується ранніх тестів на ефективність, які я застосував до редагування цього питання (його потрібно було відредагувати мільйон разів), то був відомий конкретний випадок @ GyörgyKőszeg , в якому метод IF-CHAIN ​​працює повільніше, ніж метод LOG10.

Це все ж відбувається, хоча різниця стала набагато нижчою після виправлення проблеми, на яке вказував @AlanSingfield . Це виправлення (додавання амплітуди до double) викликає помилку при обчисленні, коли значення введення точно таке -999999999999999999: метод LOG10 повертається 20замість 19. Метод LOG10 також повинен мати aif захист для випадку, коли вхідне значення дорівнює нулю.

Метод LOG10 досить складний, щоб працювати над усіма значеннями, а значить, слід уникати цього. Якщо хтось знайде спосіб змусити його правильно працювати для всіх тестів на узгодженість нижче, будь ласка, опублікуйте коментар!

Метод WHILE також отримав нещодавню оновлену версію, яка є швидшою, але вона все ще повільна Platform = x86 (я не могла знайти причину, чому до цього часу).

Метод STRING послідовно повільний: він жадібно виділяє занадто багато пам'яті ні на що. Цікаво, що в .NET Core розподіл рядків здається набагато швидшим, ніж у .NET Framework. Добре знати.

Метод IF-CHAIN ​​повинен перевершити всі інші методи в 99,99% випадків; і, на мою особисту думку, це ваш найкращий вибір (враховуючи всі коригування, необхідні для правильної роботи методу LOG10, та погану ефективність двох інших методів).

Нарешті, результати:

введіть тут опис зображення

Оскільки ці результати залежать від апаратного забезпечення, я рекомендую все-таки запустити нижче тести працездатності на власному комп’ютері, якщо вам потрібно бути 100% впевненим у вашому конкретному випадку.

Код тесту

Нижче наведено код тесту на працездатність і тест на узгодженість. Той самий код використовується і для .NET Framework, і для .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
Мені подобається це рішення, воно набагато читабельніше, ніж математичні трюки, і швидкість говорить сама за себе, кудо.
MrLore

3
Чому це не позначено як рішення? Продуктивність має значення, і це здається найбільш обширною відповіддю.
Мартьєн де Йонг

Цікаво, я отримую різні результати . Для випадкових значень Log10 і груба сила майже однакові, але для long.MaxValueLog10 значно кращі. Або це просто в .NET Core?
György Kőszeg

@ GyörgyKőszeg: Я додав тести для Int64. Будь ласка, майте на увазі, що тести Int32та Int64генерують різні набори даних, що може пояснити, чому Int64стало швидше, ніж Int32у деяких випадках. Хоча в межах Int32тесту та в межах Int64тесту набори даних не змінюються при тестуванні різних методів обчислення. Що стосується .NET Core, я сумніваюся, що в бібліотеці Math є якась магічна оптимізація, яка змінила б ці результати, але я хотів би почути більше про це (моя відповідь вже величезна, напевно, одна з найбільших в SO ;-)
sɐunıɔ ןɐ qɐp

@ GyörgyKőszeg: Також показники низької продуктивності дуже складні. Я звичайно віддаю перевагу тримати код якомога більше простим (я віддаю перевагу прості forпетлі через enumerations, я попередньо обробляти випадкові набори даних, а також уникати використання дженериків, завдання, Function<>, Action<>або будь-який чорної коробочки рамках вимірювання). Підсумовуючи це, нехай це буде просто. Я також вбиваю всі непотрібні програми (Skype, Windows Defender, відключаю Антивірус, Chrome, кеш Microsoft Office тощо).
sɐunıɔ ןɐ qɐp

13

Не безпосередньо C #, але формула така: n = floor(log10(x)+1)


2
log10 (0) є -інфінітін
Алекс Клаус

2
@Klaus - log10 (0) насправді не визначений. Але ви правильні в тому, що це особливий випадок, який потрібно перевірити і лікувати окремо. Це справедливо і для будь-якого неподатного цілого числа. Дивіться коментарі до відповіді Стіва.
ysap

@ysap: Log10 досить складний, щоб правильно працювати. Чи маєте ви якесь уявлення про те, як правильно його реалізувати для всього діапазону можливих вхідних значень?
sɐunıɔ ןɐ qɐp

@ sɐunıɔ ןɐ qɐp - log10це в більшості випадків функція бібліотеки. Чому б ви хотіли це реалізувати самостійно, і з якими питаннями ви стикаєтесь? log10(x) = log2(x) / log2(10)або взагалі logA(x) = logB(x) / logB(A).
ysap

Я не хотів знову реалізовувати Log10, я маю на увазі Log10(0)- нескінченність. Log10 не можна використовувати для обчислення кількості цифр від’ємних чисел, якщо ви не використовуєте Math.Abs()перед передачею значення в Log10. Але потім Math.Abs(int.MinValue)кидає виняток ( long.MinValueтеж у випадку Int64). Якщо ми передамо число вдвічі, перш ніж передати його в Log10, воно працює майже для всіх чисел, крім -999999999999999999(у випадку Int64). Чи знаєте ви будь-яку формулу для обчислення кількості цифр, яка використовує log10 і приймає будь-яке значення int32 або int64 як вхідні та виводить лише дійсні значення?
sɐunıɔ ןɐ qɐp

9

Відповіді, які вже тут, працюють для непідписаних цілих чисел, але я не знайшов хороших рішень для отримання кількості цифр від десяткових знаків і подвійних.

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

Ви можете змінити тип введення з doubleна, decimalякщо точність має значення, але десяткові також мають обмеження.


7

Відповідь Стіва правильна , але вона не працює для цілих чисел менше 1.

Ось оновлена ​​версія, яка працює для негативів:

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

Ви пропускаєте кастинг int:digits = n == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(n)) + 1);
sɐunıɔ ןɐ qɐp

Я зробив це без, якщо заява: digits = (int) Math.Floor (Math.Abs ​​(Math.Log10 (Math.Abs ​​(n)))) + 1)
KOLRH

Це кидає виняток, коли n = int.MinValue.
sɐunıɔ ןɐ qɐp

5

Використання рекурсії (іноді просять на інтерв'ю)

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
Це кидає виняток, коли number = int.MinValue.
sɐunıɔ ןɐ qɐp

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

2
Остерігайтеся негативів: -1= 2
MrLore

2

Ось реалізація за допомогою двійкового пошуку. Схоже, найшвидший на int32.

Реалізація Int64 залишається як вправа для читача (!)

Я намагався використовувати Array.BinarySearch, а не жорстке кодування дерева, але це було приблизно в половині швидкості.

EDIT: Таблиця пошуку набагато швидша, ніж двійковий пошук, за рахунок використання більшої кількості пам'яті. Реально я б, ймовірно, використовував двійковий пошук у виробництві, таблиця пошуку - це багато складності для збільшення швидкості, яка, ймовірно, буде затьмарена іншими частинами програмного забезпечення.

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

Версія таблиці пошуку:

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

Версія двійкового пошуку

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

Дуже цікавий підхід. Це дійсно швидше, ніж "Log10", "string.Length" і "while" для рівномірно розподілених цілих значень. У реальних сценаріях випадку розподіл цілих значень завжди повинен враховуватися на рішеннях, подібних до ланцюга. +1
sɐunıɔ ןɐ qɐp

Підхід LookUpTable, здається, дуже швидкий для сценаріїв, коли доступ до пам'яті не є вузьким місцем. Я твердо переконаний, що для сценаріїв, що мають часто доступ до пам'яті, LookUpTable стає повільнішим, ніж методи, подібні до ланцюга, як і той, який ви запропонували. До речі, у вас є Int64реалізація для LookUpTable? Або ви вважаєте, що це занадто складно реалізувати? Пізніше я хотів би запустити тести на продуктивність у комплекті.
sɐunıɔ ןɐ qɐp

Гей, не дійшло до 64-бітного. Принцип повинен був дещо відрізнятися тим, що вам знадобиться 4-х рівнів, а не просто hiword та loword. Ви точно погоджуєтесь, що в реальному світі ваш кеш процесора матиме багато інших конкуруючих потреб у просторі, і є багато місця для вдосконалення в зменшенні розміру пошуку (>> 1, то навіть парні цифри приходять в голову) . Бінарний пошук можна покращити шляхом зміщення до 9,10,8 цифр замість 1,2,3,4 - враховуючи розподіл вашого випадкового набору даних.
Алан Сінгфілд

1

Якщо ділити число на 10, ви отримаєте найбільшу ліву цифру, а потім виконавши мод 10 на число, ви отримаєте число без першої цифри і повторіть, поки у вас не будуть всі цифри


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

2
не для негативного int та для таких чисел, як 23.00. Зробіть string.TrimStart('-')краще
nawfal

0

Створіть метод, який повертає всі цифри, та інший, який їх рахує:

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

Мені здавалося, що мені подобається більш інтуїтивно зрозумілий підхід при вирішенні цієї проблеми. Я спробувавLog10Спочатку метод через його очевидну простоту, але він має шалену кількість кутових справ і проблем з точністю.

Я також знайшов своє if ланцюжок, запропонований в іншій відповіді, трохи некрасивий, щоб подивитися.

Я знаю, що це не найефективніший метод, але він дає вам інше розширення для повернення цифр, а також для інших цілей (ви можете просто позначити його, privateякщо вам не потрібно використовувати його поза класом).

Майте на увазі, що він не розглядає негативний знак як цифру.


-2

перетворити в рядок, і тоді ви можете порахувати татал без цифри методом .length. Подібно до:

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

1
Виділяти рядок зовсім непотрібно.
Krythic

-2

Це залежить від того, що саме ви хочете робити з цифрами. Ви можете повторити цифри, починаючи з останньої до першої, як ця:

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

1
Ваша логіка зворотна. Вам потрібно скористатися, %щоб отримати цифру, а потім /=її скоротити.
julealgon


-3

Якщо припустити, що ваше запитання стосувалося int, такі результати також працюють як за мінусом, так і за нулем:

Math.Floor((decimal) Math.Abs(n)).ToString().Length
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.