2010-07-22 29 views
110

我用下面的函數來計算日誌基地2的整數:如何計算整數的Java中的日誌庫2?

public static int log2(int n){ 
    if(n <= 0) throw new IllegalArgumentException(); 
    return 31 - Integer.numberOfLeadingZeros(n); 
} 

它有最佳的性能?

有人知道爲此目的準備好J2SE API函數嗎?

UPD1令人驚訝的是,浮點運算似乎比整數運算更快。

UPD2由於意見我會進行更詳細的調查。

UPD3我的整數算術函數比Math.log(n)的/Math.log快10倍(2)。

+1

你怎麼測試這個性能?在我的系統(Core i7,jdk 1.6 x64)上,整數版本幾乎比浮點版本快10倍。一定要用函數的結果做一些事情,以便JIT不能完全刪除計算! – x4u 2010-07-22 03:57:19

+0

你是對的。我沒有使用計算結果,編譯器已經優化了一些東西。現在我的結果和你一樣 - 整數函數速度提高10倍(Core 2 Duo,jdk 1.6 c64) – Nulldevice 2010-07-22 05:03:40

+3

這有效地爲你提供了'Math.floor(Math.log(n)/Math.log(2))',所以它不是真的計算日誌庫2! – Dori 2016-04-20 16:48:46

回答

56

如果你正在考慮使用浮點幫助整數算術,你必須要小心。

我通常儘可能地避免FP計算。

浮點運算不準確。你永遠無法知道(int)(Math.log(65536)/Math.log(2))會評估什麼。例如,Math.ceil(Math.log(1<<29)/Math.log(2))在我的PC上爲30,在數學上它應該是29.我沒有找到x的值,其中(int)(Math.log(x)/Math.log(2))失敗(僅僅因爲只有32個「危險」值),但這並不意味着它會在任何PC上以相同的方式工作。

這裏常用的技巧是在四捨五入時使用「epsilon」。像(int)(Math.log(x)/Math.log(2)+1e-10)應該永遠不會失敗。這個「epsilon」的選擇不是一項簡單的任務。

更多演示,使用更一般的任務 - 試圖實現int log(int x, int base)

的測試代碼:

static int pow(int base, int power) { 
    int result = 1; 
    for (int i = 0; i < power; i++) 
     result *= base; 
    return result; 
} 

private static void test(int base, int pow) { 
    int x = pow(base, pow); 
    if (pow != log(x, base)) 
     System.out.println(String.format("error at %d^%d", base, pow)); 
    if(pow!=0 && (pow-1) != log(x-1, base)) 
     System.out.println(String.format("error at %d^%d-1", base, pow)); 
} 

public static void main(String[] args) { 
    for (int base = 2; base < 500; base++) { 
     int maxPow = (int) (Math.log(Integer.MAX_VALUE)/Math.log(base)); 
     for (int pow = 0; pow <= maxPow; pow++) { 
      test(base, pow); 
     } 
    } 
} 

如果我們用最直觀的實現對數,

static int log(int x, int base) 
{ 
    return (int) (Math.log(x)/Math.log(base)); 
} 

此打印:

error at 3^5 
error at 3^10 
error at 3^13 
error at 3^15 
error at 3^17 
error at 9^5 
error at 10^3 
error at 10^6 
error at 10^9 
error at 11^7 
error at 12^7 
... 

要完全擺脫錯誤,我不得不添加介於1e-11和1e-14之間的epsilon。 您可以在測試之前告訴這個嗎? 我絕對不能。

+2

「這並不意味着它會在任何PC上以相同的方式工作」 - 如果您使用'strictfp',那麼會不會? – Ken 2010-07-22 05:14:42

+0

@Ken:也許...但只有在詳盡列舉所有可能的輸入值之後,才能確定。 (我們很幸運這裏有這麼少的人) – Rotsor 2010-07-22 11:01:59

+1

技術上,是的,但是對於任何功能都是如此。在某些情況下,如果你使用可用的文檔,並且測試一些精心挑選的「所有可能的輸入值」中的一小部分,那麼你必須相信,你的程序將工作得很好。實際上嚴格來說''strictfp'似乎已經得到了很多廢話。 :-) – Ken 2010-07-25 18:31:49

12

爲什麼不:

public static double log2(int n) 
{ 
    return (Math.log(n)/Math.log(2)); 
} 
+0

儘管在數學上這是正確的,但請注意,由於不精確的浮點算術,存在計算錯誤的風險,正如Rotsor的答案中所解釋的。 – leeyuiwah 2017-10-22 21:52:02

25

嘗試Math.log(x)/Math.log(2)

+1

儘管在數學上這是正確的,但請注意,由於不精確的浮點運算,存在計算錯誤的風險,正如Rotsor的答案中所解釋的。 – leeyuiwah 2017-10-22 21:51:34

21

您可以使用身份

  log[a]x 
log[b]x = --------- 
      log[a]b 

所以這將是適用於的log 2。

  log[10]x 
log[2]x = ---------- 
      log[10]2 

只需將其插入到這個java的數學方法LOG10 ....

http://mathforum.org/library/drmath/view/55565.html

+0

又名「基礎變更」公式:http://home.windstream.net/okrebs/page57.html – 2017-02-23 22:00:36

+0

儘管在數學上這是正確的,但請注意,由於不精確的浮點數有誤計算的風險算術,正如Rotsor的答案中所解釋的那樣。 – leeyuiwah 2017-10-22 21:51:46

75

這是我使用該計算的功能:

public static int binlog(int bits) // returns 0 for bits=0 
{ 
    int log = 0; 
    if((bits & 0xffff0000) != 0) { bits >>>= 16; log = 16; } 
    if(bits >= 256) { bits >>>= 8; log += 8; } 
    if(bits >= 16 ) { bits >>>= 4; log += 4; } 
    if(bits >= 4 ) { bits >>>= 2; log += 2; } 
    return log + (bits >>> 1); 
} 

它比Integer.numberOfLeadingZeros()(20-30%)稍快幾乎10倍(JDK 1.6 x64)的比Math.log()的實現這樣一個:

private static final double log2div = 1.000000000001/Math.log(2); 
public static int log2fp0(int bits) 
{ 
    if(bits == 0) 
     return 0; // or throw exception 
    return (int) (Math.log(bits & 0xffffffffL) * log2div); 
} 

兩個函數將返回所有可能的輸入值相同的結果。

更新: Java 1.7服務器JIT能夠用基於CPU內部函數的替代實現替換一些靜態數學函數。其中一個函數是Integer.numberOfLeadingZeros()。因此,對於1.7或更新的服務器虛擬機,類似於問題中的實現的實現實際上比上述binlog稍快。不幸的是客戶JIT似乎沒有這種優化。

public static int log2nlz(int bits) 
{ 
    if(bits == 0) 
     return 0; // or throw exception 
    return 31 - Integer.numberOfLeadingZeros(bits); 
} 

此實現還返回與上述其他兩個實現相同的結果,即所有2^32個可能的輸入值。

這裏是我的電腦上的實際運行時間(Sandy Bridge的酷睿i7):

JDK 1.7的32位客戶機VM:

binlog:   11.5s 
log2nlz:  16.5s 
log2fp:  118.1s 
log(x)/log(2): 165.0s 

JDK 1.7的x64服務器虛擬機:

binlog:   5.8s 
log2nlz:   5.1s 
log2fp:   89.5s 
log(x)/log(2): 108.1s 

這是測試代碼:

int sum = 0, x = 0; 
long time = System.nanoTime(); 
do sum += log2nlz(x); while(++x != 0); 
time = System.nanoTime() - time; 
System.out.println("time=" + time/1000000L/1000.0 + "s -> " + sum); 
+6

x86的'BSR'指令執行'32 - numberOfLeadingZeros',但是未定義爲0,所以(JIT)編譯器必須檢查非零值,如果它不能證明它不必。 BMI指令集擴展(Haswell和更新)引入了'LZCNT',它完全實現'numberOfLeadingZeros',只需一條指令。它們都是3個週期的延遲,每個週期的吞吐量爲1個。所以我絕對推薦使用'numberOfLeadingZeros',因爲這樣可以很容易地創建一個好的JVM。 (關於'lzcnt'的一個奇怪的事情是它錯誤地依賴於它覆蓋的寄存器的舊值。) – 2015-09-15 16:14:54

+0

我最感興趣的是您對Java 1.7服務器JIT CPU intrinsics替換的評論。你有參考網址嗎? (JIT源代碼鏈接也可以。) – kevinarpe 2016-03-07 11:57:18

0

讓我們添加:

int[] fastLogs; 

private void populateFastLogs(int length) { 
    fastLogs = new int[length + 1]; 
    int counter = 0; 
    int log = 0; 
    int num = 1; 
    fastLogs[0] = 0; 
    for (int i = 1; i < fastLogs.length; i++) { 
     counter++; 
     fastLogs[i] = log; 
     if (counter == num) { 
      log++; 
      num *= 2; 
      counter = 0; 
     } 
    } 
} 

來源:https://github.com/pochuan/cs166/blob/master/ps1/rmq/SparseTableRMQ.java

+0

這將創建一個查找表。 OP要求更快的方式來「計算」對數。 – Dave 2017-01-01 04:52:56

5

有番石榴庫中的函數:

LongMath.log2() 

所以我建議使用它。

+0

如何將此軟件包添加到我的應用程序中? – 2015-10-14 05:52:57

+0

從[here](https://code.google.com/p/guava-libraries/)下載jar並將其添加到項目的構建路徑。 – 2016-02-18 06:03:28

+0

我應該添加一個庫到我的應用程序只使用一個功能? – 2016-10-04 13:27:47

3

爲了增加x4u答案,它給你一個數的二進制日誌的地板上,此功能返回一個數字的二進制日誌的小區:

public static int ceilbinlog(int number) // returns 0 for bits=0 
{ 
    int log = 0; 
    int bits = number; 
    if ((bits & 0xffff0000) != 0) { 
     bits >>>= 16; 
     log = 16; 
    } 
    if (bits >= 256) { 
     bits >>>= 8; 
     log += 8; 
    } 
    if (bits >= 16) { 
     bits >>>= 4; 
     log += 4; 
    } 
    if (bits >= 4) { 
     bits >>>= 2; 
     log += 2; 
    } 
    if (1 << log < number) 
     log++; 
    return log + (bits >>> 1); 
} 
+0

「數字」變量在哪裏? – Barteks2x 2016-09-03 16:10:30