哈?整型运算?听起来好 EASY 啊喂!直奔主题咯。
边界陷阱
无论何时,请注意整型运算的边界问题,考虑最大值,最小值,越界的可能性。
Puzzle 3: Long Division
1 2 3 4 5 6 7 | |
EASY 无压力,一目了然整型越界。 MICROS_PER_DAY 虽然声明为 long,可以装下计算结果,但是计算结果本身先是以 int 类型进行计算,计算完成后再赋值给 MICROS_PER_DAY 的。很不幸,在计算的时候就溢出了。
修改太简单,加一个类型声明后缀,大写的 L。
1 2 3 4 5 6 7 | |
Puzzle 26: In the Loop
1 2 3 4 5 6 7 8 9 10 11 | |
如果你没有仔细的看代码,可能会认为输出 100;如果你仔细一点,会发现这不是 for 循环的惯用语法 (idiom),可能会认为输出 101。Well 都不是,你会发现没有输出,代码陷入了无限循环 (Infinite loop)。因为所有的 int 值都 <= Integer.MAX_VALUE。
此外,将函数 f(int) 应用到所有的 40 亿整数的 idiom 是这个样子滴
1 2 3 4 5 | |
Puzzle 33: Infinite loop
1 2 3 | |
给 i 一个声明,使上面的语句陷入 Infinite loop。
答案是 int i = Integer.Integer.MIN_VALUE。所以注意,相应的还有一个小陷阱, Math.abs() 可以 < 0。
Puzzle 65: A Strange Saga of a Suspicious Sort
以下是一个将 100 个随机整数排序的代码段,短小精悍帅的很呢。但是很遗憾,输出基本都不会是众望所归的 Order.DESCENDING,问题出在哪呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | |
问题出在 Comparator<Integer> cmp 上,虽然它的实现是喜闻乐见的 idiom。考虑以下的代码段
1 2 3 4 5 6 7 | |
x < y,并不一定代表 x - y < 0。在用 Comparator.compare() 的 idiom 时候,请确认边界问题。
修改代码很简单,用现成的类库方法 Collections.reverseOrder() 提供逆序排列子。
Do not use a subtraction-based comparator unless you are sure that the difference between values will never be greater than Integer.MAX_VALUE.
类型转换
JSL 定义了若干类型转换,关于整型的有三种
- Widening Primitive Conversion
- byte to short, int, long, float, or double
- short to int, long, float, or double
- char to int, long, float, or double
- int to long, float, or double
- long to float or double
- float to double
- Narrowing Primitive Conversion
- short to byte or char
- char to byte or short
- int to byte, short, or char
- long to byte, short, char, or int
- float to byte, short, char, int, or long
- double to byte, short, char, int, long, or float
- Widening and Narrowing Primitive Conversion
- byte to char
整型的扩宽类型转换不会丢失值信息,声明中可直接转换。所有的缩窄类型转换都有可能丢失值信息,所以必须做显示的类型转换。
最奇怪的是第三种,一次类型转换既有扩宽又有缩窄。因为 char 无符号整型特殊一点,其实 byte 是先转为 int,然后再转为了 char。
Puzzle 6: Multicast
1 2 3 4 5 | |
转换过程
- (int) -1 ➡️ (byte) -1 : 0xffff ➡️ 0xf (narrowing)
- (byte) -1 ➡️ (char) -1 (widening and narrowing)
- (byte) -1 ➡️ (int) - 1 : 0xf ➡️ 0xffff
- (int) -1 ➡️ (char) 65535 : 0xffff ➡️ 0xff
- (char) 65535 ➡️ (int) 65535 : 0xff ➡️ 0xffff (widening)
秘籍:当心 Narrowing Primitive Conversion 的值变化。
Other Tips
Puzzle 1: 求余
以下是判断一个整数是否为奇数的方法,可行吗?
1 2 3 | |
One Quarter 的情况下不可行:当 i 为负奇数时, i % 2 == -1。
1 2 3 4 5 6 | |
Puzzle 27: 移位
1 2 3 4 5 6 7 8 | |
因为 -1 的二进制表示 0xffff 有 32 位 1,所以输出应该是 32 吧?但是这是一个 Infinite loop。因为 -1 左移 32 位还是 -1。
Shift operators use only the five low-order bits of their right operand as the shift distance, or six bits if the left operand is a long.
Puzzle 31: 组合运算符
1 2 3 | |
给 i 一个声明,使上面的语句陷入 Infinite loop。
Compound assignment operators is that they can silently perform narrowing primitive conversions.
答案可以是 short i = -1。所有非 long 的整型在运算时都需要转换为 int,该运算的步骤拆分为
- (short) -1 ➡️ (int) -1 : 0xff ➡️ 0xffff
- -1 >>> 1 ➡️ 0x7fff
- (int) 231 - 1 ➡️ (short) -1 : 0x7fff ➡️ 0xff
- Infinite loop
秘籍:不要对 byte, short, char 使用组合运算符。
总结
注意边界,注意转型,注意 byte char,注意特殊运算。