Search on the blog

2013年9月8日日曜日

negateする処理に潜む罠

 Apache Commonsのソースを読んでいていたら、面白い内容があったので紹介します。

以下のサンプルを見てください。negate()メソッドは受け取ったint型の数をnegateして返します。この処理はすべてのint型の数について正しく動作するでしょうか?
package com.kenjih.test;

public class Clazz {

    public void run() throws Exception {
        int x = 77777;
        System.out.println(x);
        System.out.println(negate(x));        
    }

    int negate(int x) {
        return -x;
    }
    
    public static void main(String[] args) throws Exception {
        new Clazz().run();
    }
}

次のサンプルにあるようにintで表現できる最小の数を渡すとおかしなことが起こります。
package com.kenjih.test;

public class Clazz {

    public void run() throws Exception {
        int x = 0x80000000;
        System.out.println(x);
        System.out.println(negate(x));        
    }

    int negate(int x) {
        return -x;
    }
    
    public static void main(String[] args) throws Exception {
        new Clazz().run();
    }
}
$ -2147483648
$ -2147483648

何が起こっているか説明します。intの最小値は2の補数で表すと、
10000000000000000000000000000000
です。
これをnegateするためにまず1引きます。
01111111111111111111111111111111
そしてこれをビット反転します。
10000000000000000000000000000000

ということで、negateしても元の数に戻ってしまうという現象が起こります。

これを回避するためには以下のような例外処理を追加するとよいです。
package com.kenjih.test;

public class Clazz {

    public void run() throws Exception {
        int x = 0x80000000;
        System.out.println(x);
        System.out.println(negate(x));        
    }

    int negate(int x) {
        if (x == Integer.MIN_VALUE)
            throw new ArithmeticException("overflow: can't negate");
        return -x;
    }
    
    public static void main(String[] args) throws Exception {
        new Clazz().run();
    }
}

0 件のコメント:

コメントを投稿