Como posso obter uma contagem do número total de dígitos em um número?


114

Como posso obter uma contagem do número total de dígitos de um número em C #? Por exemplo, o número 887979789 tem 9 dígitos.


6
tente usar .Length se não funcionar, converta-o primeiro para uma string
Breezer

Digamos que x = 887979789; x.ToString (). Count (); vai te dar isso.
nPcomp

Respostas:


175

Sem converter para uma string, você pode tentar:

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

Correção após o comentário de ysap:

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

10
Receio que ceil (log10 (10)) = ceil (1) = 1, e não 2 como deveria ser para esta questão!
ysap

3
Obrigado, é um bom método. Embora não seja mais rápido do que int count = 0; faça {contagem ++; } enquanto ((i / = 10)> = 1); :(
Puterdo Borato

3
Se o seu intervalo numérico incluir negativos, você precisará usar Math.Floor (Math.Log10 (Math.Abs ​​(n)) + 1);
mrcrowl

1
Bem, se né 0pode simplesmente voltar 1:) valores negativos trabalhar também apenas substituir ncom Math.Abs(n).
Umair

3
@Puterdo Borato: meu teste de desempenho realmente mostrou que seu método é mais rápido quando o número de dígitos é <5. Passe isso, o Math.floor de Steve é ​​mais rápido.
stack247

83

Experimente isto:

myint.ToString().Length

Isso funciona ?


25
É importante ressaltar que você provavelmente terá problemas com esse método se estiver lidando com números negativos. (E, obviamente, decimais, mas o exemplo usa um int, então presumo que isso não seja um problema.)
Cody Gray

2
A alocação de strings @Krythic é a nova mania no mundo .NET.
nawfal

1
Novo? Dificilmente. Eu estava alocando cordas notoriamente em 2010. Que criador de tendências. Ri muito. Você está certo. Isso é sujo!
Andiih

3
@Krythic Não é a década de 1980, seu computador tem RAM suficiente para salvar uma seqüência de 10 caracteres na memória durante uma operação.
MrLore

2
@MrLore Em aplicativos simples, isso pode ser verdade, mas no mundo do desenvolvimento de jogos, é uma besta totalmente diferente.
Krythic

48

A solução

Qualquer um dos seguintes métodos de extensão fará o trabalho. Todos eles consideram o sinal de menos como um dígito e funcionam corretamente para todos os valores de entrada possíveis. Eles também funcionam para .NET Framework e .NET Core. No entanto, existem diferenças de desempenho relevantes (discutidas abaixo), dependendo da sua escolha de Plataforma / Estrutura.

Versão 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;
}

Versão 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;
}

Discussão

Essa resposta inclui testes executados para os tipos Int32e Int64, usando uma matriz de números / 100.000.000amostras aleatoriamente . O conjunto de dados aleatório é pré-processado em uma matriz antes de executar os testes.intlong

Testes de consistência entre os 4 métodos diferentes também foram executados, para MinValue, casos de fronteira negativos, -1, 0, 1, casos de fronteira positivos, MaxValuee também para todo o conjunto de dados aleatório. Nenhum teste de consistência falha para os métodos fornecidos acima, EXCETO para o método LOG10 (isso será discutido mais tarde).

Os testes foram executados em .NET Framework 4.7.2e .NET Core 2.2; para plataformas x86e x64, em uma máquina com processador Intel de 64 bits, com Windows 10e com VS2017 v.15.9.17. Os 4 casos a seguir têm o mesmo efeito nos resultados de desempenho:

.NET Framework (x86)

  • Platform = x86

  • Platform = AnyCPU, Prefer 32-bité verificado nas configurações do projeto

.NET Framework (x64)

  • Platform = x64

  • Platform = AnyCPU, Prefer 32-bitestá desmarcado nas configurações do projeto

.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

Resultados

Os testes de desempenho abaixo produzem uma distribuição uniforme de valores entre a ampla gama de valores que um inteiro pode assumir. Isso significa que há uma chance muito maior de valores de teste com uma grande contagem de dígitos. Em cenários da vida real, a maioria dos valores pode ser pequena, então o IF-CHAIN ​​deve ter um desempenho ainda melhor. Além disso, o processador armazenará em cache e otimizará as decisões IF-CHAIN ​​de acordo com seu conjunto de dados.

Como @AlanSingfield apontou na seção de comentários, o método LOG10 teve que ser corrigido com uma conversão para doubledentro Math.Abs()para o caso em que o valor de entrada é int.MinValueou long.MinValue.

Em relação aos primeiros testes de desempenho que implementei antes de editar esta questão (ela já tinha que ser editada um milhão de vezes), houve um caso específico apontado por @ GyörgyKőszeg , no qual o método IF-CHAIN ​​tem desempenho mais lento do que o método LOG10.

Isso ainda acontece, embora a magnitude da diferença tenha se tornado muito menor após a correção do problema apontado por @AlanSingfield . Esta correção (adicionar uma conversão a double) causa um erro de cálculo quando o valor de entrada é exatamente -999999999999999999: o método LOG10 retorna em 20vez de 19. O método LOG10 também deve ter uma ifproteção para o caso em que o valor de entrada é zero.

O método LOG10 é bastante complicado para funcionar para todos os valores, o que significa que você deve evitá-lo. Se alguém encontrar uma maneira de fazer funcionar corretamente para todos os testes de consistência abaixo, poste um comentário!

O método WHILE também ganhou uma versão refatorada recente que é mais rápida, mas ainda é lenta para Platform = x86(não consegui descobrir o motivo, até agora).

O método STRING é consistentemente lento: ele aloca avidamente muita memória para nada. Curiosamente, no .NET Core, a alocação de strings parece ser muito mais rápida do que no .NET Framework. Bom saber.

O método IF-CHAIN ​​deve superar todos os outros métodos em 99,99% dos casos; e, na minha opinião pessoal, é a sua melhor escolha (considerando todos os ajustes necessários para que o método LOG10 funcione corretamente e o mau desempenho dos outros dois métodos).

Finalmente, os resultados são:

insira a descrição da imagem aqui

Como esses resultados dependem do hardware, recomendo mesmo assim executar os testes de desempenho abaixo em seu próprio computador se você realmente precisar ter 100% de certeza em seu caso específico.

Código de Teste

Abaixo está o código para o teste de desempenho e também o teste de consistência. O mesmo código é usado para .NET Framework e .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
Gosto desta solução, é muito mais legível do que truques matemáticos e a velocidade fala por si, parabéns.
MrLore

3
Por que isso não está marcado como a solução? O desempenho é importante e esta parece ser a resposta mais ampla.
Martien de Jong

Interessante, obtenho resultados diferentes . Para valores aleatórios, Log10 e força bruta são quase os mesmos, mas para long.MaxValueLog10 é significativamente melhor. Ou está apenas no .NET Core?
György Kőszeg

@ GyörgyKőszeg: Eu adicionei testes para Int64. Esteja ciente de que os testes para Int32e Int64geram conjuntos de dados diferentes, o que pode explicar por que Int64ficou mais rápido do que Int32em alguns casos. Embora, dentro do Int32teste e dentro do Int64teste, os conjuntos de dados não sejam alterados ao testar os diferentes métodos de cálculo. Agora, em relação ao .NET Core, duvido que haja alguma otimização mágica na biblioteca de matemática que mudaria esses resultados, mas adoraria ouvir mais sobre isso (minha resposta já é enorme, provavelmente uma das maiores em SO ;-)
sɐunıɔ ןɐ qɐp

@ GyörgyKőszeg: Além disso, os instrumentos de desempenho de baixo nível são muito complicados. I geralmente preferem manter o código o mais simples possível (eu prefiro simples forlaços mais enumerations, eu pré-processar conjuntos de dados aleatórios, e evitar o uso de genéricos, Tarefas Function<>, Action<>ou qualquer estrutura de medição black-box). Em resumo, mantenha as coisas simples. Eu também mato todos os aplicativos desnecessários (Skype, Windows Defender, desabilito o antivírus, Chrome, cache do Microsoft Office, etc).
sɐunıɔ ןɐ qɐp

13

Não diretamente C #, mas a fórmula é: n = floor(log10(x)+1)


2
log10 (0) é -infinity
Alex Klaus

2
@Klaus - log10 (0) é realmente indefinido. Mas, você está correto ao dizer que é um caso especial que precisa ser testado e tratado separadamente. Isso também é verdadeiro para qualquer número inteiro não positivo. Veja os comentários à resposta de Steve.
ysap

@ysap: Log10 é bastante complicado para funcionar corretamente. Você tem alguma ideia de como implementá-lo corretamente para toda a gama de valores de entrada possíveis?
sɐunıɔ ןɐ qɐp

@ sɐunıɔ ןɐ qɐp - log10é na maioria dos casos uma função de biblioteca. Por que você deseja implementá-lo sozinho e quais problemas você encontra? log10(x) = log2(x) / log2(10), ou geralmente logA(x) = logB(x) / logB(A).
ysap

Eu não tive a intenção de implementar o Log10 novamente, quero dizer, Log10(0)é -infinity. Log10 não pode ser usado para calcular o número de dígitos de números negativos, a menos que você use Math.Abs()antes de passar o valor para Log10. Mas então Math.Abs(int.MinValue)lança uma exceção ( long.MinValuetambém, no caso de Int64). Se lançarmos o número para o dobro antes de passá-lo para Log10, ele funcionará para quase todos os números, exceto para -999999999999999999(no caso de Int64). Você conhece alguma fórmula para calcular o número de dígitos que usa log10 e aceita qualquer valor int32 ou int64 como entrada e produz apenas valores válidos?
sɐunıɔ ןɐ qɐp

9

As respostas já aqui funcionam para inteiros sem sinal, mas não encontrei boas soluções para obter o número de dígitos de decimais e duplos.

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

Você pode alterar o tipo de entrada de doublepara decimalse a precisão for importante, mas decimal também tem um limite.


7

A resposta de Steve está correta , mas não funciona para inteiros menores que 1.

Aqui está uma versão atualizada que funciona para negativos:

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

Está faltando um casting para int:digits = n == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(n)) + 1);
sɐunıɔ ןɐ qɐp

Fiz isso sem a instrução if: digits = (int) Math.Floor (Math.Abs ​​(Math.Log10 (Math.Abs ​​(n))) + 1)
KOLRH

Isso lança uma exceção quando n = int.MinValue.
sɐunıɔ ןɐ qɐp

5

Usando recursão (às vezes perguntado em entrevistas)

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
Isso lança uma exceção quando number = int.MinValue.
sɐunıɔ ןɐ qɐp

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

2
Cuidado com os negativos: -1= 2
MrLore

2

Aqui está uma implementação usando uma pesquisa binária. Parece ser o mais rápido até agora na int32.

A implementação de Int64 é deixada como um exercício para o leitor (!)

Tentei usar Array.BinarySearch em vez de codificar a árvore, mas isso foi cerca da metade da velocidade.

EDIT: Uma tabela de pesquisa é muito mais rápida do que a pesquisa binária, ao custo de usar mais memória. Realisticamente, eu provavelmente usaria a pesquisa binária na produção, a tabela de pesquisa é muito complexa para um ganho de velocidade que provavelmente será ofuscado por outras partes do software.

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

Versão da tabela de pesquisa:

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

Versão de pesquisa binária

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

Abordagem muito interessante. É realmente mais rápido que os métodos "Log10", "string.Length" e "While" para valores inteiros uniformemente distribuídos. Em cenários de caso real, a distribuição dos valores inteiros deve sempre ser considerada em soluções do tipo cadeia if. +1
sɐunıɔ ןɐ qɐp

A abordagem LookUpTable parece ser super rápida para cenários em que o acesso à memória não é o gargalo. Eu acredito fortemente que para cenários com acesso frequente à memória, o LookUpTable fica mais lento do que os métodos do tipo if-chain, como o BinSearch que você sugeriu. A propósito, você tem a Int64implementação para LookUpTable? Ou você acha que é muito complicado de implementar? Eu gostaria de executar os testes de desempenho mais tarde no conjunto completo.
sɐunıɔ ןɐ qɐp

Ei, não fui tão longe quanto o de 64 bits. O princípio teria que ser um pouco diferente, pois você precisaria de níveis 4x em vez de apenas hiword e loword. Definitivamente, concordo que, no mundo real, seu cache de CPU terá muitas outras necessidades concorrentes para o espaço e há muito espaço para melhorias na redução do tamanho da pesquisa (>> 1, então os números pares só vêm à mente) . A busca binária poderia ser melhorada enviando para 9,10,8 dígitos em vez de 1,2,3,4 - dada a distribuição de seu conjunto de dados aleatórios.
Alan Singfield

1

dividir um número por 10 lhe dará o dígito mais à esquerda; em seguida, fazer um mod 10 no número dá o número sem o primeiro dígito e repete até ter todos os dígitos


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

2
falha para int negativo e para números como 23,00. Faça um string.TrimStart('-')melhor
nawfal

0

Crie um método que retorne todos os dígitos e outro que os conte:

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

Essa parecia a abordagem mais intuitiva para mim ao lidar com esse problema. Eu tentei oLog10 método primeiro devido à sua aparente simplicidade, mas ele tem uma quantidade absurda de casos de canto e problemas de precisão.

Eu também encontrei o if cadeia proposta na outra resposta para um pouco feia de se olhar.

Eu sei que este não é o método mais eficiente, mas fornece a outra extensão para retornar os dígitos também para outros usos (você pode apenas marcá-lo privatese não precisar usá-lo fora da classe).

Lembre-se de que ele não considera o sinal negativo como um dígito.


-2

converter em string e então você pode contar o número tatal de dígitos pelo método .length. Gostar:

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

1
Alocar uma string é completamente desnecessário.
Krythic

-2

Depende do que exatamente você deseja fazer com os dígitos. Você pode iterar através dos dígitos, começando do último para o primeiro, assim:

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

1
Sua lógica está invertida. Você precisa usar %para obter o dígito e, em seguida, /=reduzi-lo.
julealgon


-3

Supondo que sua pergunta se referisse a um int, o seguinte funciona para negativo / positivo e zero também:

Math.Floor((decimal) Math.Abs(n)).ToString().Length
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.