JAVA循環(huán)謎題33:循環(huán)者遇到了狼人

字號(hào):

請(qǐng)?zhí)峁┮粋€(gè)對(duì)i的聲明,將下面的循環(huán)轉(zhuǎn)變?yōu)橐粋€(gè)無(wú)限循環(huán)。這個(gè)循環(huán)不需要使用任何5.0版的特性:
    while (i != 0 && i == -i) {
    }
    這仍然是一個(gè)循環(huán)。在布爾表達(dá)式(i != 0 && i == -i)中,一元減號(hào)操作符作用于i,這意味著它的類型必須是數(shù)字型的:一元減號(hào)操作符作用于一個(gè)非數(shù)字型操作數(shù)是非法的。因此,我們要尋找一個(gè)非0的數(shù)字型數(shù)值,它等于它自己的負(fù)值。NaN不能滿足這個(gè)屬性,因?yàn)樗坏扔谌魏螖?shù)值,因此,i必須表示一個(gè)實(shí)際的數(shù)字。肯定沒有任何數(shù)字滿足這樣的屬性嗎?
    嗯,沒有任何實(shí)數(shù)具有這種屬性,但是沒有任何一種Java數(shù)值類型能夠?qū)?shí)數(shù)進(jìn)行完美建模。浮點(diǎn)數(shù)值是用一個(gè)符號(hào)位、一個(gè)被通俗地稱為尾數(shù)(mantissa)的有效數(shù)字以及一個(gè)指數(shù)來(lái)表示的。除了0之外,沒有任何浮點(diǎn)數(shù)等于其符號(hào)位反轉(zhuǎn)之后的值,因此i的類型必然是整數(shù)型的。
    有符號(hào)的整數(shù)類型使用的是2的補(bǔ)碼算術(shù)運(yùn)算:為了對(duì)一個(gè)數(shù)值取其負(fù)值,你要反轉(zhuǎn)其每一位,然后加1,從而得到結(jié)果[JLS 15.15.4]。2的補(bǔ)碼算術(shù)運(yùn)算的一個(gè)很大的優(yōu)勢(shì)是,0具有的表示形式。如果你要對(duì)int數(shù)值0取負(fù)值,你將得到0xffffffff+1,它仍然是0。
    但是,這也有一個(gè)相應(yīng)的不利之處,總共存在偶數(shù)個(gè)int數(shù)值——準(zhǔn)確地說(shuō)有232個(gè)——其中一個(gè)用來(lái)表示0,這樣就剩些奇數(shù)個(gè)int數(shù)值來(lái)表示正整數(shù)和負(fù)整數(shù),這意味著正的和負(fù)的int數(shù)值的數(shù)量必然不相等。這暗示著至少有一個(gè)int數(shù)值,其負(fù)值不能正確地表示成為一個(gè)int數(shù)值。
    事實(shí)上,恰恰就有一個(gè)這樣的int數(shù)值,它就是Integer.MIN_VALUE,即-231。他的十六進(jìn)制表示是0x80000000。其符號(hào)位為1,其余所有的位都是0。如果我們對(duì)這個(gè)值取負(fù)值,那么我們將得到0x7fffffff+1,也就是0x80000000,即Integer.MIN_VALUE!換句話說(shuō),Integer.MIN_VALUE是它自己的負(fù)值,Long.MIN_VALUE也是一樣。對(duì)這兩個(gè)值取負(fù)值將會(huì)產(chǎn)生溢出,但是Java在整數(shù)計(jì)算中忽略了溢出。其結(jié)果已經(jīng)闡述清楚了,即使它們并不總是你所期望的。
    下面的聲明將使得布爾表達(dá)式(i != 0 && i == -i)的計(jì)算結(jié)果為true,從而使循環(huán)無(wú)限環(huán)繞下去:
    int i = Integer.MIN_VALUE;
    下面這個(gè)也可以:
    long i = Long.MIN_VALUE;
    如果你對(duì)取模運(yùn)算很熟悉,那么很有必要指出,這個(gè)謎題也可以用代數(shù)方法解決。Java的int算術(shù)運(yùn)算是實(shí)際的算術(shù)運(yùn)算對(duì)232取模的運(yùn)算,因此本謎題需要一個(gè)對(duì)這種線性全等的非0解決方案:
    i ≡ -i(mod 232)
    將i加到恒等式的兩邊,我們可以得到:
    2i ≡ 0(mod 32)
    對(duì)這種全等的非0解決方案就是 i = 231。盡管這個(gè)值不能表示成為一個(gè)int,但是它是和-231全等的,即與Integer.MIN_VALUE全等。
    總之,Java使用2的補(bǔ)碼的算術(shù)運(yùn)算,它是非對(duì)稱的。對(duì)于每一種有符號(hào)的整數(shù)類型(int、long、byte和short),負(fù)的數(shù)值總是比正的數(shù)值多一個(gè),這個(gè)多出來(lái)的值總是這種類型所能表示的最小數(shù)值。對(duì)Integer.MIN_VALUE取負(fù)值得到的還是它沒有改變過(guò)的值,Long.MIN_VALUE也是如此。對(duì)Short.MIN_VALUE取負(fù)值并將所產(chǎn)生的int數(shù)值轉(zhuǎn)型回short,返回的同樣是最初的值(Short.MIN_VALUE)。對(duì)Byte.MIN_VALUE來(lái)說(shuō),也會(huì)產(chǎn)生相似的結(jié)果。更一般地講,千萬(wàn)要當(dāng)心溢出:就像狼人一樣,它是個(gè)殺手。
    對(duì)語(yǔ)言設(shè)計(jì)者的教訓(xùn)與謎題26中的教訓(xùn)一樣。應(yīng)該對(duì)某種溢出不會(huì)悄悄發(fā)生的整數(shù)算術(shù)運(yùn)算形式提供語(yǔ)言級(jí)的支持。