第 5 天的问题
下面的程序是对两个十六进制(hex)字面常量进行相加,然后打印出十六进制的结果。这个程序会打印出什么呢?
public class JoyOfHex {
public static void main(String[] args) {
System.out.println(Long.toHexString(0x100000000L + 0xcafebabe));
}
}
第 5 天问题的解答
看起来很明显,该程序应该打印出1cafebabe。毕竟,这确实就是十六进制数字10000000016与cafebabe16的和。该程序使用的是long型运算,它可以支持16位十六进制数,因此运算溢出是不可能的。
遗憾的告诉你,结果是cafebabe,并没有任何前导的1。这个输出表示的是正确结果的低32位,但是不知何故,第33位丢失了。
原因
十进制字面常量具有一个很好的属性,即所有的十进制字面常量都是正的,而十六进制或是八进制字面常量并不具备这个属性。
要想书写一个负的十进制常量,可以使用一元取反操作符(-)连接一个十进制字面常量。以这种方式,你可以用十进制来书写任何int或long型的数值,不管它是正的还是负的,并且负的十进制常数可以很明确地用一个减号符号来标识。
但是十六进制和八进制字面常量并不是这么回事,它们可以具有正的以及负的数值。如果十六进制和八进制字面常量的最高位被置位了,那么它们就是负数。在这个程序中,数字0xcafebabe是一个int常量,它的最高位被置位了,所以它是一个负数。它等于十进制数值-889275714。
该程序执行的这个加法是一种"混合类型的计算(mixed-type computation)。左操作数是 long 类型的,而右操作数是 int 类型的。为了执行该计算,Java将int类型的数值用拓宽原始类型转换提升为一个long类型,然后对两个long类型数值相加。因为int是一个有符号的整数类型,所以这个转换执行的是符合扩展,它将负的int类型的数值提升为一个在数值上相等的long类型数值。
解决办法
System.out.println(Long.toHexString(0x100000000L + 0xcafebabeL));
这个加法的右操作数0xcafebabe被提升为了long类型的数值0xffffffffcafebabeL。这个数值之后被加到了左操作数0x100000000L上。当作为int类型来被审视时,经过符号扩展之后的右操作数的高32位是-1,而左操作数的高32位是1,将这两个数值相加就得到了0,这也就解释了为什么在程序输出中前导1丢失了。
第 5 天问题的总结
这个题给我们的教训是:混合类型的计算可能会产生混淆,尤其是十六进制和八进制字面常量无需显式的减号符号就可以表示负的数值。为了避免这种窘境,通常最好是避免混合类型的计算。
对于语言的设计者们来说,应该考虑支持无符号的整数类型,从而根除符号扩展的可能性。可能会有这样的争辩:负的十六进制和八进制字面常量应该被禁用,但是这可能会挫伤程序员,他们经常使用十六进制字面常量来表示那些符号没有任何重要含义的数值。
今天的问题
转型被用来将一个数值从一种类型转换到另一种类型。下面的程序连续使用了三个转型。那么它到底会打印出什么呢?
public class Multicast {
public static void main(String[] args) {
System.out.println((int) (char) (byte) -1);
}
}
posted on 2008-05-17 17:52
李四飞刀 阅读(1571)
评论(2) 编辑 收藏 所属分类:
每日一题