java java.lang.Long詳解之三 大顯神通的位移運算

文章看事後感受受益不淺,因此留下了以備溫故:http://www.congmo.net/blog/2012/03/11/Long-ByteShifting/
java

本篇主要講述位移運算在Long中所扮演的重要角色,有些出神入化的我根本沒法理解,可是我一直秉承着不管對錯都要記錄思考的過程的宗旨寫每一篇文章。這一篇也不例外,讀Long這個類確實須要比較普遍的知識面,我也是一邊在OSChina和stackoverflow上提問,一邊慢慢的鑽研,不免會存在誤差。web

先來看個簡單的。
算法

public static int signum(long i) {
  // HD, Section 2-7
  return (int) ((i >> 63) | (-i >>> 63));
}

這個函數做用就是返回參數i的符號,若是返回-1則是負數,若是返回0則是0,若是返回1則是正數。算法就是(int) ((i >> 63) | (-i >>> 63)),若是是正數的話i有符號右移63位後爲0,-i無符號右移63位以後結果爲1,或操做以後結果就是1.若是i爲負數,那麼有符號右移63位後就變成了1,而後-i無符號右移63位後就只剩下符號位,最後作或(|)操做結果就是-1. 若是參數i爲0,那麼移位後結果就是0.
函數

System.out.println(Long.signum(100L));
System.out.println(Long.signum(0L));
System.out.println(Long.signum(-100L));
 
輸出結果:
1
0
-1

接着是一個不多用到,可是實現方式不錯的兩個方法,循環左移和循環右移方法。
ui

public static long rotateLeft(long i, int distance) {
    return (i << distance) | (i >>> -distance);
}
public static long rotateRight(long i, int distance) {
    return (i >>> distance) | (i << -distance);
}

實現的代碼量能夠說已經精簡到最少了,有一點要注意的是,循環移位時,參數distance能夠接受負數,當distance爲負數時,這個等式是成立的,rotateLeft(i, distance) = rotateRight(i, -distance)。這個方法中有兩點值得借鑑的,第一從總體上講循環移位的實現方式;第二是distance與-distance的巧妙運用。google

就拿循環左移先來講說第二點吧,前置條件,咱們首先假設distance大於0,起先我是很不理解i >>> -distance的,後來在stackoverflow上發問,有人給出瞭解釋,在移位的時候,若是distance小於0,會根據被移位數的長度進行轉換。就好比說這裏咱們對long進行移位,那麼-distance就會被轉換成(64 + distance)(注,這裏的distance是小於0的)。這樣的話,若是distance大於0時,(i << distance) | (i >>> -distance);就會被轉化成(i << distance) | (i >>> 64 + distance);spa

清楚了第二點,那麼第一點也就不難理解了。用一幅圖來解釋循環左移。

在distance大於0的前提下,先左移distance位,而後再右移64-distance,最終用或運算相加,就是循環移位的結果。圖中爲了省事兒用了8位作了個演示,先左移3位,而後右移(8-3)位,或運算以後就是結果啦。關於-distance在stackoverflow上的提問在這裏.net

下面是個更給力的方法-reverse(long i),能夠說就是高效率的化身。code

public static long reverse(long i) {
    // HD, Figure 7-1
    i = (i & 0x5555555555555555L) << 1 | (i >>> 1) & 0x5555555555555555L;
    i = (i & 0x0f0f0f0f0f0f0f0fL) << 4 | (i >>> 4) & 0x0f0f0f0f0f0f0f0fL;
    i = (i & 0x00ff00ff00ff00ffL) << 8 | (i >>> 8) & 0x00ff00ff00ff00ffL;
    i = (i << 48) | ((i & 0xffff0000L) << 16) |
            ((i >>> 16) & 0xffff0000L) | (i >>> 48);
    return i;
}

從總體上說,這個reverse方法集移位與二分算法於一身,堪稱經典。 第一步以單位爲單位,奇偶位交換 第二步以兩位爲單位,完成先後兩位的交換。 第三步以四位爲單位,完成先後四位的交換。 第四步以八位爲單位,完成先後八位的交換。 最後一步沒有按常理繼續二分,而是經過一個轉換一步就完成了以16和32位爲單位的交換。進而結束了整個64位的反轉。orm

如今一步一步剖析都是如何實現的。

i = (i & 0x5555555555555555L) << 1 | (i >>> 1) & 0x5555555555555555L;

16進制的5爲0101,或操做前半部分首先取出i的全部奇數位,而後總體左移一位,這樣實現i的奇數位左移一位變成偶數位;或操做後半部分先右移,即將偶數位右移變成奇數位,而後再取出奇數位。這樣就完成了64位中奇數位與偶數位的交換。

i = (i & 0x3333333333333333L) << 2 | (i >>> 2) & 0x3333333333333333L;

這句一樣是實現交換,只不過3對應的16進製爲0011,即本次交換以2個字節爲單位,交換完成了4個字節的反轉。 Liquid error: Flag value is invalid: -O 」」 直到這行代碼,實現了以字節爲單位的反轉,最後僅僅使用一行代碼就實現了一兩個字節和四個字節爲單位的反轉。 爲了方便畫圖,如今對操做進行編號,另外從以字節爲單位交換開始,以前的細節忽略。
(i & 0x00ff00ff00ff00ffL) << 8 | (i >>> 8) & 0x00ff00ff00ff00ffL;
(i << 48)
(i & 0xffff0000L) << 16)
(i >>> 16) & 0xffff0000L)
(i >>> 48);
這幅圖描述每一個編號代碼執行以後64位的變化。

很少作解釋,因爲這個reverse的最後一行不是按常理」出牌」,因此我使用純粹的二分法來實現reverse。
public static long reverse(long i) {
  // HD, Figure 7-1
  i = (i & 0x5555555555555555L) << 1 | (i >>> 1) & 0x5555555555555555L;
  i = (i & 0x3333333333333333L) << 2 | (i >>> 2) & 0x3333333333333333L;
  i = (i & 0x0f0f0f0f0f0f0f0fL) << 4 | (i >>> 4) & 0x0f0f0f0f0f0f0f0fL;
  i = (i & 0x00ff00ff00ff00ffL) << 8 | (i >>> 8) & 0x00ff00ff00ff00ffL;
  i = (i & 0x0000ffff0000ffffL) << 16 | (i >>> 16) & 0x0000ffff0000ffffL;
  i = (i & 0x00000000ffffffffL) << 32 | (i >>> 32) & 0x00000000ffffffffL;
  return i;
}

至於爲何要採用那種方式,而不是用」純粹」的二分法,在stackoverflow上有人提到,多是由於前一種實現方式須要9個操做,然後一種實現方式須要10個操做。具體是出於怎樣的目的可能只有做者才知道。關於reverse我在stackoverflow上的提問在這裏

最後看一個方法。

public static int bitCount(long i) {
    // HD, Figure 5-14
    i = i - ((i >>> 1) & 0x5555555555555555L);
    i = (i & 0x3333333333333333L) + ((i >>> 2) & 0x3333333333333333L);
    i = (i + (i >>> 4)) & 0x0f0f0f0f0f0f0f0fL;
    i = i + (i >>> 8);
    i = i + (i >>> 16);
    i = i + (i >>> 32);
    return (int)i & 0x7f;    
}
這個方法是返回一個long類型的數字對應的二進制中1的個數,其實google上有不少種,這裏採用的叫平行算法實現的,算法以下圖。

可是這個方法的第一行又採起了一個特別的方式實現i中奇數位+偶數位,我有點兒沒想明白。整體上就是像圖中所示那樣,相鄰的兩位相加的結果再相鄰的四位相加,最後獲得二進制中1的個數。 還有一點值得提一下,就是最後一行與上7f,由於long類型,1的個數最多也不會超過64個,因此只取最後7位便可。
相關文章
相關標籤/搜索