binarySearchメソッドのバグ
GoogleのJoshua Bloch(Sun Microsystemsから転職したのは,このブログの読者は知ってるよね?)が,Google Research Blogで長い間眠っていた興味深いJavaのライブラリのバグについて報告している.
http://googleresearch.blogspot.com/2006/06/extra-extra-read-all-about-it-nearly.html
問題があるのは二分探索のコード中の中間を知るために平均を求める部分なのだが,整数値を加算した時にオーバーフローすると負の値になってしまい,その結果ArrayIndexOutOfBoundsExceptionが投げられるというものだ.java.util.Arraysクラス以外にも,java.util.Collectionsクラスやjava.util.TreeMapクラスや,他の分割統治法を用いるようなコードに存在している.自分のコードを見直してみるのもよいだろう.
さて,この部分だけを取り出したサンプルコードを書いてみた.
public class AverageTest { public static void main(String args[]) { int high = 100; int low = 10; System.out.printf("high = %d, low = %d?n", high, low); System.out.printf("1, (low + high) / 2 -> %d?n", (low + high) / 2); System.out.printf("2, (low + high) >> 1 -> %d?n", (low + high) >> 1); System.out.printf("3, (low + high) >>> 1 -> %d?n", (low + high) >>> 1); System.out.printf("4, low + ( (high - low) / 2) -> %d?n", low + ( (high - low) / 2)); high = Integer.MAX_VALUE; low = Integer.MAX_VALUE - 1; System.out.printf("?nhigh = %d, low = %d?n", high, low); System.out.printf("1, (low + high) / 2 -> %d?n", (low + high) / 2); System.out.printf("2, (low + high) >> 1 -> %d?n", (low + high) >> 1); System.out.printf("3, (low + high) >>> 1 -> %d?n", (low + high) >>> 1); System.out.printf("4, low + ( (high - low) / 2) -> %d?n", low + ( (high - low) / 2)); } }
この実行結果は,以下のようになる.つまり,正しいコードは3と4だ.
high = 100, low = 10 1, (low + high) / 2 -> 55 2, (low + high) >> 1 -> 55 3, (low + high) >>> 1 -> 55 4, low + ((high - low) / 2) -> 55 high = 2147483647, low = 2147483646 1, (low + high) / 2 -> -1 2, (low + high) >> 1 -> -2 3, (low + high) >>> 1 -> 2147483646 4, low + ((high - low) / 2) -> 2147483646
さて,この興味深いバグは,いくつかの示唆を与えてくれる.一つは,この種のバグは非常に見つけにくいことであり,もう一つは技術の進歩の早さである.昔は32ビット空間は広大だと思ったが,今では必ずしもそうではない.Webの登場により,大規模データを処理するのが当たり前となりつつある昨今,この種のバグを作らないこと,また早期に発見して修正することが,ますます重要になっていくだろう.