Puzzle 1: Oddity
下面的方法就是用来判断唯一的一个参数是否为奇数,这个方法生效吗?
public static boolean isOdd(int i) {
return i % 2 == 1;
} |
Solution 1: Oddity
可以这样来判断奇数:一个整数被2除后余数为1。表达式i%2就是计算i被2除后的余数,所以这个程序似乎看起来应该生效。不幸的是,它没有;它有四分之一的时间返回了错误的答案。
为什么是四分之一? 因为一半的int值是负数,所以isOdd方法对所有的负奇数判断失败。对于任何负数,甚至为奇数时,它也始终返回false。这是java对于取余操作符(%)的逻辑定义。它按照以下的恒等式来定义任何int值a和任何非零int值b:
(a / b) * b + (a % b) == a |
换句话说,如果你用b来除a,再用b来乘以商,然后加上a对b的余数,你就能得到原来的数a。这个等式说明的很清楚,但是结合Java截取整型除法运算来看,它暗示了当取余操作返回一个非零的结果时,它同操作符左边的数具有同样的符号。
isOdd方法以及对于奇数的定义都是在假设所有的余数为正数的基础上的。尽管这种假设很符合某些形式的除法,但是Java取余操作符是完全与整型除法运算一致,它抛弃了结果的小数部分。
如果i是一个负奇数,i%2就等于-1而不是1,因此isOdd方法就会错误的返回false,相反地比较如下:
public static boolean isOdd(int i) {
return i % 2 != 0;
} |
如果你在把isOdd方法使用在一个临界的设置中,你最好使用按位AND操作符(&)来代替取余操作符:
public static boolean isOdd(int i) { return (i & 1) != 0; } |
第二个版本中可能运行的比第一个版本快,这得依赖于运行的平台和你使用的虚拟机,但是不可能运行的更慢。总的来说,除法和取余操作要比其他算术和逻辑运算慢。过早的优化并不是个好主意,但是在上面这个情形下,运行较快的版本是很清晰明白的,没有理由更喜欢这种原始的计算。
总之,在你使用取余操作时,你总是得思考操作数的符号以及计算的结果。当操作数为非负数时,这个很明显,但是当其中一个或者两个操作数都是负数时,就不是那么明显了。