Question

Pour un projet simple, je dois rendre lisibles les grands nombres (4294967123, par exemple), aussi je n’écris que les premiers chiffres avec un préfixe (4294967123 - > 4.29G, 12345 - > 12,34K, etc.)

Le code (simplifié) ressemble à ceci:

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

Cela fonctionne, mais je pense qu’il existe une solution plus élégante / meilleure que de calculer le logarithme de précision complète, de l’arrondir et de le réduire à nouveau à un entier.

Autres solutions auxquelles j'ai pensé:

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

(Ceci est nettement plus lent, mais plus facile à lire)

Les numéros sont répartis entre selon la loi de Benford et le nombre doit être traité comme un nombre non signé de 64 bits, car il ne devrait pas y avoir d'erreur d'arrondi près de 10 ^ x (par exemple, en python math.log (1000,10 ) renvoie 2.999996, qui passe à 2). Y a-t-il un autre moyen rapide et précis qui me manque?

Était-ce utile?

La solution

Votre code log10 / floor est parfaitement lisible et son coût en termes de performances sera probablement inférieur à celui de la mise en forme de la chaîne que vous effectuerez ultérieurement sur votre sortie.

Cependant, supposons que vous deviez vraiment avoir besoin de la performance ...

Notez que log10 (x) == log2 (x) / log2 (10) == log2 (x) * 1 / log2 (10)

1 / log2 (10) est une constante

log2 (x) peut généralement être effectué à moindre coût dans le pipeline d’entiers sur des architectures modernes à l’aide d’instructions telles que CLZ ou bit bidouillage , donnant un nombre compris entre 0 et 63 pour un entier 64 bits. Cela correspond à 6 bits, nous laissant ainsi jusqu'à 58 bits après le point fondamental utilisables pour l'arithmétique en virgule fixe dans un type 64 bits.

Nous pouvons alors utiliser l'arithmétique en virgule fixe pour trouver le 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;
}

L'implémentation de integer_log2 dépend du compilateur / de la plate-forme; par exemple. sur GCC / PowerPC, il est

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

Cette approche peut être généralisée pour trouver le logarithme de n'importe quelle base, calculez simplement la constante appropriée comme décrit ci-dessus.

Autres conseils

C’est la méthode la plus simple et la plus simple à laquelle je puisse penser ... et ce sera peut-être un peu plus rapide que de calculer le logarithme:

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;

Ne manipulez pas le numéro, mais s (n) imprimez-le en un chaîne en utilisant "% E", puis remplacez de manière appropriée par E + 00 E + 03 E + 09 (etc) (IIRC, vous ne devriez obtenir que des puissances de 3 avec une notation scientifique - ce que vous voulez).

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

car * substitute_powers (const car * number_buff) est en désordre dans C.

sed serait quelque chose comme

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

Convertissez le nombre en chaîne et utilisez la longueur de la chaîne. Ce n'est certainement pas plus rapide, mais sera très précis. Vous pouvez ensuite utiliser la chaîne directement pour générer le résultat en la découpant de manière appropriée.

Tout d’abord, si vous devez formater un zéro, vous ne voulez pas en prendre le logarithme. Deuxièmement, vous voulez quelque chose de joli, vous ne voulez donc pas, par exemple, "1000M" pour 999,800,000. Troisièmement, vous voulez probablement arrondir.

Je vous suggère d'utiliser quelque chose comme ce pseudocode:


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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top