-1

Given an unsigned integer x and a signed integer dx, what's the most efficient way to perform x = x + dx while preventing over/underflow? I could do something like:

unsigned int x;
int dx;

...

long int result = (long int)(x + dx);
if (result < 0) {
    x = 0;
} else if (result > UINT_MAX) {
    x = UINT_MAX;
} else {
    x = result;
}

But I'm wondering if there's a better way.

EDIT: updated the example to be semantically correct.

Jeff L
  • 491
  • 5
  • 14
  • `x + dx < 0` cannot be true, as the result of `x + dx` is an unsigned integer, if both types have the same size. – mch Jun 11 '21 at 07:23
  • do you mean the result in mathematical result? because as @mch said in C the signed integer will be converted to `unsigned` if its size is smaller than or equal to the unsigned type – phuclv Jun 11 '21 at 07:29
  • Sorry, I didn't realize the example was semantically wrong. I would have to store the quantity `x + dx` in a larger (signed) type first. – Jeff L Jun 11 '21 at 07:33
  • The whole task doesn't make much sense to begin with since an unsigned addition with MSB set could be the result of the signed operand being negative, or simply that the value is large (but fits). Do you need signed or unsigned addition? You can't have both at once in the same expression, since they have different boundaries. – Lundin Jun 11 '21 at 07:34
  • 1
    what I'm trying to achieve is that I have an unsigned integer that logically ranges from `0` to `UINT_MAX`. I need to add a signed quantity, to that variable, but if the result would overflow, the output would be `UINT_MAX`. if the result would underflow, the result would be `0`. So to answer your question, the addition would be signed. – Jeff L Jun 11 '21 at 07:42
  • So what you actually want is signed addition, then storing the results in an unsigned variable? Your example code is still incorrect, it performs addition on unsigned types, so any sign info is lost. – Lundin Jun 11 '21 at 07:53
  • The sensible thing to do here is probably to forget all about using unsigned types and restrict the data to the range 0 to `INT_MAX`. Then check for overflow and < 0. – Lundin Jun 11 '21 at 08:00

2 Answers2

2

The only efficient way is to use inline assembly (add and check the processor flags which are not accessible in C) or to use compiler builtins.

Example:

int add(int a,  int b)
{
    int result;
    return __builtin_sadd_overflow(a,b,&result) ? result : INT_MAX;
}

unsigned uadd(unsigned a,  int b)
{
    unsigned result;
    return __builtin_uadd_overflow(a,b,&result) ? result : UINT_MAX;
}

and the resulting machine code:

add:
        add     edi, esi
        jo      .L3
        mov     eax, 2147483647
        ret
.L3:
        mov     eax, edi
        ret



uadd:
        add     edi, esi
        jc      .L9
        mov     eax, -1
        ret
.L9:
        mov     eax, edi
        ret
0___________
  • 60,014
  • 4
  • 34
  • 74
0

OP's approach fails at inception as x + dx does not benefit with extended integer width - the addition still uses unsigned math. Casting to long after the addition is moot.

// Not useful
long int result = (long int)(x + dx);
// Perhaps
long int result = x + (long int)dx;
// or
long long result = x + (long long)dx;

Using long addition provides no wider math when int and long have the save size. Perhaps long long? Even long long is not specified to be wider than int, yet it often.


What's the most efficient way to perform bounded addition?

I'll set aside most efficient and focus on correct portable functionality for all unsigned int x, int dx.

A variation on @unwind:

unsigned saturated_add_alt(unsigned int x, int dx) {
  if (dx >= 0) {
    if (x > UINT_MAX - dx) {
      return UINT_MAX;
    }
  } else if (x < (1 - dx)) { // Avoid x < -dx.  UB when dx == INT_MIN
    return 0;
  }
  return x + dx;
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256