detecting multiplication of uint64_t integers overflow with C

前端 未结 5 1160
天涯浪人
天涯浪人 2021-02-07 08:11

Is there any efficient and portable way to check when multiplication operations with int64_t or uint64_t operands overflow in C?

For instance, for addition of uint64_t I

相关标签:
5条回答
  • 2021-02-07 08:35

    Actually, the same principle can be used for multiplication:

    uint64_t a;
    uint64_t b;
    ...
    if (b != 0 && a > UINT64_MAX / b) { // if you multiply by b, you get: a * b > UINT64_MAX
        < error >
    }
    uint64_t c = a * b;
    

    For signed integers similar can be done, you'd probably need a case for each combination of signs.

    0 讨论(0)
  • 2021-02-07 08:41

    It might not detect exact overflows, but in general you can test the result of your multiplication on a logarithmic scale:

    if (log(UINT64_MAX-1) - log(a) - log(b) < 0) overflow_detected(); // subtracting 1 to allow some tolerance when the numbers are converted to double
    else prod = a * b;
    

    It depends if you really need to do multiplication up to exact UINT64_MAX, otherwise this a very portable and convenient way to check multiplications of large numbers.

    0 讨论(0)
  • 2021-02-07 08:42

    If you want to avoid division as in Ambroz' answer:

    First you have to see that the smaller of the two numbers, say a, is less than 232, otherwise the result will overflow anyhow. Let b be decomposed into the two 32 bit words that is b = c 232 + d.

    The computation then is not so difficult, I find:

    uint64_t mult_with_overflow_check(uint64_t a, uint64_t b) {
      if (a > b) return mult_with_overflow_check(b, a);
      if (a > UINT32_MAX) overflow();
      uint32_t c = b >> 32;
      uint32_t d = UINT32_MAX & b;
      uint64_t r = a * c;
      uint64_t s = a * d;
      if (r > UINT32_MAX) overflow();
      r <<= 32;
      return addition_with_overflow_check(s, r);
    }
    

    so this are two multiplications, two shifts, some additions and condition checks. This could be more efficient than the division because e.g the two multiplications can be pipelined in paralle. You'd have to benchmark to see what works better for you.

    0 讨论(0)
  • 2021-02-07 08:48

    Related question with some (hopefully) useful answers: Best way to detect integer overflow in C/C++. Plus it not covers uint64_t only ;)

    0 讨论(0)
  • 2021-02-07 08:56
    case 6:
        for (a = 0; a < N; a++) {
            uint64_t b = a + c;
            uint64_t a1, b1;
            if (a > b) { a1 = a; b1 = b; }
            else       { a1 = b; b1 = a; }
            uint64_t cc = b1 * a1;
            c += cc;
            if (b1 > 0xffffffff) o++;
            else {
                uint64_t a1l = (a1 & 0xffffffff) + (a1 >> 32);
                a1l = (a1 + (a1 >> 32)) & 0xffffffff;
                uint64_t ab1l = a1l * b1;
                ab1l = (ab1l & 0xffffffff) + (ab1l >> 32);
                ab1l += (ab1l >> 32);
                uint64_t ccl = (cc & 0xffffffff) + (cc >> 32);
                ccl += (ccl >> 32);
                uint32_t ab32 = ab1l; if (ab32 == 0xffffffff) ab32 = 0;
                uint32_t cc32 = ccl; if (cc32 == 0xffffffff) cc32 = 0;
                if (ab32 != cc32) o++;
            }
        }
        break;
    

    This method compares (possibly overflowing) result of normal multiplication with the result of multiplication, which cannot overflow. All calculations are modulo (2^32 - 1).

    It is more complicated and (most likely) not faster than Jens Gustedt's method.

    After some small modifications it may multiply with 96-bit precision (but without overflow control). What may be more interesting, the idea of this method may be used to check overflow for a series of arithmetic operations (multiplications, additions, subtractions).

    Some questions answered

    First of all, about "your code is not portable". Yes, code is not portable because it uses uint64_t, which is requested in the original question. Strictly speaking, you cannot get any portable answer with (u)int64_t because it is not required by standard.

    About "once some overflow happens, you can not assume the result value to be anything". Standard says that unsigned itegers cannot overflow. Chapter 6.2.5, item 9:

    A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type.

    So unsigned 64-bit multiplication is performed modulo 2^64, without overflow.

    Now about the "logic behind". "Hash function" is not the correct word here. I only use calculations modulo (2^32 - 1). The result of multiplication may be represented as n*2^64 + m, where m is the visible result, and n means how much we overflow. Since 2^64 = 1 (mod 2^32 - 1), we may calculate [true value] - [visible value] = (n*2^64 + m) - m = n*2^64 = n (mod 2^32 - 1). If calculated value of n is not zero, there is an overflow. If it is zero, there is no overflow. Any collisions are possible only after n >= 2^32 - 1. This will never happen since we check that one of the multiplicands is less than 2^32.

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