Pregunta

Para un proyecto simple tengo que hacer que los números grandes (por ejemplo, 4294967123) sean legibles, por lo que estoy escribiendo solo los primeros dígitos con un prefijo (4294967123 - > 4.29G, 12345 - > 12.34K, etc.)

El código (simplificado) tiene este aspecto:

const char* postfixes=" KMGT";
char postfix(unsigned int x)
{
     return postfixes[(int) floor(log10(x))];
}

Funciona, pero creo que hay una solución más elegante / mejor que calcular el logaritmo de precisión total, redondearlo y convertirlo en un int de nuevo.

Otras soluciones que pensé:

int i=0;
for(; x >= 1000 ; ++i) x/=1000;
return postfixes[i];

(Esto es significativamente más lento, pero más fácil de leer)

Los números se distribuyen de acuerdo con la Ley de Benford y el número debe tratarse como un número de bit de 64 sin signo, ya que no debería haber ningún error de redondeo cerca de 10 ^ x (por ejemplo, en el math.log de python (1000,10 ) devuelve 2.999996, que se sitúa en 2). ¿Hay alguna otra manera rápida y precisa que me esté perdiendo?

¿Fue útil?

Solución

Su código log10 / piso es perfectamente legible, y su costo de rendimiento probablemente se reducirá con el del formato de cadena que posteriormente realizará en su salida.

Sin embargo, supongamos que realmente necesita el rendimiento ...

Tenga en cuenta que log10 (x) == log2 (x) / log2 (10) == log2 (x) * 1 / log2 (10)

1 / log2 (10) es una constante

log2 (x) generalmente se puede realizar a bajo costo en la tubería de enteros en arquitecturas modernas usando instrucciones como CLZ o bit twiddling hack , obteniendo un número entre 0 y 63 para un entero de 64 bits. Eso encaja en 6 bits, lo que nos deja hasta 58 bits después del punto radix utilizable para la aritmética de punto fijo en un tipo de 64 bits.

Entonces podemos usar aritmética de punto fijo para encontrar el log10:

unsigned long long integer_log10( unsigned long long _in )
{
    unsigned long long log10fp6x58 = 0x134413509f79ff0llu; // (unsigned long long) (double(1llu<<58) / log2(10.0))
    return (((integer_log2(_in)) * log10fp6x58)+(1llu<<57)) >> 58;
}

La implementación de integer_log2 depende del compilador / plataforma; p.ej. en GCC / PowerPC, es

unsigned long long integer_log2( unsigned long long _in )
{
    return 63 - __cntlzd(_in);
}

Este enfoque se puede generalizar para encontrar el logaritmo de cualquier base, simplemente calcule la constante apropiada como se describe anteriormente.

Otros consejos

Este es el método más sencillo y simple que se me ocurre ... y quizás sea un poco más rápido que calcular el logaritmo:

postfixes = {{1e12, "T"},
             {1e9,  "G"},
             {1e6,  "M"},
             {1e3,  "K"}}

for each postfix in postfixes{
    if(x > postfix.value){
        return (x / postfix.value) + postfix.letter;
    }
}

return x;

No juegues con el número, en lugar de s (n) imprime el número en una cadena usando "% E " ;, luego sustituya apropiadamente por E + 00 E + 03 E + 09 (etc.) (IIRC, solo debes obtener poderes de 3 con notación científica - que es lo que quieres).

char number_buff[30];
snprintf(number_buff, 29, "%E", x);
char *powered_number_string = substitute_powers(number_buff);

char * substit_powers (const char * number_buff) está desordenado en C.

sed sería algo como

-e s / E + 0 // -e s / E + 3 / K / -e s / E + 6 / M / -e s / E + 9 / G /

Convierta el número en una cadena y use la longitud de las cadenas. Esto ciertamente no es más rápido, pero será muy preciso. Luego puede continuar y usar la cadena directamente para generar el resultado al cortarlo adecuadamente.

En primer lugar, si necesita formatear un cero, no desea tomar el logaritmo de eso. Segundo, quieres algo bonito, por lo que no quieres, por ejemplo, " 1000M " Por 999,800,000. Tercero, probablemente quieras redondear.

Le sugiero que use algo como este pseudocódigo:


function format(long x by value)
int p=5, char suf
if x<100000 then return string(x)
if x>=10000000000000 then
   x/=100000000
   p+=8
if x>=1000000000 then
   x/=10000
   p+=4
if x>=10000000 then
   x/=100
   p+=2
if x>=1000000 then
   x/=10
   p+=1
x+=5
if x>=100000 then
   x/=10
   p+=1
switch(p/3)
   6: suf='E'
   5: suf='P'
   4: suf='T'
   3: suf='G'
   2: suf='M'
   1: suf='K'
switch(p mod 3)
   2: return format("000 A",x/1000,suf)
   1: return format("00.0 A",x/10000,(x%10000)/100,suf)
   0: return format("0.00 A",x/100000,(x%100000)/100,suf)
end function
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top