2009-07-30 63 views
2

對於一個簡單的項目,我必須使大數字(例如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)。 我錯過了什麼快速,準確的其他方式?

+0

我想你應該嘗試替換x/= 1000;其中x << = 3; – Drakosha 2009-07-30 10:02:37

+2

您也可以使用「查找整數的整數日誌庫2(也稱爲最高位集的位置)」http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup – Drakosha 2009-07-30 10:05:22

+0

哦!這讓我想起了着名的遊戲_Taipan!_(玩過那個?)_Taipan!_使用了對數。 – 2009-09-05 08:48:09

回答

16

您的log10/floor代碼是完全可讀的,其性能成本可能會被您隨後在輸出中執行的字符串格式化所壓制。

但是,假設你是真的需要表現...

注意LOG10(X)== LOG2(X)/ LOG 2(10)== LOG2(X)* 1/LOG2 (10)

1/LOG2(10)是一個常數

LOG2(X)通常可以在使用諸如CLZ或bit twiddling hack指令現代建築的整數流水線便宜地進行,得到之間0的數63爲64位整數。它適用於6位,在64位類型的定點算術可用的基數點之後,剩餘58位。

因此,我們就可以使用定點算法來找到日誌10:

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

這種方法可以概括爲找到任何鹼的對數,簡單地計算如上所述的適當的常數。

+0

它可能會更好地更改: unsigned long long log10fp6x58 => static const unsigned long long log10fp6x58 – Drakosha 2009-07-30 10:50:18

+0

如何在具有Clang/LLVM和Intel的現代化設置上使用此選項? (OS X) – 2013-06-22 01:09:25

+1

這是一個很好的解決方案,但1/log_2_(10)的近似值意味着結果不能保證是正確的。 一個快速,準確的解決方案將包括一個靜態數組與[1,10^3,10^6等],以及匹配的[「」,「K」,「M」,「G」等.. ]。然後在運行時,使用二進制搜索來查找第一個數組中最接近的匹配項,並將其索引到char的第二個數組中。 – Isaac 2014-11-24 01:32:39

0

將數字轉換爲字符串並使用字符串長度。這當然不會更快,但會非常準確。然後,您可以繼續使用該字符串直接通過適當地切片來構建結果。

+0

關於在重要點上舍入或舍入的情況如何? – paul 2009-07-30 10:02:02

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; 
1

不要用數字不甘示弱,不是s( n)使用「%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中會是這樣的

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

+0

它的工作原理,但它比for循環慢,它在開始時很難理解。 – tstenner 2009-07-30 10:37:33

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