Reputation: 24705
There are three variables with the following types
uint64_t old_addr, new_addr;
int delta;
and I want to do this assignment
new_addr = old_addr + delta;
However the problem is, when old_addr=915256
and delta=-6472064
, the new_addr
becoms 18446744069414584325
to fix that I have to check some things:
if ( delta < 0 ) {
if ( old_addr < abs(delta) )
new_addr = 0;
else
new_addr = old_addr + delta;
}
Is there a better and efficient way?
Upvotes: 1
Views: 1687
Reputation: 41625
This code is very simple and takes care of overflow in both directions.
#include <assert.h>
#include <inttypes.h>
#include <stdint.h>
static uint64_t saturated_add(uint64_t a, int delta) {
uint64_t result = a + delta;
if (delta < 0 && result > a) {
return 0;
} else if (delta > 0 && result < a) {
return -1;
} else {
return result;
}
}
int main() {
assert(saturated_add(915256, -6472064) == 0);
assert(saturated_add(100, -99) == 1);
assert(saturated_add(100, -100) == 0);
assert(saturated_add(100, -101) == 0);
assert(saturated_add(UINT64_C(0x1111222233334444), -0x33334445) == UINT64_C(0x11112221FFFFFFFF));
assert(saturated_add(-5, 6) == UINT64_C(-1));
assert(saturated_add(-5, 5) == UINT64_C(-1));
assert(saturated_add(-5, 4) == UINT64_C(-1));
assert(saturated_add(-5, 3) == UINT64_C(-2));
return 0;
}
Upvotes: 0
Reputation: 153919
The question is what values old_addr
and new_addr
can take. And why
they are uint64_t
, rather than simply int
. The simplest expression
would be:
new_addr = old_addr + std::min( delta, -static_cast<int>( old_addr ) );
, but if old_addr
can be greater than INT_MAX
, this won't work.
Otherwise, the rules of mixed signed/unsigned arithmetic in C/C++ are
such that you're probably safest using explicit if
s, and not risking
any mixed arithmetic before being sure of the values.
And note that on most machines, abs( delta )
will still be negative if
delta
is equal to INT_MIN
. To correctly handle all of the cases,
you'ld need something like:
if ( delta > 0 ) {
new_addr = std::numeric_limits<uin64_t>::max() - delta > old_addr
? old_addr + delta
: std::numeric_limits<uint64_t>::max();
} else if ( delta < 0 ) {
new_addr = old_addr != 0 && -(delta + 1) < old_addr - 1
? old_addr + delta
: 0;
} else {
new_addr = old_addr;
}
(Just off the top of my head. There could easily be an off by one error in there.)
Upvotes: 2
Reputation: 41625
This is called saturated addition, and some processors have special machine instructions for that. You could extract that code into an inline function, and depending on the target execution environment, use the machine instruction.
Instead of abs(delta)
you can simply write -delta
, since you already know that delta < 0
.
Upvotes: 4