[英]How to detect overflow of “unsigned” long multiplication in Java?
Java當然沒有“無符號”長值,但有時將有符號長值有效地視為無符號長值(例如, System.nanoTime()
的結果)。 從這個意義上講,算術溢出並不意味着值溢出,而是意味着64位表示形式溢出。 例子:
Long.MAX_VALUE * 2L // overflows the signed product but not the unsigned product
Long.MAX_VALUE * 4L // overflows the signed product and the unsigned product
-1L * 2L // overflows the unsigned product but not the signed product
測試乘法是否溢出似乎有些復雜,因為操作的符號性會妨礙操作。 可能需要注意的是,任何負值乘以0或1以外的任何值都會溢出無符號乘積,因為設置了負值的最高位。
確定兩個“無符號”長值(實際上是有符號長值)的乘積是否會使64位表示形式溢出的最佳方法是什么? 使用BigInteger
實例是一個顯而易見的解決方案,並且我派生了一個僅涉及原始操作的復雜測試,但是我感覺好像缺少了一些顯而易見的東西。
給定兩個我們假裝為無符號長值的有符號長值,這是僅使用有符號原始操作(請原諒腳步)的方法,以確定無符號產品是否會溢出:
boolean unsignedMultiplyOverflows(final long a, final long b) {
if ((a == 0L) || (b == 0L)) {
// Unsigned overflow of a * b will not occur, since the result would be 0.
return false;
}
if ((a == 1L) || (b == 1L)) {
// Unsigned overflow of a * b will not occur, since the result would be a or b.
return false;
}
if ((a < 0L) || (b < 0L)) {
// Unsigned overflow of a * b will occur, since the highest bit of one argument is set, and a bit higher than the lowest bit of the other argument is set.
return true;
}
/*
* 1 < a <= Long.MAX_VALUE
* 1 < b <= Long.MAX_VALUE
*
* Let n == Long.SIZE (> 2), the number of bits of the primitive representation.
* Unsigned overflow of a * b will occur if and only if a * b >= 2^n.
* Each side of the comparison must be re-written such that signed overflow will not occur:
*
* [a.01] a * b >= 2^n
* [a.02] a * b > 2^n - 1
* [a.03] a * b > ((2^(n-1) - 1) * 2) + 1
*
* Let M == Long.MAX_VALUE == 2^(n-1) - 1, and substitute:
*
* [a.04] a * b > (M * 2) + 1
*
* Assume the following identity for non-negative integer X and positive integer Y:
*
* [b.01] X == ((X / Y) * Y) + (X % Y)
*
* Let X == M and Y == b, and substitute:
*
* [b.02] M == ((M / b) * b) + (M % b)
*
* Substitute for M:
*
* [a.04] a * b > (M * 2) + 1
* [a.05] a * b > ((((M / b) * b) + (M % b)) * 2) + 1
* [a.06] a * b > ((M / b) * b * 2) + ((M % b) * 2) + 1
*
* Assume the following identity for non-negative integer X and positive integer Y:
*
* [c.01] X == ((X / Y) * Y) + (X % Y)
*
* Let X == ((M % b) * 2) + 1 and Y == b, and substitute:
*
* [c.02] ((M % b) * 2) + 1 == (((((M % b) * 2) + 1) / b) * b) + ((((M % b) * 2) + 1) % b)
*
* Substitute for ((M % b) * 2) + 1:
*
* [a.06] a * b > ((M / b) * b * 2) + ((M % b) * 2) + 1
* [a.07] a * b > ((M / b) * b * 2) + (((((M % b) * 2) + 1) / b) * b) + ((((M % b) * 2) + 1) % b)
*
* Divide each side by b (// represents real division):
*
* [a.08] (a * b) // b > (((M / b) * b * 2) + (((((M % b) * 2) + 1) / b) * b) + ((((M % b) * 2) + 1) % b)) // b
* [a.09] (a * b) // b > (((M / b) * b * 2) // b) + ((((((M % b) * 2) + 1) / b) * b) // b) + (((((M % b) * 2) + 1) % b) // b)
*
* Reduce each b-divided term that otherwise has a known factor of b:
*
* [a.10] a > ((M / b) * 2) + ((((M % b) * 2) + 1) / b) + (((((M % b) * 2) + 1) % b) // b)
*
* Let c == ((M % b) * 2) + 1), and substitute:
*
* [a.11] a > ((M / b) * 2) + (c / b) + ((c % b) // b)
*
* Assume the following tautology for integers X, Y and real Z such that 0 <= Z < 1:
*
* [d.01] X > Y + Z <==> X > Y
*
* Assume the following tautology for non-negative integer X and positive integer Y:
*
* [e.01] 0 <= (X % Y) // Y < 1
*
* Let X == c and Y == b, and substitute:
*
* [e.02] 0 <= (c % b) // b < 1
*
* Let X == a, Y == ((M / b) * 2) + (c / b), and Z == ((c % b) // b), and substitute:
*
* [d.01] X > Y + Z <==> X > Y
* [d.02] a > ((M / b) * 2) + (c / b) + ((c % b) // b) <==> a > ((M / b) * 2) + (c / b)
*
* Drop the last term of the right-hand side:
*
* [a.11] a > ((M / b) * 2) + (c / b) + ((c % b) // b)
* [a.12] a > ((M / b) * 2) + (c / b)
*
* Substitute for c:
*
* [a.13] a > ((M / b) * 2) + ((((M % b) * 2) + 1) / b)
*
* The first term of the right-hand side is clearly non-negative.
* Determine the upper bound for the first term of the right-hand side (note that the least possible value of b == 2 produces the greatest possible value of (M / b) * 2):
*
* [f.01] (M / b) * 2 <= (M / 2) * 2
*
* Assume the following tautology for odd integer X:
*
* [g.01] (X / 2) * 2 == X - 1
*
* Let X == M and substitute:
*
* [g.02] (M / 2) * 2 == M - 1
*
* Substitute for (M / 2) * 2:
*
* [f.01] (M / b) * 2 <= (M / 2) * 2
* [f.02] (M / b) * 2 <= M - 1
*
* The second term of the right-hand side is clearly non-negative.
* Determine the upper bound for the second term of the right-hand side (note that the <= relation is preserved across positive integer division):
*
* [h.01] M % b < b
* [h.02] M % b <= b - 1
* [h.03] (M % b) * 2 <= (b - 1) * 2
* [h.04] ((M % b) * 2) + 1 <= (b * 2) - 1
* [h.05] (((M % b) * 2) + 1) / b <= ((b * 2) - 1) / b
* [h.06] (((M % b) * 2) + 1) / b <= 1
*
* Since the upper bound of the first term is M - 1, and the upper bound of the second term is 1, the upper bound of the right-hand side is M.
* Each side of the comparison has been re-written such that signed overflow will not occur.
*/
final boolean unsignedMultiplyOverflows = (a > ((Long.MAX_VALUE / b) * 2L) + ((((Long.MAX_VALUE % b) * 2L) + 1L) / b));
return unsignedMultiplyOverflows;
}
如果從確定哪個值較大開始,那一定會有數值的較大和較小,這保證了溢出的發生或不發生。 假設X較大; Y較小。
如果X小於2 ^ 31,或者Y小於2,則不可能發生溢出; 否則,如果X大於2 ^ 62或Y不小於2 ^ 32,則肯定會發生溢出。 如果任一條件適用,則返回。
否則,由於X的下限,已知V =(X >> 31)<< 31在X和X / 2之間。 由於X的上限,V >> 31小於2 ^ 31 Y小於2 ^ 32,因此可以計算T =(V >> 31)* Y(也等於(X >> 31)* Y)溢出。 因為V是2 ^ 31的倍數,所以T也等於(V * Y)>> 31,因此我們知道T在(X * Y)>> 31和(X * Y)>> 32之間。
如果T小於2 ^ 31,則X * Y必須小於2 ^ 63,並且不可能發生溢出。 如果T不小於2 ^ 32,則X * Y必須至少為2 ^ 63,並且一定會發生溢出。
如果兩個條件都不適用,則乘積將在2 ^ 62到2 ^ 64之間。 溢出可以通過直接進行乘法並檢查結果的符號來確定。 與C不同,在Java中,有符號整數溢出會產生不確定的行為,Java保證如果x和y為正且x * y小於2 ^ 64,則算術溢出將產生負結果。
總之,代碼應首先對X和Y進行排名,然后進行四個比較和有條件的返回。 如果它們都不產生確定性結果,則可以計算(X >> 31)* Y並進行兩次以上比較。 如果這些結果沒有確定性結果,則再進行一次乘法運算並測試將得出最終答案,在最壞的情況下,使用八次比較,一次移位和兩次乘法(如果X和Y的等級未知,則添加另一個比較以對其進行排名)。
請注意,如果原始數字可能為負,則需要進行更多檢查以處理一些額外的情況。 盡管如此,上述方法應該比需要一個或多個划分的方法更快。
編輯
正如我對原始帖子的評論之一所承諾的那樣,我現在將發布嚴格而正確的解決方案。 它與Nathan自己的公式相比有較少的划分(對於那些感興趣的人,請參見他的答案的最后代碼行),但是它具有附加的分支,因此我不確定它在性能方面會更好。
而且,可惜的是,它不是單線的。 這里是:
static boolean unsignedMultiplyOverflows(final long a, final long b) {
if (a == 0 || b == 0) {
return false;
}
// now proceed with non-zero input
// we branch based upon parity of a and b
final long aHalf = a >>> 1;
final long bHalf = b >>> 1;
final byte aLastBit = (byte) (a & 1);
final byte bLastBit = (byte) (b & 1);
if (aLastBit == 0) { // a = 2 * aHalf, meaning unsigned representation of a
return Long.MAX_VALUE / b < aHalf;
} else if (bLastBit == 0) { // b = 2 * bHalf
return Long.MAX_VALUE / a < bHalf; // symmetrical to previous case
} else { // a = 2 * aHalf + 1; b = 2 * bHalf + 1
return (Long.MAX_VALUE - bHalf) / b < aHalf;
}
}
形式證明基於對2個案例的調查,即1.乘數中的至少一個是偶數,而2. a
, b
均為奇數。 如果有人感興趣,我可以添加它。
我已經對整個字符范圍進行了單元測試:0〜0xffff,用於16位數字的溢出,還對一些隨機的long
輸入進行了測試,將結果與Nathan方法和BigInteger
解決方案進行了比較。
希望能有所幫助。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.