How to avoid overflow in expr. A * B - C * D

后端 未结 15 999
萌比男神i
萌比男神i 2021-01-29 18:18

I need to compute an expression which looks like: A*B - C*D, where their types are: signed long long int A, B, C, D; Each number can be really big (not

相关标签:
15条回答
  • 2021-01-29 18:55

    The simplest and most general solution is to use a representation that can't overflow, either by using a long integer library (e.g. http://gmplib.org/) or representing using a struct or array and implementing a kind of long multiplication (i.e. separating each number to two 32bit halves and performing the multiplication as below:

    (R1 + R2 * 2^32 + R3 * 2^64 + R4 * 2^96) = R = A*B = (A1 + A2 * 2^32) * (B1 + B2 * 2^32) 
    R1 = (A1*B1) % 2^32
    R2 = ((A1*B1) / 2^32 + (A1*B2) % 2^32 + (A2*B1) % 2^32) % 2^32
    R3 = (((A1*B1) / 2^32 + (A1*B2) % 2^32 + (A2*B1) % 2^32) / 2^32 + (A1*B2) / 2^32 + (A2*B1) / 2^32 + (A2*B2) % 2^32) %2^32
    R4 = ((((A1*B1) / 2^32 + (A1*B2) % 2^32 + (A2*B1) % 2^32) / 2^32 + (A1*B2) / 2^32 + (A2*B1) / 2^32 + (A2*B2) % 2^32) / 2^32) + (A2*B2) / 2^32
    

    Assuming the end result fits in 64 bits you actually don't really need most bits of R3 and none of R4

    0 讨论(0)
  • 2021-01-29 18:56

    For the sake of completeness, since no one mentioned it, some compilers (e.g. GCC) actually provide you with a 128 bit integer nowadays.

    Thus an easy solution could be:

    (long long)((__int128)A * B - (__int128)C * D)
    
    0 讨论(0)
  • 2021-01-29 18:58

    If you know the final result is representable in your integer type, you can perform this calculation quickly using the code below. Because the C standard specifies that unsigned arithmetic is modulo arithmetic and does not overflow, you can use an unsigned type to perform the calculation.

    The following code assumes there is an unsigned type of the same width and that the signed type uses all bit patterns to represent values (no trap representations, the minimum of the signed type is the negative of half the modulus of the unsigned type). If this does not hold in a C implementation, simple adjustments can be made to the ConvertToSigned routine for that.

    The following uses signed char and unsigned char to demonstrate the code. For your implementation, change the definition of Signed to typedef signed long long int Signed; and the definition of Unsigned to typedef unsigned long long int Unsigned;.

    #include <limits.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    
    //  Define the signed and unsigned types we wish to use.
    typedef signed char   Signed;
    typedef unsigned char Unsigned;
    
    //  uHalfModulus is half the modulus of the unsigned type.
    static const Unsigned uHalfModulus = UCHAR_MAX/2+1;
    
    //  sHalfModulus is the negation of half the modulus of the unsigned type.
    static const Signed   sHalfModulus = -1 - (Signed) (UCHAR_MAX/2);
    
    
    /*  Map the unsigned value to the signed value that is the same modulo the
        modulus of the unsigned type.  If the input x maps to a positive value, we
        simply return x.  If it maps to a negative value, we return x minus the
        modulus of the unsigned type.
    
        In most C implementations, this routine could simply be "return x;".
        However, this version uses several steps to convert x to a negative value
        so that overflow is avoided.
    */
    static Signed ConvertToSigned(Unsigned x)
    {
        /*  If x is representable in the signed type, return it.  (In some
            implementations, 
        */
        if (x < uHalfModulus)
            return x;
    
        /*  Otherwise, return x minus the modulus of the unsigned type, taking
            care not to overflow the signed type.
        */
        return (Signed) (x - uHalfModulus) - sHalfModulus;
    }
    
    
    /*  Calculate A*B - C*D given that the result is representable as a Signed
        value.
    */
    static signed char Calculate(Signed A, Signed B, Signed C, Signed D)
    {
        /*  Map signed values to unsigned values.  Positive values are unaltered.
            Negative values have the modulus of the unsigned type added.  Because
            we do modulo arithmetic below, adding the modulus does not change the
            final result.
        */
        Unsigned a = A;
        Unsigned b = B;
        Unsigned c = C;
        Unsigned d = D;
    
        //  Calculate with modulo arithmetic.
        Unsigned t = a*b - c*d;
    
        //  Map the unsigned value to the corresponding signed value.
        return ConvertToSigned(t);
    }
    
    
    int main()
    {
        //  Test every combination of inputs for signed char.
        for (int A = SCHAR_MIN; A <= SCHAR_MAX; ++A)
        for (int B = SCHAR_MIN; B <= SCHAR_MAX; ++B)
        for (int C = SCHAR_MIN; C <= SCHAR_MAX; ++C)
        for (int D = SCHAR_MIN; D <= SCHAR_MAX; ++D)
        {
            //  Use int to calculate the expected result.
            int t0 = A*B - C*D;
    
            //  If the result is not representable in signed char, skip this case.
            if (t0 < SCHAR_MIN || SCHAR_MAX < t0)
                continue;
    
            //  Calculate the result with the sample code.
            int t1 = Calculate(A, B, C, D);
    
            //  Test the result for errors.
            if (t0 != t1)
            {
                printf("%d*%d - %d*%d = %d, but %d was returned.\n",
                    A, B, C, D, t0, t1);
                exit(EXIT_FAILURE);
            }
        }
        return 0;
    }
    
    0 讨论(0)
  • 2021-01-29 19:01

    You could try breaking the equation into smaller components which don't overflow.

    AB - CD
    = [ A(B - N) - C( D - M )] + [AN - CM]
    
    = ( AK - CJ ) + ( AN - CM)
    
        where K = B - N
              J = D - M
    

    If the components still overflow you could break them into smaller components recursively and then recombine.

    0 讨论(0)
  • 2021-01-29 19:04

    This should work ( I think ):

    signed long long int a = 0x7ffffffffffffffd;
    signed long long int b = 0x7ffffffffffffffd;
    signed long long int c = 0x7ffffffffffffffc;
    signed long long int d = 0x7ffffffffffffffe;
    signed long long int bd = b / d;
    signed long long int bdmod = b % d;
    signed long long int ca = c / a;
    signed long long int camod = c % a;
    signed long long int x = (bd - ca) * a * d - (camod * d - bdmod * a);
    

    Here's my derivation:

    x = a * b - c * d
    x / (a * d) = (a * b - c * d) / (a * d)
    x / (a * d) = b / d - c / a
    
    now, the integer/mod stuff:
    x / (a * d) = (b / d + ( b % d ) / d) - (c / a + ( c % a ) / a )
    x / (a * d) = (b / d - c / a) - ( ( c % a ) / a - ( b % d ) / d)
    x = (b / d - c / a) * a * d - ( ( c % a ) * d - ( b % d ) * a)
    
    0 讨论(0)
  • 2021-01-29 19:04

    You could consider computing a greatest common factor for all your values, and then dividing them by that factor before doing your arithmetic operations, then multiplying again. This assumes that such a factor exists, however (for example, if A, B, C and D happen to be relatively prime, they won't have a common factor).

    Similarly, you could consider working on log-scales, but this is going to be a little scary, subject to numerical precision.

    0 讨论(0)
提交回复
热议问题