0

I have a value, of type signed int, which is always clamped between, for example, ±1073741824 (2^30). I should be able to safely add or subtract any arbitrary value, and if the sum/difference is outside the range it is clamped (any value above the maximum maps to the maximum, and any value below the minimum maps to the minimum). I have the following:

signed int Min(signed int X, signed int Y) {
    return X < Y ? X : Y;
}
signed int Max(signed int X, signed int Y) {
    return X > Y ? X : Y;
}
signed int X;
void UpdateX(signed int Delta) {
    X = Min(Max(X+Delta, -(1<<30)), (1<<30));
}

However, if Delta and/or X is sufficiently large or small that the value overflows or underflows, this invokes Undefined Behavior because there is a signed int overflow before the clamping. One solution is to use a larger integer type temporarily, but this is not an option for other cases where the largest available type is already being used. How can I safely add then clamp a signed int without risking invoking undefined behavior?

user16217248
  • 3,119
  • 19
  • 19
  • 37
  • @Exampleperson If X is the maximum 1073741824 and if Delta is also 1073741824, then the sum will be 2147483648 which is beyond the integer maximum and signed integer overflow is undefined – user16217248 Feb 22 '22 at 17:26
  • 1
    As a side note, if you want your type to be 32 bit in size, use int32_t, not int. – Fredrik Feb 22 '22 at 17:26
  • 1
    @Exampleperson Casting to unsigned int and casting back will remove the undefined behavior but I am guessing it will still give incorrect results if overflow occurs – user16217248 Feb 22 '22 at 17:32
  • @Exampleperson Then the result will be 0 instead of the correct maximum – user16217248 Feb 22 '22 at 17:33
  • Simple answer for 64-bit processes: convert 32-bit inputs to `int64_t`, do the calculation as `int64_t`, compare, and clamp. Then downcast to `int32_t`. – Andrew Henle Feb 22 '22 at 17:40
  • @AndrewHenle they have said that it is not an option as they may be using the largest integer available. – Example person Feb 22 '22 at 17:41
  • @AndrewHenle I thought of that, but what about *other* cases where I am already using the largest available integer type? – user16217248 Feb 22 '22 at 17:41

1 Answers1

2

You're right to worry about overflow. The obvious techniques for checking for overflow detect it after it's happened, which is of course too late, if there's any undefined behavior involved.

The standard technique is to rearrange the test. Rather than saying:

if(X + Delta > MAX) { whoops! it overflowed; }

subtract Delta from both sides so you have

if(X > MAX - Delta) { whoops! it overflowed; }

Of course you also have to consider the possibility that Delta is negative. So in your case I believe something like this will do it:

#define MAX (1<<30)

void UpdateX(signed int Delta) {
    if(Delta >= 0) X = (X <=  MAX - Delta) ? X + Delta :  MAX;
    else           X = (X >= -MAX - Delta) ? X + Delta : -MAX;
}

See also How to check for signed integer overflow in C without undefined behaviour?

See also Question 20.6b in the C FAQ list.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103