3

对于一个简单的项目,我必须使大数字(例如 4294967123)可读,所以我只写带前缀的第一个数字(4294967123 -> 4.29G、12345 -> 12.34K 等)

代码(简化)如下所示:

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

它有效,但我认为有一个更优雅/更好的解决方案,而不是计算完整的精度对数,将其四舍五入并再次将其转换为 int。

我想到的其他解决方案:

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

(这要慢得多,但更容易阅读)

这些数字根据本福德定律分布,并且该数字应被视为无符号 64 位数字,因为在 10^x 附近不应存在舍入误差(例如,在 python 中math.log(1000,10)返回 2.999996,它被限制为 2)。有没有我想念的快速、准确的其他方式?

4

5 回答 5

17

您的 log10/floor 代码完全可读,其性能成本可能与您随后在输出中执行的字符串格式相比相形见绌。

但是,假设您真的需要性能...

请注意,log10(x) == log2(x) / log2(10) == log2(x) * 1/log2(10)

1/log2(10) 是一个常数

log2(x) 通常可以在现代架构上的整数管道中使用 CLZ 或bit twiddling hack等指令廉价地执行,对于 64 位整数产生介于 0 和 63 之间的数字。这适合 6 位,在 64 位类型中可用于定点算术的小数点之后最多为 58 位。

所以我们可以使用定点算法来找到 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;
}

integer_log2 的实现依赖于编译器/平台;例如在 GCC/PowerPC 上,它是

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

这种方法可以推广到找到任何底的对数,只需如上所述计算适当的常数。

于 2009-07-30T10:20:38.767 回答
2

这是我能想到的最直接和最简单的方法......也许它会比计算对数快一点:

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;
于 2009-07-30T10:08:56.013 回答
1

将数字转换为字符串并使用字符串长度。这当然不会更快,但会非常准确。然后,您可以继续并直接使用字符串通过适当地切片来构建结果。

于 2009-07-30T09:46:44.703 回答
1

不要摆弄数字,而是使用“%E”将数字s(n)printf成一个字符串,然后适当地替换E+00 E+03 E+09(等)(IIRC,你应该只获得权力3 用科学记数法 - 这就是你想要的)。

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

char *substitute_powers(const char *number_buff)在 C 中很乱。

sed 就像

-es/E+0// -es/E+3/K/ -es/E+6/M/ -es/E+9/G/

于 2009-07-30T10:10:10.537 回答
0

首先,如果你需要格式化一个零,你不想取它的对数。其次,你想要一些漂亮的东西,所以你不想要,例如,“1000M”代表 999,800,000。第三,你可能想要四舍五入。

我建议你使用类似这样的伪代码:


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
于 2009-09-05T09:32:59.597 回答