Как я могу подсчитать общее количество цифр в номере?


114

Как я могу подсчитать общее количество цифр числа в C #? Например, номер 887979789 состоит из 9 цифр.


6
попробуйте использовать .Length, если он не работает, сначала преобразуйте его в строку
Breezer

Допустим, x = 887979789; x.ToString (). Счетчик (); даст вам это.
nPcomp

Ответы:


176

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

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; делать {считать ++; } while ((i / = 10)> = 1); :(
Puterdo Borato

3
Если ваш диапазон чисел включает отрицательные числа, вам нужно использовать Math.Floor (Math.Log10 (Math.Abs ​​(n)) + 1);
mrcrowl

1
Хорошо, если nэто 0можно просто вернуть 1:) Слишком обрабатывайте отрицательные значения, просто замените nна Math.Abs(n).
Umair

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

83

Попробуй это:

myint.ToString().Length

Это работает ?


25
Стоит отметить, что вы, вероятно, столкнетесь с проблемами при использовании этого метода, если имеете дело с отрицательными числами. (И, очевидно, десятичные дроби, но в примере используется int, так что я предполагаю, что это не проблема.)
Коди Грей

2
Распределение строк @Krythic - это новое увлечение в мире .NET.
nawfal

1
новый? Едва. Я вопиюще распределял строки еще в 2010 году. Какой законодатель моды. Ржунимагу. Но ты прав. Это грязно!
Andiih

3
@Krythic Это не 1980-е, у вашего компьютера достаточно оперативной памяти, чтобы сохранить в памяти строку из 10 символов на время одной операции.
MrLore

2
@MrLore В простых приложениях это может быть правдой, но в мире разработки игр это совсем другой зверь.
Krythic

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, с 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ögeryKőszeg , в котором метод IF-CHAIN ​​работает медленнее, чем метод LOG10.

Это все еще происходит, хотя величина разницы стала намного меньше после исправления проблемы, указанной @AlanSingfield . Это исправление (добавление приведения к double) вызывает ошибку вычислений, если входное значение точно -999999999999999999: метод LOG10 возвращает 20вместо 19. Метод LOG10 также должен иметь защиту ifна случай, когда входное значение равно нулю.

Метод 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ögeryKőszeg: Я добавил тесты для Int64. Имейте в виду, что тесты Int32и Int64генерируют разные наборы данных, что может объяснить, почему они Int64работали быстрее, чем Int32в некоторых случаях. Хотя внутри Int32теста и внутри Int64теста наборы данных не меняются при тестировании различных методов расчета. Что касается .NET Core, я сомневаюсь, что в библиотеке Math есть какая-то волшебная оптимизация, которая изменила бы эти результаты, но я хотел бы услышать об этом больше (мой ответ уже огромен, возможно, один из самых больших в SO ;-)
sɐunıɔ ןɐ qɐp

@ ДьердьКёсег: Кроме того, измерение производительности на низком уровне очень сложно. Я обычно предпочитаю держать код как можно более простым (я предпочитаю простые forпетли через enumerations, я предварительно обрабатывать случайные наборы данных, а также избегать использования дженериков, задачи, Function<>, Action<>или любой черной коробочный рамках измерения). Таким образом, будьте проще. Также убиваю все ненужные приложения (Skype, Защитник Windows, отключаю Антивирус, Chrome, кеш Microsoft Office и т. Д.).
sɐunıɔ ןɐ qɐp

13

Не прямо C #, но формула: n = floor(log10(x)+1)


2
log10 (0) is -infinity
Alex Klaus

2
@Klaus - log10 (0) на самом деле не определено. Но вы правы в том, что это особый случай, который нужно исследовать и рассматривать отдельно. Это также верно для любого целого неположительного числа. См. Комментарии к ответу Стива.
ysap 03

@ysap: Log10 довольно сложно заставить работать правильно. У вас есть идеи, как правильно реализовать это для всего диапазона возможных входных значений?
sɐunıɔ ןɐ qɐp 06

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

Я не хотел снова внедрять Log10, я имею в виду Log10(0)-infinity. 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)

Вам не хватает castingto int:digits = n == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(n)) + 1);
sɐunıɔ ןɐ qɐp

Я сделал это без оператора if: digits = (int) Math.Floor (Math.Abs ​​(Math.Log10 (Math.Abs ​​(n))) + 1)
KOLRH 02

Это вызывает исключение, когда 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 вместо жесткого кодирования дерева, но это было примерно вдвое меньше.

РЕДАКТИРОВАТЬ: таблица поиска намного быстрее, чем двоичный поиск, за счет использования большего объема памяти. На самом деле я бы, вероятно, использовал двоичный поиск в производстве, таблица поиска представляет собой большую сложность для увеличения скорости, которую, вероятно, затмевают другие части программного обеспечения.

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» для равномерно распределенных целочисленных значений. В реальных сценариях распределение целочисленных значений всегда должно рассматриваться в решениях, подобных цепочке if. +1
sɐunıɔ ןɐ qɐp 07

Подход LookUpTable кажется очень быстрым для сценариев, в которых доступ к памяти не является узким местом. Я твердо верю, что для сценариев с частым доступом к памяти LookUpTable работает медленнее, чем методы, подобные цепочке if, например, предложенный вами BinSearch. Кстати, а у вас есть Int64реализация LookUpTable? Или вы думаете, что это слишком сложно реализовать? Попозже хочу провести тесты производительности по комплектации.
sɐunıɔ ןɐ qɐp

Эй, до 64-битной не дошел. Принцип должен быть немного другим, так как вам понадобятся 4x уровня, а не только 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.