[comp.lang.c] How to multiply two ints and check overflow

ken@aiai.ed.ac.uk (Ken Johnson) (05/12/89)

/*
	The following program performs integer multiplication and
	detects overflow. It works on ints greater than or equal to 0.
	It is of course a very great deal slower than just using the
	* operator -- I have assumed you are prepared to spend all that
	time because you can't just stick the answer in a `long' and
	check the upper bits in the long are all empty!

	Note -- this worked on all the examples I threw at it, but I
	do not guarantee it at all. Let me know if you improve it.

	Ken Johnson, 12 May 1989. You may use this code freely, except
	that if you sell copies at a profit, I want a share.

	Here goes...
*/


#include <stdio.h>

#define	YES	1
#define	NO	0

#define	RTMOST_BIT	0x0001		/* These assume a 16-bit word */
#define	SIGN_BIT	0x8000

main(argc,argv)				/* Test harness, self explanatory */
int	argc;				/* :-)                            */
char	**argv;
{
	int	vmul( );		/* The function I'm interested in */

	if (argc != 3)
	{
		fprintf(stderr,"usage: VMUL N1 N2\n");
		exit(1);
	}

	{
		int	ovf;
		int	n1, n2, ans;

		n1 = atoi(*++argv);
		n2 = atoi(*++argv);

		if ( n1 < 0 || n2 < 0)
		{
			fprintf(stderr,"VMUL: both args must be >= 0\n");
			exit(1);
		}

		ans = vmul(n1,n2,&ovf);

		printf("%d * %d = %d, %s overflow\n",
			n1,  n2,  ans,  ((ovf == YES) ? "" : "no"));
	}
}

/*
	vmul(n1,n2,&ovf) returns the product of integers n1 and n2,
	setting the flag OVF to the constant YES or NO. If overflow
	occurs, then vmul always returns 0.

	It works on the Russian Multiplication algorithm. The number of
	passes through the loop will be the number of bits set in n2,
	that is, at most one less than the number of bits in an int.
*/

int	vmul(n1,n2,p_ovf)
int	n1;
int	n2;
int	*p_ovf;
{
	int	product;
	product = 0;

	while(n2)
	{
		if (n1 & SIGN_BIT)		/* Check multiplier is in */
		{				/* range.                 */
			*p_ovf = YES;
			return(0);
		}

		if (n2 & RTMOST_BIT)		/* Add n1 into product and */
		{				/* check overflow          */
			if ((product += n1) & SIGN_BIT)
			{
				*p_ovf = YES;
				return(0);
			}
		}

		n1 <<= 1;
		n2 >>= 1;
	}

	*p_ovf = NO;				/* Normal exit */
	return(product);
}
-- 
Ken Johnson, AI Applications Institute, 80 South Bridge, Edinburgh EH1 1HN
E-mail ken@aiai.ed.ac.uk, phone 031-225 4464 extension 212
Half of what I have said in this article is rubbish. I don't know which half.

mcdaniel@uicsrd.csrd.uiuc.edu (Tim McDaniel) (05/18/89)

Here's safemul, my attempt at a portable overflow-checked int
multiply.  It has a main() driver, if you like, with various options
(print a multiplication table, do a timing check, verify all the
results).  On a VAX 11/785 under BSD 4.3, a safemul takes about 100
microseconds (including the function call), versus about 10
microseconds for a simple int multiply.

I think it's bullet-proof, even if you don't have an int divide that
always rounds towards zero.  Let me know if I goofed somehow.

-------- cut here -------- Cut Here -------- CUT HERE --------
/* Copyright 1989 by Timothy Alan McDaniel.  May be used only under
 * the terms of the Free Software Foundation GNU General Public
 * License, version one.  There is NO warranty on this software.
 *            Tim, the Bizarre and Oddly-Dressed Enchanter
 *                  mcdaniel@uicsrd.csrd.uiuc.edu
 *          {uunet,convex,pur-ee}!uiucuxc!uicsrd!mcdaniel
 *          mcdaniel%uicsrd@{uxc.cso.uiuc.edu,uiuc.csnet}
 */

#define DRIVER          /* use a main program to test safemul */
#define PRINT_TABLE     /* wanna multiplication table? */
#define ASSUME_OVERFLOW /* if x*y ovfl, assume X*y will, if X>x>0 etc */
#undef  TIMER /* 1000000 */ /* iterations for timing test */
#define USE_UINT        /* use if "/" does not always round towards zero */

/**********************************************************************\

int safemul(int x, int y, int * ovfl)

Return value:    x*y  if the result can be represented in an int
        	      without overflow
                 0    otherwise
*ovfl is set to: 1    if x*y would overflow
                 0    otherwise
If ovfl is (int *) NULL, and overflow would occur, a message is
printed on stderr and abort() is called.

The constants INT_MAX and INT_MIN are used to determine whether
overflow would occur.  If x, y, or x*y (evaluated in infinite
precision) falls outside the inclusive range [INT_MIN .. INT_MAX],
overflow is deemed to occur.  These values are in the ANSI C header
<limits.h>, or must be #defined appropriately by the user.

ALGORITHM:

A preliminary divide is used to determine whether a multiply would
overflow, because
     xy > limit     iff     x > limit/y  (when y > 0)

However, divide can overflow if INT_MAX != -INT_MIN.  For example, if
INT_MAX is 7 and INT_MIN is -8, (-8)/(-1) would overflow.  Note that
this occurs for dividing by -1.  If INT_MAX is within a factor of 2 of
-INT_MIN (a reasonable assumption, all in all), it can only occur when
dividing by -1, so I check for that specifically.  Similarly, unary
negation can overflow: e.g. -(-8).

(If INT_MAX was between a factor of 2 and 3 of -INT_MIN, I would have
to check for divide by -2 also, but divide by -3 would be OK.)

4 cases:

    x        y       then      so multiply would overflow if

  x > 0    y > 0     xy > 0    xy>INT_MAX    i.e.    x>INT_MAX/y
  x > 0    y < 0     xy < 0    xy<INT_MIN    i.e.    x>INT_MIN/y
  x < 0    y > 0     xy < 0    xy<INT_MIN    i.e.    x<INT_MIN/y
  x < 0    y < 0     xy > 0    xy>INT_MAX    i.e.    x<INT_MAX/y

(Remember that ab>c  iff  a<c/b, if b < 0.)

This algorithm is not well-suited to machines for which integer
division is very slow.  The "/" operation takes place in only one
lexical location in this routine, in case integer division is a large
inline routine.  However, in this case, it is likely to be slow ...

In the usual case, two non-zero integers that do not cause overflow,
the algorithm uses an int divide, an int multiply, 11 compares, an xor
("^"), and sundry branches/loads/stores.

In the usual case, if USE_UINT is defined, the algorithm uses an
unsigned divide, an int multiply, 10 compares, an xor, 1 unsigned int
negation, 3 casts int->unsigned int, and sundry.

COMPILATION:

You can #define DRIVER if you want to run a driver main program to
verify the results of safemul from INT_MIN-1 to INT_MAX+1.  It does
not check for overflow on its own multiplies and such, so it only
works for relatively small values of INT_MIN and INT_MAX (up to the
square root of the actual magnitudes of INT_MIN and INT_MAX).  The
driver defines its own values for INT_MIN and INT_MAX; to change them,
look just after "For testing only".

If DRIVER is #defined, and the range of INT_MIN to INT_MAX is sizeable
(say -2**15 to 2*15-1 on a machine with 32-bit ints), testing all
possible multiplication pairs is prohibitively time-consuming.  In
this case, you can #define ASSUME_OVERFLOW.  In this case, if it finds
that x*y overflows, it will assume that X*y will overflow, for all X
such that abs(X) > abs(x) and sign(X) == sign(x), and similarly for y.

If DRIVER is #defined, #define PRINT_TABLE if you want a pretty
multiplication table to be printed.  Blank entries are those that
overflowed.

If DRIVER is defined, you can #define TIMER to a positive integer.
Instead of verifying safemul, printing a multiplication table, and the
like, it just does a crude timing test.  TIMER is defined to be the
number of times safemul is called; as a comparison, 10*TIMER simple
multiplications are also done.  Note that elapsed times are found by
calling time(), which has resolution only to a full second, so the
precision is low.  On the original test machine (BSD 4.3, VAX 11/785),
a safemul takes about 100 microseconds (sounds much better than 1/10
milliseconds) and a simple multiply takes roughly 10 microseconds,
whether or not USE_UINT is defined.

If you are using an ANSI C compiler, put "#" in front of all further
occurrences of the word "error".

If you are not using an ANSI C compiler, inspect the first #defines
below for INT_MIN, INT_MAX, and EXIT_FAILURE, and make sure they are
reasonable for your system.  (EXIT_FAILURE is the argument to exit()
if the program failed; 1 is usual, except for VMS, sometimes.)

The default algorithm below depends on integer division always rounds
towards zero: that (-5)/2 == 5/(-2) == -2, and (-5)/(-2) 5/2 == 2.
pANS C guarantees r.t.z. only for positive operands.  If you are not
sure that your computer always rounds towards zero, #define USE_UINT.
The algorithm will instead do its divide using unsigned ints, which
pANS C guarantees will not overflow unless the divisor is 0.
(USE_UINT is not the default because int is supposed to be the "most
natural" integral size, which often means the fastest.  Unsigned
operations may take more time to get them right without overflowing.)

                           ***NOTE***

If you define USE_UINT, it must be the case that UINT_MAX >= INT_MAX
and UINT_MAX >= -INT_MIN.  If this is not the case, let me know and
replace your computer system, because it is TOTALLY TWISTED.

\**********************************************************************/

#include <stdio.h>
#if __STDC__ > 0

#define ANSI_C 1
#include <limits.h>
#include <stdlib.h>

#ifdef USE_UINT
#if UINT_MAX < INT_MAX
 error "UINT_MAX < INT_MAX -- sorry, no can do"
#endif
#if UINT_MAX < -INT_MIN
 error "UINT_MAX < -INT_MIN -- sorry, no can do"
#endif
#endif /* USE_UINT */

#else

#define ANSI_C 0
#define volatile /* nothing */
/* Define these well, please. */
#define INT_MAX (0x7fffffff)
#define INT_MIN (-2147483648)
#define EXIT_FAILURE (1)

#endif

#ifdef DRIVER
/* For testing only. */
#undef INT_MAX
#undef INT_MIN
#undef UINT_MAX

/* The first choice is suitable for multiplication tables
 * and quick checking; the second is just to be more sure. */
#if 1
#define INT_MAX (7)
#define INT_MIN (-8)
#define UINT_MIN ((unsigned int) 15)
#else
#define INT_MAX (32767)
#define INT_MIN (-32768)
#define UINT_MAX ((unsigned int) 65535)
#endif
#endif


/* Note: INT_MAX + INT_MIN > 0 iff the positive ints have a larger
 * range than the negative ints, except there is no chance of
 * overflow on unary negation in the first case.  Similarly for
 * "== 0" and "< 0". */
/* This sanity check is a tad tighter than necessary.  Also, I hope
 * that preprocessor arithmetic has at least as large a range
 * as the target-machine arithmetic -- there is no guarantee in
 * ANSI C. */
#if INT_MAX + INT_MIN > 0
#if INT_MAX / -INT_MIN >= 2
 error "INT_MAX is at least a factor of 2 larger than -INT_MIN; sorry"
#endif
#else
#if -INT_MIN / INT_MAX >= 2
 error "-INT_MIN is at least a factor of 2 larger than INT_MAX; sorry"
#endif
#endif



int safemul(x, y, arg_ovfl)
    register int x;
    register int y;
    int * arg_ovfl;
{
    int result, t, ovfl, limit;
    unsigned int uint_x, uint_y, uint_limit;

#ifdef DRIVER
    /* If DRIVER is used, INT_MAX and INT_MIN are quite a bit
     * smaller than their normal values.  Normally, the case
     * checked for here should never happen, if INT_MAX and
     * INT_MIN are defined properly. */
    if (x > INT_MAX || y > INT_MAX || x < INT_MIN || y < INT_MIN)
            ovfl = 1;
    else
        /* intentionally left without a ";" so it joins with the next if */
#endif

    if (x == 0 || y == 0)
        ovfl = result = 0;
    /* Now if we divide by x or y, we will not get zerodivide. */


#ifndef USE_UINT
    else {
        if (x == -1) {
            /* After swapping, this case reduces to the next case. */
            t = x;
            x = y;
            y = t;
            }
        if (y == -1) {
            /* It is a negation: especially beware if INT_MAX != -INT_MIN. */
#if INT_MAX + INT_MIN >= 0
            ovfl = (x > -INT_MIN);
#else
            ovfl = (x < -INT_MAX);
#endif
            if (!ovfl)
                result = -x;
            }

        else {
            /* Since we know that abs(INT_MAX) is within a factor of 2 of
             * abs(INT_MIN), and abs(x) and abs(y) >= 2, we cannot overflow on
             * divide in here. */

            /* See the "truth table" above for the logic of this. */
            /* If the sign of x differs from the sign of y, then ... */
            if ((x > 0) ^ (y > 0))
                limit = INT_MIN;
            else
                limit = INT_MAX;
            limit /= y;
            if (x > 0)
                ovfl = (x > limit);
            else
                ovfl = (x < limit);
            if (!ovfl)
                result = x * y;
            }
        }

#else /* if USE_UINT is defined */

    else {
        uint_x = (x >= 0) ? (unsigned int) x : -(unsigned int) x;
        uint_y = (y >= 0) ? (unsigned int) y : -(unsigned int) y;
        uint_limit = ((x > 0) ^ (y > 0))
                         ? -(unsigned int) INT_MIN
                         : (unsigned int) INT_MAX;
        ovfl = (uint_x > uint_limit / uint_y);
        if (!ovfl)
            result = x * y;
        }

#endif /* ifndef USE_UINT */

    if (arg_ovfl)
        *arg_ovfl = ovfl;
    if (ovfl) {
        if (!arg_ovfl) {
            fprintf(stderr, "int overflow would occur in safemul.\n");
            abort();
            exit(EXIT_FAILURE);
            }
        result = 0;
        }
    return result;
    }


#ifdef DRIVER

static int verify_safemul(x, y)
    int x;
    int y;
{
    int ovfl, result, my_ovfl, bad;

    result = safemul(x, y, &ovfl);
    bad = 0;
    if (ovfl != 0 && ovfl != 1) {
        fprintf(stderr, "Overflow should be 1 or 0, not %d\n", ovfl);
        bad = 1;
        }
    if (ovfl && result) {
        fprintf(stderr, "Overflow AND result!  %d*%d=%d\n",
                x, y, result);
        bad = 1;
        }
    my_ovfl = (x > INT_MAX) || (x < INT_MIN) ||
              (y > INT_MAX) || (y < INT_MIN) ||
              (((long) x)*y > INT_MAX) || (((long) x)*y < INT_MIN);
    if (ovfl != my_ovfl) {
        fprintf(stderr, "Bad overflow!  %d*%d %s, safemul says %s\n",
                x, y, my_ovfl ? "should overflow"
                              : "should not overflow",
                ovfl ? "should overflow"
                     : "should not overflow");
        bad = 1;
        }
    if (!ovfl && result != ((long) x)*y) {
        fprintf(stderr, "Incorrect result! %d*%d gives %d\n",
                x, y, result);
        bad = 1;
        }
    if (bad)
        exit(EXIT_FAILURE);
    return ovfl;
}

main() {
    int x, y, ovfl, low, high, my_ovfl;
    long t1, t2, t3, t4;
    volatile int result, t;	/* volatile so the timing loop does */
    volatile long count;	/* not get optimized away (I hope!) */
    extern long time(/* long * */);

#ifdef TIMER

    t1 = time((long *) 0);
    for (count = 0; count < TIMER; count++)
        result = safemul(17, -29, &ovfl);
    t2 = time((long *) 0);
    for (count = 0; count < TIMER; count++)
        result = -493;
    t3 = time((long *) 0);
    t = 17;
    for (count = 0; count < 10*TIMER; count++)
        result = t * -29;
    t4 = time((long *) 0);
    printf("safemul (averaged over %ld calls) takes about %f microseconds.\n",
           (long) TIMER, (t2 - t1 - (t3 - t2)) / (double) TIMER * 1e6);
    printf("* (averaged over %ld executions) takes about %f microseconds.\n",
           (long) 10*TIMER, (t4 - t3 - (t3 - t2)) / (double) TIMER * 1e5);

#else /* TIMER not defined */

    low = INT_MIN - 1;
    high = INT_MAX + 1;

    printf("\nAttempting to multiply values from %d to %d.\n", low, high);

#ifdef ASSUME_OVERFLOW
#define ACT break
#else
#define ACT
#endif

    /* Get the obvious boundary cases out of the way early. */
    for (x = -2; x <= 2; x++)
        for (y = -2; y <= 2; y++) {
            verify_safemul(x, y);
            verify_safemul(x, INT_MAX + y);
            verify_safemul(x, INT_MIN + y);
            verify_safemul(INT_MAX + x, y);
            verify_safemul(INT_MIN + x, y);
            verify_safemul(INT_MAX + x, INT_MIN + y);
            verify_safemul(INT_MIN + x, INT_MAX + y);
        }

    for (x = -1; x >= low; x--) {
        for (y = -1; y >= low; y--)
            if (verify_safemul(x, y))
                ACT;
        for (y = 0; y <= high; y++)
            if (verify_safemul(x, y))
                ACT;
        }

    for (x = 0; x <= high; x++) {
        for (y = 0; y <= high; y++)
            if (verify_safemul(x, y))
                ACT;
        for (y = -1; y >= low; y--)
            if (verify_safemul(x, y))
                ACT;
        }

#ifdef PRINT_TABLE
    printf("x/y | ");
    for (y = low; y <= high; y++)
        printf("%4d", y);
    printf("\n");

    printf("----|-");
    for (y = low; y <= high; y++)
        printf("----", y);
    printf("\n");

    for (x = low; x <= high; x++) {
        printf("%3d | ", x);
        for (y = low; y <= high; y++) {
            result = safemul(x, y, &ovfl);
            if (ovfl)
                printf("    ");
            else
                printf("%4d", result);
            }
        printf("\n");
        }
#endif /* PRINT_TABLE */

#endif /* TIMER */

    exit(0);
    }
#endif /* DRIVER */

--
"6:20 O Timothy, keep that which is committed to thy trust, avoiding
profane and vain babblings, and oppositions of science falsely so
called: 6:21 Which some professing have erred concerning the faith."

Tim, the Bizarre and Oddly-Dressed Enchanter  |  mcdaniel@uicsrd.csrd.uiuc.edu
            {uunet,convex,pur-ee}!uiucuxc!uicsrd!mcdaniel
            mcdaniel%uicsrd@{uxc.cso.uiuc.edu,uiuc.csnet}

mcdaniel@uicsrd.csrd.uiuc.edu (Tim McDaniel) (05/19/89)

The INT_MIN and INT_MAX #defines are there for non-pANS C sites;
they're inherently unportable.  If INT_MIN is #defined as
	(-2147483648)
it may not work (it may get the wrong type, according to Chris Torek).
INT_MIN may have to be #defined as
	(-2147483647 - 1)
or
        (0x80000000)
or even just
        (-2147483647)
if your compiler won't let you input -(2**32) and you're willing to
sacrifice the smallest int.  (INT_MIN and INT_MAX don't have to be
exact, or even close, for my safemul code to work.)

--
"6:20 O Timothy, keep that which is committed to thy trust, avoiding
profane and vain babblings, and oppositions of science falsely so
called: 6:21 Which some professing have erred concerning the faith."

Tim, the Bizarre and Oddly-Dressed Enchanter  |  mcdaniel@uicsrd.csrd.uiuc.edu
            {uunet,convex,pur-ee}!uiucuxc!uicsrd!mcdaniel
            mcdaniel%uicsrd@{uxc.cso.uiuc.edu,uiuc.csnet}