¡Me temo que ceil (log10 (10)) = ceil (1) = 1, y no 2 como debería ser para esta pregunta!
ysap
3
Gracias, es un buen método. Aunque no es más rápido que int count = 0; hacer {contar ++; } mientras ((i / = 10)> = 1); :(
Puterdo Borato
3
Si su rango de números incluye negativos, deberá usar Math.Floor (Math.Log10 (Math.Abs (n)) + 1);
mrcrowl
1
Bueno, si nse 0puede devolver 1:) Para manejar valores negativos, simplemente reemplácelos ncon Math.Abs(n).
Umair
3
@Puterdo Borato: mi prueba de rendimiento mostró que tu método es más rápido cuando el número de dígitos es <5. Pasa eso, Steve's Math.floor es más rápido.
Vale la pena señalar que es probable que tenga problemas con este método si se trata de números negativos. (Y obviamente decimales, pero el ejemplo usa un int, así que supongo que eso no es un problema).
Cody Gray
2
La asignación de cadenas @Krythic es la nueva moda en el mundo .NET.
nawfal
1
¿nuevo? Apenas. Estuve asignando cuerdas de manera atroz en 2010. Qué marca de tendencias. Jajaja Aunque tienes razón. ¡Esto está sucio!
Andiih
3
@Krythic No es la década de 1980, su computadora tiene suficiente RAM para guardar una cadena de 10 caracteres en la memoria durante una operación.
MrLore
2
@MrLore En aplicaciones simples, esto puede ser cierto, pero en el mundo del desarrollo de juegos, es una bestia completamente diferente.
Krythic
48
La solución
Cualquiera de los siguientes métodos de extensión funcionará. Todos ellos consideran el signo menos como un dígito y funcionan correctamente para todos los valores de entrada posibles. También funcionan para .NET Framework y .NET Core. Sin embargo, existen diferencias de rendimiento relevantes (que se analizan a continuación), según su elección de plataforma / marco.
Esta respuesta incluye pruebas realizadas para ambos tipos Int32y Int64, utilizando una matriz de números / 100.000.000muestreados aleatoriamente . El conjunto de datos aleatorio se procesa previamente en una matriz antes de ejecutar las pruebas.intlong
También se ejecutaron pruebas de coherencia entre los 4 métodos diferentes, por MinValue, casos fronterizos negativos, -1, 0, 1, casos fronterizos positivas, MaxValuey también para todo el conjunto de datos al azar. Ninguna prueba de consistencia falla para los métodos proporcionados anteriormente, EXCEPTO para el método LOG10 (esto se analiza más adelante).
Las pruebas se ejecutaron en .NET Framework 4.7.2y .NET Core 2.2; para plataformas x86y x64, en una máquina con procesador Intel de 64 bits, con Windows 10y con VS2017 v.15.9.17. Los siguientes 4 casos tienen el mismo efecto en los resultados de rendimiento:
.NET Framework (x86)
Platform = x86
Platform = AnyCPU, Prefer 32-bitestá marcado en la configuración del proyecto
.NET Framework (x64)
Platform = x64
Platform = AnyCPU, Prefer 32-bitestá desmarcado en la configuración del proyecto
Las pruebas de rendimiento siguientes producen una distribución uniforme de valores entre la amplia gama de valores que podría asumir un número entero. Esto significa que existe una probabilidad mucho mayor de probar valores con una gran cantidad de dígitos. En escenarios de la vida real, la mayoría de los valores pueden ser pequeños, por lo que IF-CHAIN debería funcionar aún mejor. Además, el procesador almacenará en caché y optimizará las decisiones de IF-CHAIN de acuerdo con su conjunto de datos.
Como @AlanSingfield señaló en la sección de comentarios, el método LOG10 tuvo que arreglarse con una conversión hacia doubleadentro Math.Abs()para el caso en que el valor de entrada es int.MinValueo long.MinValue.
Con respecto a las primeras pruebas de rendimiento que implementé antes de editar esta pregunta (ya tenía que ser editado un millón de veces), hubo un caso específico señalado por @ GyörgyKőszeg , en el que el método IF-CHAIN funciona más lento que el método LOG10.
Esto todavía sucede, aunque la magnitud de la diferencia se redujo mucho después de la solución del problema señalado por @AlanSingfield . Esta corrección (agregar una conversión a double) provoca un error de cálculo cuando el valor de entrada es exactamente -999999999999999999: el método LOG10 devuelve en 20lugar de 19. El método LOG10 también debe tener una ifprotección para el caso en que el valor de entrada sea cero.
El método LOG10 es bastante complicado para trabajar con todos los valores, lo que significa que debe evitarlo. Si alguien encuentra una manera de hacer que funcione correctamente para todas las pruebas de coherencia a continuación, ¡publique un comentario!
El método WHILE también obtuvo una versión refactorizada reciente que es más rápida, pero aún es lenta Platform = x86(no pude encontrar la razón por la cual, hasta ahora).
El método STRING es consistentemente lento: asigna con avidez demasiada memoria por nada. Curiosamente, en .NET Core, la asignación de cadenas parece ser mucho más rápida que en .NET Framework. Bueno saber.
El método IF-CHAIN debería superar a todos los demás métodos en el 99,99% de los casos; y, en mi opinión personal, es su mejor opción (considerando todos los ajustes necesarios para que el método LOG10 funcione correctamente y el mal desempeño de los otros dos métodos).
Finalmente, los resultados son:
Dado que estos resultados dependen del hardware, recomiendo de todos modos ejecutar las pruebas de rendimiento a continuación en su propia computadora si realmente necesita estar 100% seguro en su caso específico.
Código de prueba
A continuación se muestra el código para la prueba de rendimiento y también la prueba de coherencia. El mismo código se usa para .NET Framework y .NET Core.
using System;
using System.Diagnostics;
namespace NumberOfDigits{// Performance Tests:classProgram{privatestaticvoidMain(string[] args){Console.WriteLine("\r\n.NET Core");RunTests_Int32();RunTests_Int64();}// Int32 Performance Tests:privatestaticvoidRunTests_Int32(){Console.WriteLine("\r\nInt32");constint size =100000000;int[] samples =newint[size];Random random =newRandom((int)DateTime.Now.Ticks);for(int i =0; i < size;++i)
samples[i]= random.Next(int.MinValue,int.MaxValue);Stopwatch sw1 =newStopwatch();
sw1.Start();for(int i =0; i < size;++i) samples[i].Digits_IfChain();
sw1.Stop();Console.WriteLine($"IfChain: {sw1.ElapsedMilliseconds} ms");Stopwatch sw2 =newStopwatch();
sw2.Start();for(int i =0; i < size;++i) samples[i].Digits_Log10();
sw2.Stop();Console.WriteLine($"Log10: {sw2.ElapsedMilliseconds} ms");Stopwatch sw3 =newStopwatch();
sw3.Start();for(int i =0; i < size;++i) samples[i].Digits_While();
sw3.Stop();Console.WriteLine($"While: {sw3.ElapsedMilliseconds} ms");Stopwatch sw4 =newStopwatch();
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 =newint[]{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:privatestaticvoidRunTests_Int64(){Console.WriteLine("\r\nInt64");constint size =100000000;long[] samples =newlong[size];Random random =newRandom((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 =newStopwatch();
sw1.Start();for(int i =0; i < size;++i) samples[i].Digits_IfChain();
sw1.Stop();Console.WriteLine($"IfChain: {sw1.ElapsedMilliseconds} ms");Stopwatch sw2 =newStopwatch();
sw2.Start();for(int i =0; i < size;++i) samples[i].Digits_Log10();
sw2.Stop();Console.WriteLine($"Log10: {sw2.ElapsedMilliseconds} ms");Stopwatch sw3 =newStopwatch();
sw3.Start();for(int i =0; i < size;++i) samples[i].Digits_While();
sw3.Stop();Console.WriteLine($"While: {sw3.ElapsedMilliseconds} ms");Stopwatch sw4 =newStopwatch();
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 =newlong[]{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");}}}
Me gusta esta solución, es mucho más legible que los trucos matemáticos y la velocidad habla por sí sola, felicitaciones.
MrLore
3
¿Por qué esto no está marcado como la solución? El rendimiento importa y esta parece ser la respuesta más extensa.
Martien de Jong
Interesante, obtengo resultados diferentes . Para valores aleatorios, Log10 y la fuerza bruta son casi iguales, pero para long.MaxValueLog10 es significativamente mejor. ¿O es solo en .NET Core?
György Kőszeg
@ GyörgyKőszeg: He agregado pruebas para Int64. Tenga en cuenta que las pruebas Int32y Int64generan diferentes conjuntos de datos, lo que puede explicar por qué Int64fue más rápido que Int32en algunos casos. Aunque, dentro de la Int32prueba y dentro de la Int64prueba, los conjuntos de datos no se modifican cuando se prueban los diferentes métodos de cálculo. Ahora, con respecto a .NET Core, dudo que haya alguna optimización mágica en la biblioteca de matemáticas que cambie estos resultados, pero me encantaría saber más sobre eso (mi respuesta ya es enorme, probablemente una de las más grandes en SO ;-)
sɐunıɔ ןɐ qɐp
@ GyörgyKőszeg: Además, las mediciones de rendimiento de bajo nivel son muy complicadas. Por lo general prefieren mantener el código tan simple como sea posible (yo prefiero simples forbucles sobre enumerations, I conjuntos de datos aleatorios pre-proceso, y evitar el uso de genéricos, Tareas Function<>, Action<>o cualquier marco de medición negro-caja). En resumen, manténgalo simple. También elimino todas las aplicaciones innecesarias (Skype, Windows Defender, deshabilito Anti-Virus, Chrome, caché de Microsoft Office, etc.).
sɐunıɔ ןɐ qɐp
13
No directamente en C #, pero la fórmula es: n = floor(log10(x)+1)
@Klaus - log10 (0) en realidad no está definido. Pero tiene razón en que se trata de un caso especial que debe analizarse y tratarse por separado. Esto también es cierto para cualquier número entero no positivo. Vea los comentarios a la respuesta de Steve.
ysap
@ysap: Log10 es bastante complicado para que funcione correctamente. ¿Tiene alguna idea de cómo implementarlo correctamente para todo el rango de posibles valores de entrada?
sɐunıɔ ןɐ qɐp
@ sɐunıɔ ןɐ qɐp - log10es en la mayoría de los casos una función de biblioteca. ¿Por qué querría implementarlo usted mismo y qué problemas encuentra? log10(x) = log2(x) / log2(10), o en general logA(x) = logB(x) / logB(A).
ysap
No quise implementar Log10 nuevamente, quiero decir Log10(0)es -infinito. Log10 no se puede usar para calcular el número de dígitos de números negativos a menos que lo use Math.Abs()antes de pasar el valor a Log10. Pero luego Math.Abs(int.MinValue)arroja una excepción ( long.MinValuetambién, en el caso de Int64). Si convertimos el número al doble antes de pasarlo a Log10, entonces funciona para casi todos los números excepto para -999999999999999999(en el caso de Int64). ¿Conoce alguna fórmula para calcular la cantidad de dígitos que usa log10 y acepta cualquier valor int32 o int64 como entrada y genera solo valores válidos?
sɐunıɔ ןɐ qɐp
9
Las respuestas aquí ya funcionan para enteros sin signo, pero no he encontrado buenas soluciones para obtener el número de dígitos de decimales y dobles.
publicstaticintLength(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
Puede cambiar el tipo de entrada de doublea decimalsi la precisión importa, pero el decimal también tiene un límite.
Aquí hay una implementación que utiliza una búsqueda binaria. Parece ser el más rápido hasta ahora en int32.
La implementación de Int64 se deja como ejercicio para el lector (!)
Intenté usar Array.BinarySearch en lugar de codificar el árbol, pero eso fue aproximadamente la mitad de la velocidad.
EDITAR: Una tabla de búsqueda es mucho más rápida que la búsqueda binaria, a expensas de usar más memoria. De manera realista, probablemente usaría la búsqueda binaria en producción, la tabla de búsqueda es muy compleja para una ganancia de velocidad que probablemente sea eclipsada por otras partes del software.
Lookup-Table:439 ms
Binary-Search:1069 ms
If-Chain:1409 ms
Log10:1145 ms
While:1768 ms
String:5153 ms
Versión de la tabla de búsqueda:
staticbyte[] _0000llll =newbyte[0x10000];staticbyte[]_FFFFllll=newbyte[0x10001];staticsbyte[] _hhhhXXXXdigits =newsbyte[0x10000];// Special cases where the high DWORD is not enough information to find out how// many digits.staticushort[] _lowordSplits =newushort[12];staticsbyte[] _lowordSplitDigitsLT =newsbyte[12];staticsbyte[] _lowordSplitDigitsGE =newsbyte[12];staticInt32Extensions(){// 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();}privatestaticvoid 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 negativesint 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 etcint 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++;}}}privatestaticvoid 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;}privatestaticvoid 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;}publicstaticintDigits_LookupTable(thisint 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];}elseif(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];elsereturn _lowordSplitDigitsGE[splitIndex];}
Enfoque muy interesante. De hecho, es más rápido que los métodos "Log10", "string.Length" y "While" para valores enteros distribuidos uniformemente. En escenarios de casos reales, la distribución de los valores enteros siempre se debe considerar en soluciones de tipo if-chain. +1
sɐunıɔ ןɐ qɐp
El enfoque LookUpTable parece ser súper rápido para escenarios donde el acceso a la memoria no es el cuello de botella. Creo firmemente que para escenarios con acceso frecuente a la memoria, LookUpTable se vuelve más lento que los métodos similares a if-chain, como el BinSearch que ha sugerido. Por cierto, ¿tiene la Int64implementación para LookUpTable? ¿O crees que es demasiado complicado implementarlo? Me gustaría ejecutar las pruebas de rendimiento más adelante en el conjunto completo.
sɐunıɔ ןɐ qɐp
Oye, no llegué tan lejos como el de 64 bits. El principio tendría que ser ligeramente diferente en el sentido de que necesitarías niveles 4x en lugar de solo hiword y loword. Definitivamente estoy de acuerdo en que, en el mundo real, la caché de su CPU tendrá muchas otras necesidades competitivas para el espacio, y hay mucho margen de mejora para reducir el tamaño de la búsqueda (>> 1, entonces los números pares solo me vienen a la mente) . La búsqueda binaria se podría mejorar al sesgar hacia 9,10,8 dígitos en lugar de 1,2,3,4, dada la distribución de su conjunto de datos aleatorios.
Alan Singfield
1
dividir un número por 10 le dará el dígito más a la izquierda, luego hacer un mod 10 en el número da el número sin el primer dígito y repetirlo hasta que tenga todos los dígitos
Este me pareció el enfoque más intuitivo al abordar este problema. Probé elLog10 método primero debido a su aparente simplicidad, pero tiene una cantidad increíble de casos de esquina y problemas de precisión.
También encontré el if cadena propuesta en la otra respuesta a un poco fea de ver.
Sé que este no es el método más eficiente, pero le brinda la otra extensión para devolver los dígitos también para otros usos (puede marcarlo privatesi no necesita usarlo fuera de la clase).
Tenga en cuenta que no considera el signo negativo como un dígito.
La asignación de una cadena es completamente innecesaria.
Krythic
-2
Depende de lo que quieras hacer exactamente con los dígitos. Puede iterar a través de los dígitos comenzando desde el último hasta el primero de esta manera:
Respuestas:
Sin convertir a una cadena, puede intentar:
Corrección siguiendo el comentario de ysap:
fuente
n
se0
puede devolver1
:) Para manejar valores negativos, simplemente reemplácelosn
conMath.Abs(n)
.Prueba esto:
Eso funciona ?
fuente
int
, así que supongo que eso no es un problema).La solución
Cualquiera de los siguientes métodos de extensión funcionará. Todos ellos consideran el signo menos como un dígito y funcionan correctamente para todos los valores de entrada posibles. También funcionan para .NET Framework y .NET Core. Sin embargo, existen diferencias de rendimiento relevantes (que se analizan a continuación), según su elección de plataforma / marco.
Versión Int32:
Versión Int64:
Discusión
Esta respuesta incluye pruebas realizadas para ambos tipos
Int32
yInt64
, utilizando una matriz de números /100.000.000
muestreados aleatoriamente . El conjunto de datos aleatorio se procesa previamente en una matriz antes de ejecutar las pruebas.int
long
También se ejecutaron pruebas de coherencia entre los 4 métodos diferentes, por
MinValue
, casos fronterizos negativos,-1
,0
,1
, casos fronterizos positivas,MaxValue
y también para todo el conjunto de datos al azar. Ninguna prueba de consistencia falla para los métodos proporcionados anteriormente, EXCEPTO para el método LOG10 (esto se analiza más adelante).Las pruebas se ejecutaron en
.NET Framework 4.7.2
y.NET Core 2.2
; para plataformasx86
yx64
, en una máquina con procesador Intel de 64 bits, conWindows 10
y conVS2017 v.15.9.17
. Los siguientes 4 casos tienen el mismo efecto en los resultados de rendimiento:.NET Framework (x86)
Platform = x86
Platform = AnyCPU
,Prefer 32-bit
está marcado en la configuración del proyecto.NET Framework (x64)
Platform = x64
Platform = AnyCPU
,Prefer 32-bit
está desmarcado en la configuración del proyecto.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
Las pruebas de rendimiento siguientes producen una distribución uniforme de valores entre la amplia gama de valores que podría asumir un número entero. Esto significa que existe una probabilidad mucho mayor de probar valores con una gran cantidad de dígitos. En escenarios de la vida real, la mayoría de los valores pueden ser pequeños, por lo que IF-CHAIN debería funcionar aún mejor. Además, el procesador almacenará en caché y optimizará las decisiones de IF-CHAIN de acuerdo con su conjunto de datos.
Como @AlanSingfield señaló en la sección de comentarios, el método LOG10 tuvo que arreglarse con una conversión hacia
double
adentroMath.Abs()
para el caso en que el valor de entrada esint.MinValue
olong.MinValue
.Con respecto a las primeras pruebas de rendimiento que implementé antes de editar esta pregunta (ya tenía que ser editado un millón de veces), hubo un caso específico señalado por @ GyörgyKőszeg , en el que el método IF-CHAIN funciona más lento que el método LOG10.
Esto todavía sucede, aunque la magnitud de la diferencia se redujo mucho después de la solución del problema señalado por @AlanSingfield . Esta corrección (agregar una conversión a
double
) provoca un error de cálculo cuando el valor de entrada es exactamente-999999999999999999
: el método LOG10 devuelve en20
lugar de19
. El método LOG10 también debe tener unaif
protección para el caso en que el valor de entrada sea cero.El método LOG10 es bastante complicado para trabajar con todos los valores, lo que significa que debe evitarlo. Si alguien encuentra una manera de hacer que funcione correctamente para todas las pruebas de coherencia a continuación, ¡publique un comentario!
El método WHILE también obtuvo una versión refactorizada reciente que es más rápida, pero aún es lenta
Platform = x86
(no pude encontrar la razón por la cual, hasta ahora).El método STRING es consistentemente lento: asigna con avidez demasiada memoria por nada. Curiosamente, en .NET Core, la asignación de cadenas parece ser mucho más rápida que en .NET Framework. Bueno saber.
El método IF-CHAIN debería superar a todos los demás métodos en el 99,99% de los casos; y, en mi opinión personal, es su mejor opción (considerando todos los ajustes necesarios para que el método LOG10 funcione correctamente y el mal desempeño de los otros dos métodos).
Finalmente, los resultados son:
Dado que estos resultados dependen del hardware, recomiendo de todos modos ejecutar las pruebas de rendimiento a continuación en su propia computadora si realmente necesita estar 100% seguro en su caso específico.
Código de prueba
A continuación se muestra el código para la prueba de rendimiento y también la prueba de coherencia. El mismo código se usa para .NET Framework y .NET Core.
fuente
long.MaxValue
Log10 es significativamente mejor. ¿O es solo en .NET Core?Int32
yInt64
generan diferentes conjuntos de datos, lo que puede explicar por quéInt64
fue más rápido queInt32
en algunos casos. Aunque, dentro de laInt32
prueba y dentro de laInt64
prueba, los conjuntos de datos no se modifican cuando se prueban los diferentes métodos de cálculo. Ahora, con respecto a .NET Core, dudo que haya alguna optimización mágica en la biblioteca de matemáticas que cambie estos resultados, pero me encantaría saber más sobre eso (mi respuesta ya es enorme, probablemente una de las más grandes en SO ;-)for
bucles sobreenumerations
, I conjuntos de datos aleatorios pre-proceso, y evitar el uso de genéricos, TareasFunction<>
,Action<>
o cualquier marco de medición negro-caja). En resumen, manténgalo simple. También elimino todas las aplicaciones innecesarias (Skype, Windows Defender, deshabilito Anti-Virus, Chrome, caché de Microsoft Office, etc.).No directamente en C #, pero la fórmula es:
n = floor(log10(x)+1)
fuente
log10
es en la mayoría de los casos una función de biblioteca. ¿Por qué querría implementarlo usted mismo y qué problemas encuentra?log10(x) = log2(x) / log2(10)
, o en generallogA(x) = logB(x) / logB(A)
.Log10(0)
es -infinito. Log10 no se puede usar para calcular el número de dígitos de números negativos a menos que lo useMath.Abs()
antes de pasar el valor a Log10. Pero luegoMath.Abs(int.MinValue)
arroja una excepción (long.MinValue
también, en el caso de Int64). Si convertimos el número al doble antes de pasarlo a Log10, entonces funciona para casi todos los números excepto para-999999999999999999
(en el caso de Int64). ¿Conoce alguna fórmula para calcular la cantidad de dígitos que usa log10 y acepta cualquier valor int32 o int64 como entrada y genera solo valores válidos?Las respuestas aquí ya funcionan para enteros sin signo, pero no he encontrado buenas soluciones para obtener el número de dígitos de decimales y dobles.
Puede cambiar el tipo de entrada de
double
adecimal
si la precisión importa, pero el decimal también tiene un límite.fuente
La respuesta de Steve es correcta , pero no funciona para números enteros menores que 1.
Aquí una versión actualizada que funciona para negativos:
fuente
digits = n == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(n)) + 1);
n = int.MinValue
.Usar recursividad (a veces preguntado en entrevistas)
fuente
number = int.MinValue
.fuente
-1
= 2Aquí hay una implementación que utiliza una búsqueda binaria. Parece ser el más rápido hasta ahora en int32.
La implementación de Int64 se deja como ejercicio para el lector (!)
Intenté usar Array.BinarySearch en lugar de codificar el árbol, pero eso fue aproximadamente la mitad de la velocidad.
EDITAR: Una tabla de búsqueda es mucho más rápida que la búsqueda binaria, a expensas de usar más memoria. De manera realista, probablemente usaría la búsqueda binaria en producción, la tabla de búsqueda es muy compleja para una ganancia de velocidad que probablemente sea eclipsada por otras partes del software.
Versión de la tabla de búsqueda:
Versión de búsqueda binaria
fuente
Int64
implementación para LookUpTable? ¿O crees que es demasiado complicado implementarlo? Me gustaría ejecutar las pruebas de rendimiento más adelante en el conjunto completo.dividir un número por 10 le dará el dígito más a la izquierda, luego hacer un mod 10 en el número da el número sin el primer dígito y repetirlo hasta que tenga todos los dígitos
fuente
fuente
string.TrimStart('-')
mejorCree un método que devuelva todos los dígitos y otro que los cuente:
Este me pareció el enfoque más intuitivo al abordar este problema. Probé el
Log10
método primero debido a su aparente simplicidad, pero tiene una cantidad increíble de casos de esquina y problemas de precisión.También encontré el
if
cadena propuesta en la otra respuesta a un poco fea de ver.Sé que este no es el método más eficiente, pero le brinda la otra extensión para devolver los dígitos también para otros usos (puede marcarlo
private
si no necesita usarlo fuera de la clase).Tenga en cuenta que no considera el signo negativo como un dígito.
fuente
convertir en cadena y luego puede contar el número de tatal de dígitos por el método .length. Me gusta:
fuente
Depende de lo que quieras hacer exactamente con los dígitos. Puede iterar a través de los dígitos comenzando desde el último hasta el primero de esta manera:
fuente
%
para obtener el dígito y luego/=
cortarlo.Si es solo para validar, podría hacer:
887979789 > 99999999
fuente
Suponiendo que su pregunta se refería a un int, lo siguiente también funciona para negativo / positivo y cero:
fuente