2012-09-01 30 views
24

可能重複:
How can I check if multiplying two numbers in Java will cause an overflow?如何防止Java代碼中的整數溢出?

假設我有一個Java類方法,即用*+操作。

 
int foo(int a, int b) { 
    ... // some calculations with + and * 
} 

如何確保foo沒有發生溢出?

我想我可以用BigDecimal或更換所有+和*與「包裝」,如:

 
int sum(int a, int b) { 
    int c = a + b; 
    if (a > 0 && b > 0 && c < 0) 
    throw new MyOverfowException(a, b) 
    return c; 
} 

int prod(int a, int b) { 
    int c = a * b; 
    if (a > 0 && b > 0 && c < 0) 
    throw new MyOverfowException(a, b) 
    return c; 
} 

是否有更好的方式來確保沒有int溢流出的Java方法發生?

+3

你爲什麼要檢查它是否低於零?一個int可以低於0. – 11684

+0

準確地說,這是一個int的範圍:-2,147,483,648到2,147,483,647 – 11684

+0

你想鉗住這些值嗎? –

回答

17

從工程角度來看,這是一個難題。

Secure Coding網站建議:

  • 使用的前提條件;即範圍檢查輸入,使得不可能發生溢出,使用下一個較大的原始整數類型進行每個單獨的算術運算並明確地檢查溢出,或者使用BigInteger來檢查溢出或

這個Dr Dobbs article建議創建一個基本的算術方法庫,它通過顯式的溢出檢查完成每個基本操作。 (您可以將此視爲上述第2項第2點的實現)。但作者進一步建議您使用字節碼重寫來替代算術字節碼,並調用包含溢出檢查的等效方法。

不幸的是,沒有辦法在Java中本地啓用溢出檢查。(但同樣適用於很多其他語言;例如C,C++ ...)

+0

其中的第二個是我的答案 – Alnitak

+1

「從工程角度來看,這是一個棘手的問題。」 - 這並不難:只需生成機器碼以在每次操作後檢查溢出寄存器標誌。這就是C#「checked」塊的功能。問題在於Java不提供這個選項,並不是因爲它超出了人的智慧。 – Rich

+0

@Rich - 如果您無法修改Java編譯器,則很難。大多數人不是! –

5

總和:檢查b是否大於您可以存儲在int中的最大值的差值減去a的值。如果a和/或b可能是負數,您必須(i)注意不要爲差異檢查產生溢出,並且(ii)對最小值進行類似檢查。

產品:那更困難。我會將整數分成兩個半長整數(即,如果int是32位,則使用位掩碼和移位將它分成兩個16位數)。然後進行乘法運算,然後查看結果是否適合32位。

一切都在你不想簡單地採取long作爲臨時結果的條件下。

20

檢查溢出的一種方法是將操作數提升爲更大類型(原始操作數位長的兩倍),然後執行操作,然後查看結果值是否對於原始類型太大,例如

int sum(int a, int b) { 
    long r = (long)a + b; 
    if (r >>> 32 != 0) { // no sign extension 
     throw new MyOverflowException(a, b); 
    } 
    return (int)r; 
} 

如果你原來的類型是long,你不得不使用BigInteger作爲更大的類型。

+0

我知道這個答案是舊的,但這種解決方案不能保證工作,因爲較大的類型本身可能會溢出並最終出現在對較小類型正常的範圍內。 – yitzih

+0

@yitzih你錯了 - 增加兩個(正數)整數不能超過比最長操作數長1位以上的值。這個問題的限制沒有辦法以「更大的類型本身溢出」結束 – Alnitak

+0

@yitzihI我忘記了也涉及乘法,但是也有兩個31位正整數的乘積不能超過62位。 – Alnitak

3

假設a和b都是正數或負數,並且如果a + b的符號與a和b的符號不相等b,然後發生溢出。您可以使用此規則來判斷是否發生溢出並拋出異常。當你清楚這個表達時,你可以按照之前答案中提到的方法處理它。 另一種方法是使用不會溢出的最大範圍類型進行操作。您可以使用long來進行整數之間的操作。