第 3 天的問題
被除數表示的是一天里的微秒數;而除數表示的是一天里的毫秒數。這個程序會打印出什么呢?
public class LongDivision{
public static void main(String args[]){
final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;
final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
System.out.println(MICROS_PER_DAY/MILLIS_PER_DAY);
}
}
第 3 天問題的解答
這個題看起來相當直觀。每天的毫秒數和每天的微秒數都是常量。為清楚起見,它們都被表示成積的形式。你可能想都沒想,就脫口而出,結果是1000。 遺憾的是,它打印的是5。這里到底發生了什么呢?
原因
問題在于常數MICROS_PER_DAY的計算"確實"溢出了。盡管計算的結果適合放入long中,并且其空間還有富余,但是這個結果并不適合放入int中。 這個計算完全是以int運算來執行的,并且只有在運算完成之后,其結果才被提升到long,而此時已經太遲了:計算已經溢出了, 它返回的是一個小了200倍的數值。從int提升到long是一種拓寬原始類型轉換(widening primitive conversion), 它保留了(不正確的)數值。這個值之后被MILLIS_PER_DAY整除,而MILLIS_PER_DAY的計算是正確的,因為它適合int運算。這樣整除的結果就得到了5
那么為什么計算會是以int運算來執行的呢?因為所有乘在一起的因子都是int數值。當你將兩個int數值相乘時, 你將得到另一個int數值。Java不具有目標確定類型的特性,這是一種語言特性,其含義是指存儲結果的變量的類型會影響到計算所使用的類型
解決辦法
public class LongDivision{
public static void main(String args[ ]){
final long MICROS_PER_DAY = 24L * 60 * 60 * 1000 * 1000;
final long MILLIS_PER_DAY = 24L * 60 * 60 * 1000;
System.out.println(MICROS_PER_DAY/MILLIS_PER_DAY);
}
}
通過使用long常量來替代int常量作為每一個乘積的第一個因子,我們就可以很容易地訂正這個程序。這樣做可以強制表達式中所有的后續計算都用long運作來完成。 盡管這么做只在MICROS_PER_DAY表達式中是必需的,但是在兩個乘積中都這么做是一種很好的方式。相似地,使用long作為乘積的"第一個"數值也并不總是必需的, 但是這么做也是一種很好的形式。在兩個計算中都以long數值開始可以很清楚地表明它們都不會溢出。下面的程序將打印出我們所期望的1000
第 3 天問題的總結
這個教訓很簡單:當你在操作很大的數字時,千萬要提防溢出--它可是一個緘默殺手。 即使用來保存結果的變量已顯得足夠大,也并不意味著要產生結果的計算具有正確的類型。當你拿不準時,就使用long運算來執行整個計算。
語言設計者從中可以吸取的教訓是:也許降低緘默溢出產生的可能性確實是值得做的一件事。這可以通過對不會產生緘默溢出的運算提供支持來實現。 程序可以拋出一個異常而不是直接溢出,就像Ada所作的那樣,或者它們可以在需要的時候自動地切換到一個更大的內部表示上以防止溢出,就像Lisp所作的那樣。 這兩種方式都可能會遭受與其相關的性能方面的損失。 降低緘默溢出的另一種方式是支持目標確定類型,但是這么做會顯著地增加類型系統的復雜度
今天的問題
它又會打印出什么呢?
public class Elementary{
public static void main(String[] args){
System.out.println(12345+5432l);
}
}
posted on 2008-05-16 23:41
李四飛刀 閱讀(1255)
評論(1) 編輯 收藏 所屬分類:
每日一題