Overflow and range checking for mul/div

This is a discussion on Overflow and range checking for mul/div within the C++ Programming forums, part of the General Programming Boards category; Originally Posted by grumpy
For basic integral types in C/C++ that is true. It is not true for all integral ...

For basic integral types in C/C++ that is true. It is not true for all integral types (eg other languages allow specification of integral types with values in specified ranges, and such things can be implemented as a class in C++).

If your acceptable range is [-10,-1] then overflow on division is possible. -10/-1 equals 10 which exceeds any value in the accepted range.

Whoops missed that.
All you have to do then though is check the value of the result. It isn't actually necessary to work out if the end result is in range before performing the calculation in this case.
I guess -32768 / -1 (assuming a 16-bit int) would be a problem but that's the only case.

Whoops missed that.
All you have to do then though is check the value of the result. It isn't actually necessary to work out if the end result is in range before performing the calculation in this case.
I guess -32768 / -1 (assuming a 16-bit int) would be a problem but that's the only case.

That's assuming your type can hold the value it is not intended to represent. By definition, if you have an overflow or underflow situation, it is possible the type cannot represent the value of the result. Your conclusion does not work for types designed or specified to hold particular custom ranges.

Whoops missed that.
All you have to do then though is check the value of the result. It isn't actually necessary to work out if the end result is in range before performing the calculation in this case.
I guess -32768 / -1 (assuming a 16-bit int) would be a problem but that's the only case.

The problem is that if there is an actual overflow or underflow for the type because it cannot represent the actual value, then it's impossible to check the result.
Say, I use uint8_t.
If the result is 256, I get 0 instead. And 0 < integer_max, so by that, I get a valid number.

Originally Posted by grumpy

Rubbish. It is basic mathematics.

My apologies. My thinking was in error. Of course you're right.
But you've given me light. I don't think I'm used in thinking in terms of equations for computer mathematics problems...

However, the question remains: how to wrap the values correctly? If overflow/underflow occurs, the numbers must wrap around the maximum like it does with native integers.
We'd rather not emulate this at bit level.

The problem is that if there is an actual overflow or underflow for the type because it cannot represent the actual value, then it's impossible to check the result.

The trick is to do things to check if an operation would overflow/underflow BEFORE doing the operation. Mathematics is great for finding those things to do.

Originally Posted by Elysia

My apologies. My thinking was in error. Of course you're right.
But you've given me light. I don't think I'm used in thinking in terms of equations for computer mathematics problems...

Mathematics is all about describing a problem in a way that allows it to be solved, and finding alternative but equivalent operations if one method does not work for some reason. That philosophy is quite useful with programming.

Let's say, for sake of discussion, we want to throw an exception if multiplying two positive valued ints causes an overflow (again for sake of discussion, this means "produces a result greater than INT_MAX").

With handling of overflows or underflows, one technique is to do the operation (multiplication, division, etc), check if an overflow/underflow has occurred, and (if it has) decide what to do next (recovery action, etc). The practical problem is that techniques by which overflow/underflow are reported are machine dependent, so the methods of detecting them are too.

To meet our requirement, we could do this;

Code:

/* assume a and b are both > 0 and <= INT_MAX */
result = a*b;
if (overflow_detected(result))
throw OurException;

The problem with this is that the techniques of detecting an overflow are machine dependent (particularly as some machines wrap values, so the test is not simply "if (result > INT_MAX)". The techniques for recovery of such a condition are also machine dependent (more so for floating point, but still true for ints, as there is implementation defined behaviour).

Another way is to detect the potential overflow BEFORE doing the operation and, if a positive detection occurs, throw the exception and DO NOT do the computation. Since we are trying to detect a situation where a*b > INT_MAX, we can divide by one of the values. So the technique might be

Code:

/* assume a and b are both > 0 and <= INT_MAX */
if (INT_MAX/a < b) /* overflow would occur if we did multiplication */
throw OurException;
result = a*b;

This has the same net effect, but no overflow occurs that needs to be recovered from in a machine dependent manner.

The obvious problem of early detection is the overhead. In the example above, an integer division is needed in addition to the integer multiplication, solely for the purpose of checking for overflow. Division is considerably slower than multiplication, so you slow down the operation a lot.
Checking the overflow flag, on the other hand, incurs merely the penalty of a conditional jump.

They will always equal each other for all numbers but 0.
If we define m_Data = 2 and Data.m_Data = -2, then
In the first, the equation will be 2 < 0 / -2 = 2 < 0 (FALSE)
And the second will be 2 * -2 < 0 = -4 < 0 (TRUE)
So they will not match and this causes a problem.

Although, as for detection, a small overhead may be necessary. However, I have given the option not to check for overflow/underflow, which gets rid of that overhead entirely.
But fast overflow/underflow checking would be a good thing.

They will always equal each other for all numbers but 0.
If we define m_Data = 2 and Data.m_Data = -2, then
In the first, the equation will be 2 < 0 / -2 = 2 < 0 (FALSE)
And the second will be 2 * -2 < 0 = -4 < 0 (TRUE)
So they will not match and this causes a problem.

This has nothing to do with zero and everything to do with negative numbers. Recall that when you multiply or divide an inequality by a negative number, the inequality reverses. So in this case you multiply your inequality by Data.m_Data (to get it out of the denominator on the right). Since you multiply by a negative number, the inequality reverses. (Try it with 1 in place of the zero and see what happens.)

True. Usually, the better approach is a design that ensures the overflow will not occur. If a condition is prevented, no need to detect it before or after. Analysis of specific cases allows that, although it's difficult in general (eg for arbitrary input).

Checking the overflow flag only works on some systems. On some processors, a system exception is generated on overflow. Recovery from those suckers is non-trivial.

This has nothing to do with zero and everything to do with negative numbers. Recall that when you multiply or divide an inequality by a negative number, the inequality reverses. So in this case you multiply your inequality by Data.m_Data (to get it out of the denominator on the right). Since you multiply by a negative number, the inequality reverses. (Try it with 1 in place of the zero and see what happens.)

I get what you say (although you sure put it in difficult words), but sign changes does not work. Unless you magically can make the following equation work with a sign change:

That's assuming your type can hold the value it is not intended to represent. By definition, if you have an overflow or underflow situation, it is possible the type cannot represent the value of the result. Your conclusion does not work for types designed or specified to hold particular custom ranges.

Well that's just it, I am assuming that the data types he is using to represent all this are plaing old ints, chars, shorts, __inst64s etc. As long as the variable holding the result type is the same size then the division wont overflow, except in the case I gave, and you can perform the range check afterwards. From the point of view of his implementation, it will work, that's what I'm trying to say.
I'm only talking about the division case here of course.

Well that's just it, I am assuming that the data types he is using to represent all this are plaing old ints, chars, shorts, __inst64s etc. As long as the variable holding the result type is the same size then the division wont overflow, except in the case I gave, and you can perform the range check afterwards. From the point of view of his implementation, it will work, that's what I'm trying to say.
I'm only talking about the division case here of course.

Unless, of course, the number was a floating number...
But if it's an integer, I can relate to that you're right... A possible optimization!