So this a exercise from the book COMPUTERS SYSTEMS A PROGRAMMERS PERSPECTIVE
I need to add two signed numbers and in case it overflows or underflows return TMAX (011..1b)
or TMIN (100..0b)
respectively. In this case we can assumed two's complement representation.
The book imposes a set of rules that the solution must follow :
Forbidden:
- Conditionals, loops, function calls and macros.
- Division, modulus and multiplication.
- Relative comparison operators (
<
,>
,<=
and>=
).
Allowed operations:
- All bit level and logic operations.
- Left and right shifts, but only with shift amounts between 0 and w - 1
- Addition and subtraction.
- Equality (
==
) and inequality (!=
) tests. - Casting between
int
andunsigned
.
My code
int saturating_add(int x , int y) {
int sum = x + y;
int w = (sizeof(int) << 3) -1;
int mask = (~(x ^ y) & (y ^ sum) & (x ^ sum)) >> w;
int max_min = (1 << w) ^ (sum >> w);
return (~mask & sum) + (mask & max_min);
}
Compiled code in my machine
Note: I used the following command -> gcc -O2 -S sat_add.c
.
leal (%rdi,%rsi), %edx
movl %edx, %eax
movl %edx, %ecx
xorl %esi, %eax
xorl %edi, %esi
xorl %edx, %edi
notl %esi
sarl 31,ドル %ecx
andl %esi, %eax
addl $-2147483648, %ecx
andl %edi, %eax
sarl 31,ドル %eax
movl %eax, %esi
andl %ecx, %eax
notl %esi
andl %edx, %esi
leal (%rsi,%rax), %eax
ret
So I want to know if there is a better solution in terms of elegance and performance. Also if there is a solution that compiles to a single instruction in x86_64 (Maybe PADDS although this instruction may not be the one I am looking for). Also any other kind of feedback is welcome.
1 Answer 1
Undefined behavior from signed overflow
Technically, your first line causes undefined behavior:
int sum = x + y;
It should be written instead as:
int sum = (unsigned int) x + y;
In C, signed integer overflow is undefined behavior but unsigned integer overflow is not. Your compiler probably will treat the two lines above identically, but to be safe you should use the unsigned add.
Save a couple of instructions
This line here could be optimized:
int mask = (~(x ^ y) & (y ^ sum) & (x ^ sum)) >> w;
to this:
int mask = (~(x ^ y) & (x ^ sum)) >> w;
If x
and y
have the same sign, then you only need to check one of them against sum
instead of both of them. This saves 2 assembly instructions when you compile it.
int sum = x + y;
will overflow (and invoke undefined behavior) ifx + y
is greater thanINT_MAX
. But you might be able to salvage something by castingx
andy
tounsigned int
first, and then usingunsigned int
throughout. Care to try again? :) \$\endgroup\$