下面的程序?qū)⑸烧麛?shù)對(duì)3取余的柱狀圖,那么,它將打印出什么呢?
public class Mod {
public static void main(String[ ] args) {
final int MODULUS = 3;
int[] histogram = new int[MODULUS];
// Iterate over all ints (Idiom from Puzzle 26)
int i = Integer.MIN_VALUE;
do {
histogram[Math.abs(i) % MODULUS]++;
} while (i++ != Integer.MAX_VALUE);
for (int j = 0; j < MODULUS; j++)
System.out.println(histogram[j] + " ");
}
}
該程序首先初始化int數(shù)組histogram,其每一個(gè)位置都為對(duì)3取余的一個(gè)數(shù)值而準(zhǔn)備(0、1和2),所有這三個(gè)位置都被初始化為0。然后,該程序在所有232個(gè)int數(shù)值上遍歷變量i,使用的是在謎題26中介紹的慣用法。因?yàn)檎麛?shù)取余操作(%)在第一個(gè)操作數(shù)是負(fù)數(shù)時(shí),可以返回一個(gè)負(fù)值,就像在謎題1中所描述的那樣,所以該程序在計(jì)算i被3整除的余數(shù)之前,先取i的絕對(duì)值。然后用這個(gè)余數(shù)來(lái)遞增數(shù)組位置的索引。在循環(huán)完成之后,該程序?qū)⒋蛴istogram數(shù)組中的內(nèi)容,它的元素表示對(duì)3取余得到0、1和2的int數(shù)值的個(gè)數(shù)。
該程序所打印的三個(gè)數(shù)字應(yīng)該彼此大致相等,它們加起來(lái)應(yīng)該等于232。如果你想知道怎樣計(jì)算出它們的精確值,那么你需要有一點(diǎn)數(shù)學(xué)氣質(zhì),并仔細(xì)閱讀下面兩段話。否則,你可以跳過(guò)這兩段話。
該程序打印的三個(gè)數(shù)字不可能精確地相等,因?yàn)樗鼈儽仨毤悠饋?lái)等于232,這個(gè)數(shù)字不能被3除盡。如果你仔細(xì)觀察2的連續(xù)冪級(jí)數(shù)對(duì)3取余的值,就會(huì)發(fā)現(xiàn),它們?cè)?和2之間交替變化:20對(duì)3取余是1,21對(duì)3取余是2,22對(duì)3取余是1,23對(duì)3取余是2,以此類推。每一個(gè)2的偶次冪對(duì)3取余的值都是1,每一個(gè)2的奇次冪對(duì)3取余的值都是2。因?yàn)?32對(duì)3取余是1,所以該程序所打印的三個(gè)數(shù)字中有一個(gè)將比另外兩個(gè)大1,但是它是哪一個(gè)呢?
該循環(huán)依次遞增三個(gè)數(shù)組元素的數(shù)值,因此該循環(huán)最后遞增的那個(gè)數(shù)值必然是的數(shù)值,它就是表示Integer.MAX_VALUE或(232-1)對(duì)3取余的數(shù)值。因?yàn)?31是2 的奇次冪,所以它對(duì)3取余應(yīng)該得到2,因此(232-1)對(duì)3取余將得到1。該程序打印的三個(gè)數(shù)字中的第二個(gè)表示的就是對(duì)3取余得到1的int數(shù)值的個(gè)數(shù),因此,我們期望這個(gè)值比第一個(gè)和最后一個(gè)數(shù)值大1。
由此,該程序應(yīng)該在運(yùn)行了相當(dāng)長(zhǎng)的時(shí)間之后,打印(232/3)的較小值 (232/3)的較大值 (232/3)的較小值,即1431655765 1431655766 1431655765。但是它真的是這么做的嗎?不,它幾乎立刻就拋出了下面的異常:
Exception in thread "main" ArrayIndexOutOfBoundsException: -2
at Mod.main(Mod.java:9)
問(wèn)題出在哪了呢?
問(wèn)題在于該程序?qū)ath.abs方法的使用上,它會(huì)導(dǎo)致錯(cuò)誤的對(duì)3取余的數(shù)值??紤]一下當(dāng)i為 -2 時(shí)所發(fā)生的事情,該程序計(jì)算 Math.abs(-2) % 3的數(shù)值,得到2,但是-2對(duì)3取余應(yīng)該得到1。這可以解釋為什么產(chǎn)生了不正確的統(tǒng)計(jì)結(jié)果,但是還有一個(gè)問(wèn)題留待解決,為什么程序拋出了ArrayIndexOutOfBoundsException異常呢?這個(gè)異常表明該程序使用了一個(gè)負(fù)的數(shù)組索引,但是這肯定是不可能的:數(shù)組索引是通過(guò)的接受i的絕對(duì)值并計(jì)算這個(gè)絕對(duì)值被3整除時(shí)的余數(shù)而計(jì)算出來(lái)的。在計(jì)算一個(gè)非負(fù)的int數(shù)值整除一個(gè)正的int數(shù)值的余數(shù)時(shí),可以保證將產(chǎn)生一個(gè)非負(fù)的結(jié)果[JLS 15.17.3]。我們又要問(wèn)了,這里又出了什么問(wèn)題呢?
要回答這個(gè)問(wèn)題,我們必須要去看看Math.abs的文檔。這個(gè)方法的名字有一點(diǎn)帶有欺騙性,它幾乎總是返回它的參數(shù)的絕對(duì)值,但是在有一種情況下,它做不到這一點(diǎn)。文檔中敘述道:“如果其參數(shù)等于Integer.MIN_VALUE,那么產(chǎn)生的結(jié)果與該參數(shù)相同,它是一個(gè)負(fù)數(shù)?!蓖ㄟ^(guò)對(duì)這條知識(shí)的掌握,就可以很清楚地知道為什么該程序立即拋出了ArrayIndexOutOfBoundsException異常。循環(huán)索引i的初始值是Integer.MIN_VALUE,由Math.abs(Integer.MIN_VALUE) % 3所產(chǎn)生的數(shù)組索引等于Integer.MIN_VALUE % 3,即 -2。
為了訂正這個(gè)程序,我們必須用一個(gè)真正的取余操作來(lái)替代偽取余計(jì)算(Math.abs(i) % MODULUS)。如果我們將這個(gè)表達(dá)式替換為對(duì)下面這個(gè)方法的調(diào)用,那么該程序就可以產(chǎn)生我們做期望的輸出1431655765 1431655766 1431655765
public class Mod {
public static void main(String[ ] args) {
final int MODULUS = 3;
int[] histogram = new int[MODULUS];
// Iterate over all ints (Idiom from Puzzle 26)
int i = Integer.MIN_VALUE;
do {
histogram[Math.abs(i) % MODULUS]++;
} while (i++ != Integer.MAX_VALUE);
for (int j = 0; j < MODULUS; j++)
System.out.println(histogram[j] + " ");
}
}
該程序首先初始化int數(shù)組histogram,其每一個(gè)位置都為對(duì)3取余的一個(gè)數(shù)值而準(zhǔn)備(0、1和2),所有這三個(gè)位置都被初始化為0。然后,該程序在所有232個(gè)int數(shù)值上遍歷變量i,使用的是在謎題26中介紹的慣用法。因?yàn)檎麛?shù)取余操作(%)在第一個(gè)操作數(shù)是負(fù)數(shù)時(shí),可以返回一個(gè)負(fù)值,就像在謎題1中所描述的那樣,所以該程序在計(jì)算i被3整除的余數(shù)之前,先取i的絕對(duì)值。然后用這個(gè)余數(shù)來(lái)遞增數(shù)組位置的索引。在循環(huán)完成之后,該程序?qū)⒋蛴istogram數(shù)組中的內(nèi)容,它的元素表示對(duì)3取余得到0、1和2的int數(shù)值的個(gè)數(shù)。
該程序所打印的三個(gè)數(shù)字應(yīng)該彼此大致相等,它們加起來(lái)應(yīng)該等于232。如果你想知道怎樣計(jì)算出它們的精確值,那么你需要有一點(diǎn)數(shù)學(xué)氣質(zhì),并仔細(xì)閱讀下面兩段話。否則,你可以跳過(guò)這兩段話。
該程序打印的三個(gè)數(shù)字不可能精確地相等,因?yàn)樗鼈儽仨毤悠饋?lái)等于232,這個(gè)數(shù)字不能被3除盡。如果你仔細(xì)觀察2的連續(xù)冪級(jí)數(shù)對(duì)3取余的值,就會(huì)發(fā)現(xiàn),它們?cè)?和2之間交替變化:20對(duì)3取余是1,21對(duì)3取余是2,22對(duì)3取余是1,23對(duì)3取余是2,以此類推。每一個(gè)2的偶次冪對(duì)3取余的值都是1,每一個(gè)2的奇次冪對(duì)3取余的值都是2。因?yàn)?32對(duì)3取余是1,所以該程序所打印的三個(gè)數(shù)字中有一個(gè)將比另外兩個(gè)大1,但是它是哪一個(gè)呢?
該循環(huán)依次遞增三個(gè)數(shù)組元素的數(shù)值,因此該循環(huán)最后遞增的那個(gè)數(shù)值必然是的數(shù)值,它就是表示Integer.MAX_VALUE或(232-1)對(duì)3取余的數(shù)值。因?yàn)?31是2 的奇次冪,所以它對(duì)3取余應(yīng)該得到2,因此(232-1)對(duì)3取余將得到1。該程序打印的三個(gè)數(shù)字中的第二個(gè)表示的就是對(duì)3取余得到1的int數(shù)值的個(gè)數(shù),因此,我們期望這個(gè)值比第一個(gè)和最后一個(gè)數(shù)值大1。
由此,該程序應(yīng)該在運(yùn)行了相當(dāng)長(zhǎng)的時(shí)間之后,打印(232/3)的較小值 (232/3)的較大值 (232/3)的較小值,即1431655765 1431655766 1431655765。但是它真的是這么做的嗎?不,它幾乎立刻就拋出了下面的異常:
Exception in thread "main" ArrayIndexOutOfBoundsException: -2
at Mod.main(Mod.java:9)
問(wèn)題出在哪了呢?
問(wèn)題在于該程序?qū)ath.abs方法的使用上,它會(huì)導(dǎo)致錯(cuò)誤的對(duì)3取余的數(shù)值??紤]一下當(dāng)i為 -2 時(shí)所發(fā)生的事情,該程序計(jì)算 Math.abs(-2) % 3的數(shù)值,得到2,但是-2對(duì)3取余應(yīng)該得到1。這可以解釋為什么產(chǎn)生了不正確的統(tǒng)計(jì)結(jié)果,但是還有一個(gè)問(wèn)題留待解決,為什么程序拋出了ArrayIndexOutOfBoundsException異常呢?這個(gè)異常表明該程序使用了一個(gè)負(fù)的數(shù)組索引,但是這肯定是不可能的:數(shù)組索引是通過(guò)的接受i的絕對(duì)值并計(jì)算這個(gè)絕對(duì)值被3整除時(shí)的余數(shù)而計(jì)算出來(lái)的。在計(jì)算一個(gè)非負(fù)的int數(shù)值整除一個(gè)正的int數(shù)值的余數(shù)時(shí),可以保證將產(chǎn)生一個(gè)非負(fù)的結(jié)果[JLS 15.17.3]。我們又要問(wèn)了,這里又出了什么問(wèn)題呢?
要回答這個(gè)問(wèn)題,我們必須要去看看Math.abs的文檔。這個(gè)方法的名字有一點(diǎn)帶有欺騙性,它幾乎總是返回它的參數(shù)的絕對(duì)值,但是在有一種情況下,它做不到這一點(diǎn)。文檔中敘述道:“如果其參數(shù)等于Integer.MIN_VALUE,那么產(chǎn)生的結(jié)果與該參數(shù)相同,它是一個(gè)負(fù)數(shù)?!蓖ㄟ^(guò)對(duì)這條知識(shí)的掌握,就可以很清楚地知道為什么該程序立即拋出了ArrayIndexOutOfBoundsException異常。循環(huán)索引i的初始值是Integer.MIN_VALUE,由Math.abs(Integer.MIN_VALUE) % 3所產(chǎn)生的數(shù)組索引等于Integer.MIN_VALUE % 3,即 -2。
為了訂正這個(gè)程序,我們必須用一個(gè)真正的取余操作來(lái)替代偽取余計(jì)算(Math.abs(i) % MODULUS)。如果我們將這個(gè)表達(dá)式替換為對(duì)下面這個(gè)方法的調(diào)用,那么該程序就可以產(chǎn)生我們做期望的輸出1431655765 1431655766 1431655765