94

I am looking for an efficient (optionally standard, elegant and easy to implement) solution to multiply relatively large numbers, and store the result into one or several integers :

Let say I have two 64 bits integers declared like this :

uint64_t a = xxx, b = yyy; 

When I do a * b, how can I detect if the operation results in an overflow and in this case store the carry somewhere?

Please note that I don't want to use any large-number library since I have constraints on the way I store the numbers.

Michele Dorigatti
  • 811
  • 1
  • 9
  • 17
Ben
  • 7,372
  • 8
  • 38
  • 46
  • 3
    Strictly from C standard text, the unsigned integer multiplication cannot overflow, but it can wrap around. The behaviour of signed integer overflow is undefined. There are answers to this question that strictly assume that the operands are unsigned, and cannot be used as such for signed integers. – Antti Haapala -- Слава Україні Jun 08 '19 at 15:38
  • The signed integer wraps around as well from what I know – H-005 Jul 21 '20 at 08:58
  • 2
    @H-005: The assembly instructions the compiler generates for signed integer multiplication might wrap around, but the optimizer in your compiler almost certainly assumes no overflow happens and optimizes accordingly. This means your program might do anything if you encounter signed overflow, because signed overflow is undefined behavior. – David Stone Jun 12 '21 at 19:20

14 Answers14

112

1. Detecting the overflow:

x = a * b;
if (a != 0 && x / a != b) {
    // overflow handling
}

Edit: Fixed division by 0 (thanks Mark!)

2. Computing the carry is quite involved. One approach is to split both operands into half-words, then apply long multiplication to the half-words:

uint64_t hi(uint64_t x) {
    return x >> 32;
}

uint64_t lo(uint64_t x) {
    return ((1ULL << 32) - 1) & x;
}

void multiply(uint64_t a, uint64_t b) {
    // actually uint32_t would do, but the casting is annoying
    uint64_t s0, s1, s2, s3; 
    
    uint64_t x = lo(a) * lo(b);
    s0 = lo(x);
    
    x = hi(a) * lo(b) + hi(x);
    s1 = lo(x);
    s2 = hi(x);
    
    x = s1 + lo(a) * hi(b);
    s1 = lo(x);
    
    x = s2 + hi(a) * hi(b) + hi(x);
    s2 = lo(x);
    s3 = hi(x);
    
    uint64_t result = s1 << 32 | s0;
    uint64_t carry = s3 << 32 | s2;
}

To see that none of the partial sums themselves can overflow, we consider the worst case:

        x = s2 + hi(a) * hi(b) + hi(x)

Let B = 1 << 32. We then have

            x <= (B - 1) + (B - 1)(B - 1) + (B - 1)
              <= B*B - 1
               < B*B

I believe this will work - at least it handles Sjlver's test case. Aside from that, it is untested (and might not even compile, as I don't have a C++ compiler at hand anymore).

meriton
  • 68,356
  • 14
  • 108
  • 175
  • 11
    caf’s comment is incorrect. The C99 standard mandates that “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.” As such, meriton’s solution is valid in theory as well as practice. – Jens Ayton Nov 30 '09 at 07:34
  • 4
    Are you absolutely sure this is correct? Consider a = 7 and b = 613612691. This computation overflows (for 32 bits), but the carry (according to you) is zero. Sorry to come back to this after more than three years... but it would be sad if StackOverflow had a wrong accepted answer. – Sjlver Feb 18 '13 at 11:27
  • Nice catch - indeed the carry of the "lower" multiplication may not be neglegted. I *think* it is correct now - at least your test case passes. – meriton Feb 18 '13 at 20:54
  • What does the `>>>` construct do? Neither `>> >` nor `> >>` makes sense... I guess it's a typo? – Ruslan Aug 03 '15 at 11:52
  • 2
    Fixed. (I code a lot of Java, where >> is the right shift operator with sign extension, and >>> the right shift operator without sign extension. In C, there is only >>, and sign extension presumably depends on the the signedness of the integer inputs). – meriton Aug 03 '15 at 16:13
  • Why am I getting `warning: left shift count >= width of type` for `((1 << 32) - 1) & x`? – David G Aug 23 '16 at 01:07
  • Likely because it interprets 1 as 32 bit literal, rather than a 64 bit literal. My edit should fix that. – meriton Aug 23 '16 at 17:34
  • 1
    Why and how to prove that `a * b / a != b` captures all cases of overflow? – Weishi Z Apr 10 '17 at 00:28
  • 2
    Assume that `a * b` overflows. Then, `a * b ≤ ab - 2^64` and therefore `a * b / a ≤ ab/a - (2^64/a) < b`. – meriton Apr 10 '17 at 00:43
  • Found a similar answer which extends the multiplication to signed integers also: https://stackoverflow.com/a/22847373/2430597 – plasmacel Jun 25 '18 at 16:17
  • 1
    @JensAyton: So you found what the standard says about unsigned overflow. But caf's comment was about signed overflow. – Sebastian Mach Apr 01 '19 at 11:26
  • That’s true, but there’s no signed arithmetic in this answer. – Jens Ayton Apr 10 '19 at 12:39
  • What's the purpose of the last two assignments of `s3` and `s2`? It seems like you can just assign `x` at that point to the carry. – tay10r Aug 06 '19 at 22:34
  • 1
    Undefined behavior on any platform where a `long` is 32 bits (which is pretty common): `(1L << 32)` should be replaced with `(1ULL << 32)`. In C, bit shifts are undefined when the shift amount is the entire width of the integer or bigger. As a secondary issue, in C, left-shifting a signed integer (`1L` has signed type) is very under-defined (it is "defined", but the numerical value of the result is not, and thus the `- 1` and then the `& x` done after also have results with undefined values). It works on modern systems, but using `1ULL` instead of `1L` robustly avoids both issues. – mtraceur Jan 01 '20 at 21:13
  • I checked in C# and brute force that the same algorithm for overflow detection works when using 16-bit signed integers (i.e. Int16) (takes about 5 minutes). This is evidence towards the algorithm also working for signed 64-bit integers (proof would be nice). – kaba Jun 18 '22 at 02:31
49

The idea is to use following fact which is true for integral operation:

a*b > c if and only if a > c/b

/ is integral division here.

The pseudocode to check against overflow for positive numbers follows:

if (a > max_int64 / b) then "overflow" else "ok".

To handle zeroes and negative numbers you should add more checks.

C code for non-negative a and b follows:

if (b > 0 && a > 18446744073709551615 / b) {
     // overflow handling
}; else {
    c = a * b;
}

Note, max value for 64 type:

18446744073709551615 == (1<<64)-1

To calculate carry we can use approach to split number into two 32-digits and multiply them as we do this on the paper. We need to split numbers to avoid overflow.

Code follows:

// split input numbers into 32-bit digits
uint64_t a0 = a & ((1LL<<32)-1);
uint64_t a1 = a >> 32;
uint64_t b0 = b & ((1LL<<32)-1);
uint64_t b1 = b >> 32;


// The following 3 lines of code is to calculate the carry of d1
// (d1 - 32-bit second digit of result, and it can be calculated as d1=d11+d12),
// but to avoid overflow.
// Actually rewriting the following 2 lines:
// uint64_t d1 = (a0 * b0 >> 32) + a1 * b0 + a0 * b1;
// uint64_t c1 = d1 >> 32;
uint64_t d11 = a1 * b0 + (a0 * b0 >> 32); 
uint64_t d12 = a0 * b1;
uint64_t c1 = (d11 > 18446744073709551615 - d12) ? 1 : 0;

uint64_t d2 = a1 * b1 + c1;
uint64_t carry = d2; // needed carry stored here
sergtk
  • 10,714
  • 15
  • 75
  • 130
  • If your going to need the carry anyway (or need it enough of the time) you might as well just compute it and check for non-zero. Many 32bit system will implement a 64bit multiplication as a slightly trimmed version of it anyway so with a few short-stop checks it might be only a little slower than the direct multiplication. – BCS Jul 12 '10 at 00:41
  • `c1 = (d11 > 18446744073709551615 - d12) ? 1 : 0;` generates the wrong carry. Need something like `c1 = d11 + d12; if (c1 < d11) c1 = c1 >> 32 + 0x100000000u; else c1 >>= 32;` – chux - Reinstate Monica Dec 19 '14 at 17:48
  • @chux-ReinstateMonica `d11 > 18446744073709551615 - d12` is exactly to avoid overflow. If overflow may happen, then overflow means carry is 1. When trying direct comparison `(d11+d12 > 18446744073709551615)`, this is what can make overflow, and we don't want it. And overflow actually means carry here. It whould be good if you provide exact values when carry is wrong – sergtk Feb 08 '20 at 16:54
  • @sergtk I'll review my 5 yr old concern more later. Note: 18446744073709551615 is a problematic as it is likely not in the `long long` range - as needed for portable code for a _decimal_ constant - w/o a `u`. `18446744073709551615u` would be better. `18446744073709551615` is also a naked magic number. `UINT64_MAX` would solve both prior issues and convey code's intent better. – chux - Reinstate Monica Feb 08 '20 at 20:46
  • @chux-ReinstateMonica Since people continue to upvote the answer after a long period of time, I decided to review comments. The only thing I was worried about is producing wrong result, e.g. on border cases or something like this. Concerning your comment. Since 18446744073709551615 is not in 'long long range', it will be in 'unsigned long long'. I could add 'u' and UINT64_MAX, it looks good in the end code, but this answer is not about copy/paste piece of code. 18446744073709551615 is described in the answer, this answer is not about concerning code style or something like this. – sergtk Feb 09 '20 at 13:14
  • "Since 18446744073709551615 is not in 'long long range', it will be in 'unsigned long long'." is not supported since C99 when `(unsigned) long long` was introduced - that is not a style issue. C99 §6.4.4.1 5. – chux - Reinstate Monica Feb 09 '20 at 17:27
  • Going forward, when compilers use wider than 64-bit `long long`, `18446744073709551615` could be a `int128_t`. Although this does not change the correctness of the code, it does unnecessarily invoke wider math - hopefully which will get optimized out anyways. – chux - Reinstate Monica Feb 09 '20 at 17:31
  • "18446744073709551615 is described in the answer" --> `18446744073709551615 == (1<<64)-1` is true conceptually, but not as C code suffering from `int` overflow. IAC , code speaks louder the non-code comments. – chux - Reinstate Monica Feb 09 '20 at 17:34
  • All-in -all the algorithm here is correct, yet weaknesses include type issues mentioned above that could generate non-working code or inefficiencies. The style issue of 18446744073709551615u versus `UINT64_MAX` impacts review efficiency. – chux - Reinstate Monica Feb 09 '20 at 17:38
34

Although there have been several other answers to this question, I several of them have code that is completely untested, and thus far no one has adequately compared the different possible options.

For that reason, I wrote and tested several possible implementations (the last one is based on this code from OpenBSD, discussed on Reddit here). Here's the code:

/* Multiply with overflow checking, emulating clang's builtin function
 *
 *     __builtin_umull_overflow
 *
 * This code benchmarks five possible schemes for doing so.
 */

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <limits.h>

#ifndef BOOL
    #define BOOL int
#endif

// Option 1, check for overflow a wider type
//    - Often fastest and the least code, especially on modern compilers
//    - When long is a 64-bit int, requires compiler support for 128-bits
//      ints (requires GCC >= 3.0 or Clang)

#if LONG_BIT > 32
    typedef __uint128_t long_overflow_t ;
#else
    typedef uint64_t long_overflow_t;
#endif

BOOL 
umull_overflow1(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        long_overflow_t prod = (long_overflow_t)lhs * (long_overflow_t)rhs;
        *result = (unsigned long) prod;
        return (prod >> LONG_BIT) != 0;
}

// Option 2, perform long multiplication using a smaller type
//    - Sometimes the fastest (e.g., when mulitply on longs is a library
//      call).
//    - Performs at most three multiplies, and sometimes only performs one.
//    - Highly portable code; works no matter how many bits unsigned long is

BOOL 
umull_overflow2(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        const unsigned long HALFSIZE_MAX = (1ul << LONG_BIT/2) - 1ul;
        unsigned long lhs_high = lhs >> LONG_BIT/2;
        unsigned long lhs_low  = lhs & HALFSIZE_MAX;
        unsigned long rhs_high = rhs >> LONG_BIT/2;
        unsigned long rhs_low  = rhs & HALFSIZE_MAX;

        unsigned long bot_bits = lhs_low * rhs_low;
        if (!(lhs_high || rhs_high)) {
            *result = bot_bits;
            return 0; 
        }
        BOOL overflowed = lhs_high && rhs_high;
        unsigned long mid_bits1 = lhs_low * rhs_high;
        unsigned long mid_bits2 = lhs_high * rhs_low;

        *result = bot_bits + ((mid_bits1+mid_bits2) << LONG_BIT/2);
        return overflowed || *result < bot_bits
            || (mid_bits1 >> LONG_BIT/2) != 0
            || (mid_bits2 >> LONG_BIT/2) != 0;
}

// Option 3, perform long multiplication using a smaller type (this code is
// very similar to option 2, but calculates overflow using a different but
// equivalent method).
//    - Sometimes the fastest (e.g., when mulitply on longs is a library
//      call; clang likes this code).
//    - Performs at most three multiplies, and sometimes only performs one.
//    - Highly portable code; works no matter how many bits unsigned long is

BOOL 
umull_overflow3(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        const unsigned long HALFSIZE_MAX = (1ul << LONG_BIT/2) - 1ul;
        unsigned long lhs_high = lhs >> LONG_BIT/2;
        unsigned long lhs_low  = lhs & HALFSIZE_MAX;
        unsigned long rhs_high = rhs >> LONG_BIT/2;
        unsigned long rhs_low  = rhs & HALFSIZE_MAX;

        unsigned long lowbits = lhs_low * rhs_low;
        if (!(lhs_high || rhs_high)) {
            *result = lowbits;
            return 0; 
        }
        BOOL overflowed = lhs_high && rhs_high;
        unsigned long midbits1 = lhs_low * rhs_high;
        unsigned long midbits2 = lhs_high * rhs_low;
        unsigned long midbits  = midbits1 + midbits2;
        overflowed = overflowed || midbits < midbits1 || midbits > HALFSIZE_MAX;
        unsigned long product = lowbits + (midbits << LONG_BIT/2);
        overflowed = overflowed || product < lowbits;

        *result = product;
        return overflowed;
}

// Option 4, checks for overflow using division
//    - Checks for overflow using division
//    - Division is slow, especially if it is a library call

BOOL
umull_overflow4(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        *result = lhs * rhs;
        return rhs > 0 && (SIZE_MAX / rhs) < lhs;
}

// Option 5, checks for overflow using division
//    - Checks for overflow using division
//    - Avoids division when the numbers are "small enough" to trivially
//      rule out overflow
//    - Division is slow, especially if it is a library call

BOOL
umull_overflow5(unsigned long lhs, unsigned long rhs, unsigned long* result)
{
        const unsigned long MUL_NO_OVERFLOW = (1ul << LONG_BIT/2) - 1ul;
        *result = lhs * rhs;
        return (lhs >= MUL_NO_OVERFLOW || rhs >= MUL_NO_OVERFLOW) &&
            rhs > 0 && SIZE_MAX / rhs < lhs;
}

#ifndef umull_overflow
    #define umull_overflow2
#endif

/*
 * This benchmark code performs a multiply at all bit sizes, 
 * essentially assuming that sizes are logarithmically distributed.
 */

int main()
{
        unsigned long i, j, k;
        int count = 0;
        unsigned long mult;
        unsigned long total = 0;

        for (k = 0; k < 0x40000000 / LONG_BIT / LONG_BIT; ++k)
                for (i = 0; i != LONG_MAX; i = i*2+1)
                        for (j = 0; j != LONG_MAX; j = j*2+1) {
                                count += umull_overflow(i+k, j+k, &mult);
                                total += mult;
                        }
        printf("%d overflows (total %lu)\n", count, total);
}

Here are the results, testing with various compilers and systems I have (in this case, all testing was done on OS X, but results should be similar on BSD or Linux systems):

+------------------+----------+----------+----------+----------+----------+
|                  | Option 1 | Option 2 | Option 3 | Option 4 | Option 5 |
|                  |  BigInt  | LngMult1 | LngMult2 |   Div    |  OptDiv  |
+------------------+----------+----------+----------+----------+----------+
| Clang 3.5 i386   |    1.610 |    3.217 |    3.129 |    4.405 |    4.398 |
| GCC 4.9.0 i386   |    1.488 |    3.469 |    5.853 |    4.704 |    4.712 |
| GCC 4.2.1 i386   |    2.842 |    4.022 |    3.629 |    4.160 |    4.696 |
| GCC 4.2.1 PPC32  |    8.227 |    7.756 |    7.242 |   20.632 |   20.481 |
| GCC 3.3   PPC32  |    5.684 |    9.804 |   11.525 |   21.734 |   22.517 |
+------------------+----------+----------+----------+----------+----------+
| Clang 3.5 x86_64 |    1.584 |    2.472 |    2.449 |    9.246 |    7.280 |
| GCC 4.9 x86_64   |    1.414 |    2.623 |    4.327 |    9.047 |    7.538 |
| GCC 4.2.1 x86_64 |    2.143 |    2.618 |    2.750 |    9.510 |    7.389 |
| GCC 4.2.1 PPC64  |   13.178 |    8.994 |    8.567 |   37.504 |   29.851 |
+------------------+----------+----------+----------+----------+----------+

Based on these results, we can draw a few conclusions:

  • Clearly, the division-based approach, although simple and portable, is slow.
  • No technique is a clear winner in all cases.
  • On modern compilers, the use-a-larger-int approach is best, if you can use it
  • On older compilers, the long-multiplication approach is best
  • Surprisingly, GCC 4.9.0 has performance regressions over GCC 4.2.1, and GCC 4.2.1 has performance regressions over GCC 3.3
Charphacy
  • 2,110
  • 1
  • 20
  • 12
13

Easy and fast with clang and gcc:

unsigned long long t a, b, result;
if (__builtin_umulll_overflow(a, b, &result)) {
    // overflow!!
}

This will use hardware support for overflow detection where available. By being compiler extensions it can even handle signed integer overflow (replace umul with smul), eventhough that is undefined behavior in C++.

Allan Jensen
  • 518
  • 4
  • 8
12

A version that also works when a == 0:

    x = a * b;
    if (a != 0 && x / a != b) {
        // overflow handling
    }
Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
  • This approach needs to ensure that all of the `a`, `b` and `x` parts are cast to unsigned, otherwise the compiler is free to optimize out the `x / a` part. – Explorer09 Jan 26 '17 at 13:31
  • @Explorer09 can you elaborate why the compiler would optimize it away ? – Kevin Dec 19 '18 at 18:46
  • 6
    @kevinf Signed integer overflow is undefined behaviour in the C standard. Which means compilers can assert that any "obvious" overflow behaviour would be "impossible" and "don't care of their consequences", and therefore could optimize out. For the example, compiler could expand `x / a != b` to `(a*b) / a != b` due to assignment statement just before, which then reduces to `b != b` and finally a `false`. That's how the overflow check could fail to work due to compiler optimization. ... – Explorer09 Dec 26 '18 at 06:52
  • 1
    @kevinf ... To make it work you need cast: `(uintmax_t) x / a != b`, or use [`-fwrapv` flag](https://stackoverflow.com/questions/47232954/) on the compiler to assume the overflow behaviour. – Explorer09 Dec 26 '18 at 06:53
8

If you need not just to detect overflow but also to capture the carry, you're best off breaking your numbers down into 32-bit parts. The code is a nightmare; what follows is just a sketch:

#include <stdint.h>

uint64_t mul(uint64_t a, uint64_t b) {
  uint32_t ah = a >> 32;
  uint32_t al = a;  // truncates: now a = al + 2**32 * ah
  uint32_t bh = b >> 32;
  uint32_t bl = b;  // truncates: now b = bl + 2**32 * bh
  // a * b = 2**64 * ah * bh + 2**32 * (ah * bl + bh * al) + al * bl
  uint64_t partial = (uint64_t) al * (uint64_t) bl;
  uint64_t mid1    = (uint64_t) ah * (uint64_t) bl;
  uint64_t mid2    = (uint64_t) al * (uint64_t) bh;
  uint64_t carry   = (uint64_t) ah * (uint64_t) bh;
  // add high parts of mid1 and mid2 to carry
  // add low parts of mid1 and mid2 to partial, carrying
  //    any carry bits into carry...
}

The problem is not just the partial products but the fact that any of the sums can overflow.

If I had to do this for real, I would write an extended-multiply routine in the local assembly language. That is, for example, multiply two 64-bit integers to get a 128-bit result, which is stored in two 64-bit registers. All reasonable hardware provides this functionality in a single native multiply instruction—it's not just accessible from C.

This is one of those rare cases where the solution that's most elegant and easy to program is actually to use assembly language. But it's certainly not portable :-(

Norman Ramsey
  • 198,648
  • 61
  • 360
  • 533
  • 1
    How do you know your intermediate products will be done with a 64bit result? On a 32 bit machine I think you would get a 32bit result. – Michael May 15 '15 at 17:43
  • @Michael quite right you have to widen those four things before multiplying them. Fixed. – Norman Ramsey May 21 '15 at 15:31
4

The GNU Portability Library (Gnulib) contains a module intprops, which has macros that efficiently test whether arithmetic operations would overflow.

For example, if an overflow in multiplication would occur, INT_MULTIPLY_OVERFLOW (a, b) would yield 1.

Marc
  • 4,327
  • 4
  • 30
  • 46
3

Perhaps the best way to solve this problem is to have a function, which multiplies two UInt64 and results a pair of UInt64, an upper part and a lower part of the UInt128 result. Here is the solution, including a function, which displays the result in hex. I guess you perhaps prefer a C++ solution, but I have a working Swift-Solution which shows, how to manage the problem:

func hex128 (_ hi: UInt64, _ lo: UInt64) -> String
{
    var s: String = String(format: "%08X", hi >> 32)
                  + String(format: "%08X", hi & 0xFFFFFFFF)
                  + String(format: "%08X", lo >> 32)
                  + String(format: "%08X", lo & 0xFFFFFFFF)
    return (s)
}

func mul64to128 (_ multiplier: UInt64, _ multiplicand : UInt64)
             -> (result_hi: UInt64, result_lo: UInt64)
{
    let x: UInt64 = multiplier
    let x_lo: UInt64 = (x & 0xffffffff)
    let x_hi: UInt64 = x >> 32

    let y: UInt64 = multiplicand
    let y_lo: UInt64 = (y & 0xffffffff)
    let y_hi: UInt64 = y >> 32

    let mul_lo: UInt64 = (x_lo * y_lo)
    let mul_hi: UInt64 = (x_hi * y_lo) + (mul_lo >> 32)
    let mul_carry: UInt64 = (x_lo * y_hi) + (mul_hi & 0xffffffff)
    let result_hi: UInt64 = (x_hi * y_hi) + (mul_hi >> 32) + (mul_carry >> 32)
    let result_lo: UInt64 = (mul_carry << 32) + (mul_lo & 0xffffffff)

    return (result_hi, result_lo)
}

Here is an example to verify, that the function works:

var c: UInt64 = 0
var d: UInt64 = 0

(c, d) = mul64to128(0x1234567890123456, 0x9876543210987654)
// 0AD77D742CE3C72E45FD10D81D28D038 is the result of the above example
print(hex128(c, d))

(c, d) = mul64to128(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF)
// FFFFFFFFFFFFFFFE0000000000000001 is the result of the above example
print(hex128(c, d))
j.s.com
  • 1,422
  • 14
  • 24
2

There is a simple (and often very fast solution) which has not been mentioned yet. The solution is based on the fact that n-Bit times m-Bit multiplication does never overflow for a product width of n+m-bit or higher but overflows for all result widths smaller than n+m-1.

Because my old description might have been too difficult to read for some people, I try it again: What you need is checking the sum of leading-zeroes of both operands. It would be very easy to prove mathematically. Let x be n-Bit and y be m-Bit. z = x * y is k-Bit. Because the product can be n+m bit large at most it can overflow. Let's say. x*y is p-Bit long (without leading zeroes). The leading zeroes of the product are clz(x * y) = n+m - p. clz behaves similar to log, hence: clz(x * y) = clz(x) + clz(y) + c with c = either 1 or 0. (thank you for the c = 1 advice in the comment!) It overflows when k < p <= n+m <=> n+m - k > n+m - p = clz(x * y).

Now we can use this algorithm:

if max(clz(x * y)) = clz(x) + clz(y) +1 < (n+m - k)  --> overflow
if max(clz(x * y)) = clz(x) + clz(y) +1 == (n+m - k)  --> overflow if c = 0
else --> no overflow

How to check for overflow in the middle case? I assume, you have a multiplication instruction. Then we easily can use it to see the leading zeroes of the result, i.e.:

if clz(x * y / 2) == (n+m - k) <=> msb(x * y/2) == 1  --> overflow
else --> no overflow

You do the multiplication by treating x/2 as fixed point and y as normal integer:

msb(x * y/2) = msb(floor(x * y / 2))
floor(x * y/2) = floor(x/2) * y + (lsb(x) * floor(y/2)) = (x >> 1)*y + (x & 1)*(y >> 1)

(this result never overflows in case of clz(x)+clz(y)+1 == (n+m -k))

The trick is using builtins/intrinsics. In GCC it looks this way:

static inline int clz(int a) {
    if (a == 0) return 32; //only needed for x86 architecture
    return __builtin_clz(a);
}
/**@fn static inline _Bool chk_mul_ov(uint32_t f1, uint32_t f2)
 * @return one, if a 32-Bit-overflow occurs when unsigned-unsigned-multipliying f1 with f2 otherwise zero. */
static inline _Bool chk_mul_ov(uint32_t f1, uint32_t f2) {
    int lzsum = clz(f1) + clz(f2); //leading zero sum
    return
        lzsum < sizeof(f1)*8-1 || ( //if too small, overflow guaranteed
            lzsum == sizeof(f1)*8-1 && //if special case, do further check
            (int32_t)((f1 >> 1)*f2 + (f1 & 1)*(f2 >> 1)) < 0 //check product rightshifted by one
    );
}
...
    if (chk_mul_ov(f1, f2)) {
        //error handling
    }
...

Just an example for n = m = k = 32-Bit unsigned-unsigned-multiplication. You can generalize it to signed-unsigned- or signed-signed-multiplication. And even no multiple-bit-shift is required (because some microcontrollers implement one-bit-shifts only but sometimes support product divided by two with a single instruction like Atmega!). However, if no count-leading-zeroes instruction exists but long multiplication, this might not be better.

Other compilers have their own way of specifying intrinsics for CLZ operations. Compared to checking upper half of the multiplication the clz-method should scale better (in worst case) than using a highly optimized 128-Bit multiplication to check for 64-Bit overflow. Multiplication needs over linear overhead while count bits needs only linear overhead. This code worked out-of-the box for me when tried.

ChrisoLosoph
  • 459
  • 4
  • 8
  • This approach alone will not suffice. Consider `0xffff * 0x10002` (overflows `uint32_t`) vs. `0xffff * 0x10001` (no overflow). Furthermore, in the macro you should be comparing to `sizeof(f1)*8` without `/2`. – Antosha Dec 02 '19 at 13:01
  • Thank you for your advice. I corrected the macro. I shortly had a weird feeling by myself, because I actually know your case, but I rushed too much. – ChrisoLosoph Dec 03 '19 at 21:31
1

I've been working with this problem this days and I have to say that it has impressed me the number of times I have seen people saying the best way to know if there has been an overflow is to divide the result, thats totally inefficient and unnecessary. The point for this function is that it must be as fast as possible.

There are two options for the overflow detection:

1º- If possible create the result variable twice as big as the multipliers, for example:

struct INT32struct {INT16 high, low;};
typedef union
{
  struct INT32struct s;
  INT32 ll;
} INT32union;

INT16 mulFunction(INT16 a, INT16 b)
{
  INT32union result.ll = a * b; //32Bits result
  if(result.s.high > 0) 
      Overflow();
  return (result.s.low)
}

You will know inmediately if there has been an overflow, and the code is the fastest possible without writing it in machine code. Depending on the compiler this code can be improved in machine code.

2º- Is impossible to create a result variable twice as big as the multipliers variable: Then you should play with if conditions to determine the best path. Continuing with the example:

INT32 mulFunction(INT32 a, INT32 b)
{

  INT32union s_a.ll = abs(a);
  INT32union s_b.ll = abs(b); //32Bits result
  INT32union result;
  if(s_a.s.hi > 0 && s_b.s.hi > 0)
  {
      Overflow();
  }
  else if (s_a.s.hi > 0)
  {
      INT32union res1.ll = s_a.s.hi * s_b.s.lo;
      INT32union res2.ll = s_a.s.lo * s_b.s.lo;
      if (res1.hi == 0)
      {
          result.s.lo = res1.s.lo + res2.s.hi;
          if (result.s.hi == 0)
          {
            result.s.ll = result.s.lo << 16 + res2.s.lo;
            if ((a.s.hi >> 15) ^ (b.s.hi >> 15) == 1)
            {
                result.s.ll = -result.s.ll; 
            }
            return result.s.ll
          }else
          {
             Overflow();
          }
      }else
      {
          Overflow();
      }
  }else if (s_b.s.hi > 0)
{

   //Same code changing a with b

}else 
{
    return (s_a.lo * s_b.lo);
}
}

I hope this code helps you to have a quite efficient program and I hope the code is clear, if not I'll put some coments.

best regards.

user1368116
  • 121
  • 6
  • 1
    Just to note: Your example code assumes a big-endian machine. Not portable to a little-endian one such as the common x86. – Explorer09 Jan 24 '17 at 13:24
  • a programmer's time is worth more in dollars than 1ms speedups, by several orders of magnitude, so from a practical standpoint this would qualify moreso as "inefficient and unnecessary", though tbh i love low level C type optimization problems so i do still like this answer. just, there's no reason to talk about the other answers like that when realistically their solution is the more efficient one in terms of just getting it done – Nicholas Pipitone Feb 24 '20 at 20:44
  • 1
    @把友情留在无盐 The endianness issue resides in the struct definition: `struct INT32struct {INT16 high, low;};` Little-endian machine requires that byte order is swapped like this: `struct INT32struct {INT16 low; INT16 high;};` – Explorer09 May 04 '20 at 08:24
0

Here is a trick for detecting whether multiplication of two unsigned integers overflows.

We make the observation that if we multiply an N-bit-wide binary number with an M-bit-wide binary number, the product does not have more than N + M bits.

For instance, if we are asked to multiply a three-bit number with a twenty-nine bit number, we know that this doesn't overflow thirty-two bits.

#include <stdlib.h>
#include <stdio.h>

int might_be_mul_oflow(unsigned long a, unsigned long b)
{
  if (!a || !b)
    return 0;

  a = a | (a >> 1) | (a >> 2) | (a >> 4) | (a >> 8) | (a >> 16) | (a >> 32);
  b = b | (b >> 1) | (b >> 2) | (b >> 4) | (b >> 8) | (b >> 16) | (b >> 32);

  for (;;) {
    unsigned long na = a << 1;
    if (na <= a)
      break;
    a = na;
  }

  return (a & b) ? 1 : 0;
}

int main(int argc, char **argv)
{
  unsigned long a, b;
  char *endptr;

  if (argc < 3) {
    printf("supply two unsigned long integers in C form\n");
    return EXIT_FAILURE;
  }

  a = strtoul(argv[1], &endptr, 0);

  if (*endptr != 0) {
    printf("%s is garbage\n", argv[1]);
    return EXIT_FAILURE;
  }

  b = strtoul(argv[2], &endptr, 0);

  if (*endptr != 0) {
    printf("%s is garbage\n", argv[2]);
    return EXIT_FAILURE;
  }

  if (might_be_mul_oflow(a, b))
    printf("might be multiplication overflow\n");

  {
    unsigned long c = a * b;
    printf("%lu * %lu = %lu\n", a, b, c);
    if (a != 0 && c / a != b)
      printf("confirmed multiplication overflow\n");
  }

  return 0;
}

A smattering of tests: (on 64 bit system):

$ ./uflow 0x3 0x3FFFFFFFFFFFFFFF
3 * 4611686018427387903 = 13835058055282163709

$ ./uflow 0x7 0x3FFFFFFFFFFFFFFF
might be multiplication overflow
7 * 4611686018427387903 = 13835058055282163705
confirmed multiplication overflow

$ ./uflow 0x4 0x3FFFFFFFFFFFFFFF
might be multiplication overflow
4 * 4611686018427387903 = 18446744073709551612

$ ./uflow 0x5 0x3FFFFFFFFFFFFFFF
might be multiplication overflow
5 * 4611686018427387903 = 4611686018427387899
confirmed multiplication overflow

The steps in might_be_mul_oflow are almost certainly slower than just doing the division test, at least on mainstream processors used in desktop workstations, servers and mobile devices. On chips without good division support, it could be useful.


It occurs to me that there is another way to do this early rejection test.

  1. We start with a pair of numbers arng and brng which are initialized to 0x7FFF...FFFF and 1.

  2. If a <= arng and b <= brng we can conclude that there is no overflow.

  3. Otherwise, we shift arng to the right, and shift brng to the left, adding one bit to brng, so that they are 0x3FFF...FFFF and 3.

  4. If arng is zero, finish; otherwise repeat at 2.

The function now looks like:

int might_be_mul_oflow(unsigned long a, unsigned long b)
{
  if (!a || !b)
    return 0;

  {
    unsigned long arng = ULONG_MAX >> 1;
    unsigned long brng = 1;

    while (arng != 0) {
      if (a <= arng && b <= brng)
        return 0;
      arng >>= 1;
      brng <<= 1;
      brng |= 1;
    }

    return 1;
  }
}
Kaz
  • 55,781
  • 9
  • 100
  • 149
0

When you're using e.g. 64 bits variables, implement 'number of significant bits' with nsb(var) = { 64 - clz(var); }.

clz(var) = count leading zeros in var, a builtin command for GCC and Clang, or probably available with inline assembly for your CPU.

Now use the fact that nsb(a * b) <= nsb(a) + nsb(b) to check for overflow. When smaller, it is always 1 smaller.

Ref GCC: Built-in Function: int __builtin_clz (unsigned int x) Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined, so add a small check.

0

I was thinking about this today and stumbled upon this question, my thoughts led me to this result. TLDR, while I find it "elegant" in that it only uses a few lines of code (could easily be a one liner), and has some mild math that simplifies to something relatively simple conceptually, this is mostly "interesting" and I haven't tested it.

If you think of an unsigned integer as being a single digit with radix 2^n where n is the number of bits in the integer, then you can map those numbers to radians around the unit circle, e.g.

radians(x) = x * (2 * pi * rad / 2^n)

When the integer overflows, it is equivalent to wrapping around the circle. So calculating the carry is equivalent to calculating the number of times multiplication would wrap around the circle. To calculate the number of times we wrap around the circle we divide radians(x) by 2pi radians. e.g.

wrap(x) = radians(x) / (2*pi*rad)
        = (x * (2*pi*rad / 2^n)) / (2*pi*rad / 1)
        = (x * (2*pi*rad / 2^n)) * (1 / 2*pi*rad)
        = x * 1 / 2^n
        = x / 2^n

Which simplifies to

wrap(x) = x / 2^n

This makes sense. The number of times a number, for example, 15 with radix 10, wraps around is 15 / 10 = 1.5, or one and a half times. However, we can't use 2 digits here (assuming we are limited to a single 2^64 digit).

Say we have a * b, with radix R, we can calculate the carry with

Consider that: wrap(a * b) = a * wrap(b)
wrap(a * b) = (a * b) / R
a * wrap(b) = a * (b / R)
a * (b / R) = (a * b) / R

carry = floor(a * wrap(b))

Take for example a = 9 and b = 5, which are factors of 45 (i.e. 9 * 5 = 45).

wrap(5) = 5 / 10 = 0.5
a * wrap(5) = 9 * 0.5 = 4.5
carry = floor(9 * wrap(5)) = floor(4.5) = 4

Note that if the carry was 0, then we would not have had overflow, for example if a = 2, b=2.

In C/C++ (if the compiler and architecture supports it) we have to use long double.

Thus we have:

long double wrap = b / 18446744073709551616.0L; // this is b / 2^64
unsigned long carry = (unsigned long)(a * wrap); // floor(a * wrap(b))
bool overflow = carry > 0;
unsigned long c = a * b;

c here is the lower significant "digit", i.e. in base 10 9 * 9 = 81, carry = 8, and c = 1.

This was interesting to me in theory, so I thought I'd share it, but one major caveat is with the floating point precision in computers. Using long double, there may be rounding errors for some numbers when we calculate the wrap variable depending on how many significant digits your compiler/arch uses for long doubles, I believe it should be 20 more more to be sure. Another issue with this result, is that it may not perform as well as some of the other solutions simply by using floating points and division.

-3

If you just want to detect overflow, how about converting to double, doing the multiplication and if

|x| < 2^53, convert to int64

|x| < 2^63, make the multiplication using int64

otherwise produce whatever error you want?

This seems to work:

int64_t safemult(int64_t a, int64_t b) {
  double dx;

  dx = (double)a * (double)b;

  if ( fabs(dx) < (double)9007199254740992 )
    return (int64_t)dx;

  if ( (double)INT64_MAX < fabs(dx) )
    return INT64_MAX;

  return a*b;
}
  • 2
    It's very hard to know if this is correct. Not every 64-bit integer can be represented by a 64-bit double. In particular, the significand of a double only has 53-bits, so once you pass `2 << 53`, you lose so much precision that adjacent doubles are now greater than 1 apart. Numbers jump from `(2 << 53) + 0` to `(2 << 53) + 2`. When you convert `uint64_t` to `double` you may be rounding down. The value `dx` is the product of two potentially rounded down numbers. Hence there is no guarantee comparing it to `MAX_INT` tells you anything meaningful about multiplying the original values. – Daniel Stevens Nov 19 '18 at 08:54