2012年11月27日 星期二

Java 數字溢位時的處理

做個小記錄~
目前打算模仿 ConcurrentHashMap 在實作 OCC 的方法,去做出不需要 Exclusive Lock 的 Concurrent Control
所以會需要一直對某個數字做遞增~
但是不太確定 JVM 對於數字遞增到最大值以後到底會如何處理,因此稍微查了一下。
依照 [1] 中 Jim 的回應,在 Java 中數字的表示是採用 2's complement(2 的補數)來表達
而由 [2] 右上角的對照表,假設某個數字是用 8 bits 來表達,可以表達的數字範圍是 -27 ~ 27-1

0 1 1 1 1 1 1 1 = 127
0 0 0 0 0 0 1 0 = 2
0 0 0 0 0 0 0 1 = 1
0 0 0 0 0 0 0 0 = 0
1 1 1 1 1 1 1 1 = −1
1 1 1 1 1 1 1 0 = −2
1 0 0 0 0 0 0 1 = −127
1 0 0 0 0 0 0 0 = −128

2 的補數表示法,簡單來說就是要找出某個數字的補數(變換正負號以後的對應數字,例如 2 <-> -2)
只要把所有的 bits 全部反向後再加 1 就可以得到補數了。
例如 8 bits 的數字中,2 的表達是 00000010,反向可以得到 11111101,再 +1 是 11111110,即是 -2 的表達了。

其中 27-1 = 127,表達出來的 bits 是 01111111
這時如果再 +1 的話,應該會變成是 11111111,這是 -1 的表達方法(因為採用的是 2 的補數的表達法)
不過這個是理論上的預測,實際測試時結果並不是這樣 XD

接著就直接來測試了~
int x = Integer.MAX_VALUE;
System.out.println(x);
System.out.println(++x);
結果為:
2147483647
-2147483648
測試結果...最大值遞增會跑到最小值去~。

註:測試環境為 Windows 7 64-bit、Java SE 6 Update 34

如果使用的是 AtomicInteger 的話,使用 incrementAndGet() 函式的狀況下
如果遇到已經達到 integer 的最大值時,繼續遞增的結果可以參考 [3]
user949300 回應提到,去看 AtomicInteger 的原始碼,會看到以下的程式碼:
public final int incrementAndGet() {
  for (;;) {
    int current = get();
    int next = current + 1;
    if (compareAndSet(current, next))
      return next;
  }
}
表示遞增後的結果,應該會如同 JVM 預設的行為一樣才對。

一樣直接測試,測試方法如 [3] 中 Bohemian 的回應的測試,程式碼如下:
System.out.println(new AtomicInteger(Integer.MAX_VALUE).incrementAndGet());
System.out.println(Integer.MIN_VALUE);
而印出的結果如下:
-2147483648
-2147483648
可以看出 AtomicInteger 的遞增如果跑到最大值,繼續遞增的下一個數字一樣會跳到 Integer 的最小值。

參考資料:
1、How does Java handle integer underflows and overflows and how would you check for it?
2、Wikipedia:2的補數
3、AtomicInteger incrementation

沒有留言: