计算一个圆形数量级

时间:2009-07-30 09:32:31

标签: c performance precision

对于一个简单的项目,我必须使大数字(例如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];

(这明显慢,但更容易阅读)

根据Benford定律分配数字,数字应该被视为无符号64位数,因为10 ^ x附近不应该有舍入误差(例如在python math.log(1000,10)中返回2.999996,这得到落到2)。 我有什么快速,准确的方法吗?

5 个答案:

答案 0 :(得分:16)

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

这种方法可以推广用于求任何基数的对数,只需计算出如上所述的适当常数。

答案 1 :(得分: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;

答案 2 :(得分:1)

不要拨弄号码,而是将s(n)printf号码改成a 使用“%E”的字符串,然后适当地替换为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就像是

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

答案 3 :(得分:0)

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

答案 4 :(得分: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