簡體   English   中英

如何在Java中檢測“無符號”長乘法的溢出?

[英]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. ab均為奇數。 如果有人感興趣,我可以添加它。
我已經對整個字符范圍進行了單元測試:0〜0xffff,用於16位數字的溢出,還對一些隨機的long輸入進行了測試,將結果與Nathan方法和BigInteger解決方案進行了比較。

希望能有所幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM