44

What is the best (cleanest, most efficient) way to write saturating addition in C?

The function or macro should add two unsigned inputs (need both 16- and 32-bit versions) and return all-bits-one (0xFFFF or 0xFFFFFFFF) if the sum overflows.

Target is x86 and ARM using gcc (4.1.2) and Visual Studio (for simulation only, so a fallback implementation is OK there).

Peter Cordes
  • 245,674
  • 35
  • 423
  • 606
Frank Szczerba
  • 4,750
  • 3
  • 29
  • 30
  • 2
    MSalters's answer compiles to [by far the best code on x86](http://goo.gl/lcrN1X), equalling the best I can do with inline asm (actually better, because the compiler understands what's happening and can choose which operand will be the destination of the add). It's similarly pretty good on ARM. gcc doesn't seem to use ARM's add with unsigned saturation instruction, though. **MSalters's answer should be the accepted one**. – Peter Cordes Mar 13 '16 at 18:39
  • Unfortunately the victory seems to disappear with GCC 6 for the 16-bit adds16_msalters, with conditional jumps and everything. – user1649948 Feb 17 '17 at 03:08
  • Related: **signed saturation**: [Signed saturated add of 64-bit ints?](//stackoverflow.com/q/17580118) is a harder problem. [My answer there](https://stackoverflow.com/questions/17580118/signed-saturated-add-of-64-bit-ints/56531252#56531252) needed a GCC built-in function to compile efficiently; unlike the carry flag, it's hard to get compilers to use the signed-overflow flag output. – Peter Cordes Jun 11 '19 at 02:55

17 Answers17

27

You probably want portable C code here, which your compiler will turn into proper ARM assembly. ARM has conditional moves, and these can be conditional on overflow. The algorithm then becomes: add and conditionally set the destination to unsigned(-1), if overflow was detected.

uint16_t add16(uint16_t a, uint16_t b)
{
  uint16_t c = a + b;
  if (c < a)  /* Can only happen due to overflow */
    c = -1;
  return c;
}

Note that this differs from the other algorithms in that it corrects overflow, instead of relying on another calculation to detect overflow.

x86-64 clang 3.7 -O3 output for adds32: significantly better than any other answer:

add     edi, esi
mov     eax, -1
cmovae  eax, edi
ret

ARMv7: gcc 4.8 -O3 -mcpu=cortex-a15 -fverbose-asm output for adds32:

adds    r0, r0, r1      @ c, a, b
it      cs
movcs   r0, #-1         @ conditional-move
bx      lr

16bit: still doesn't use ARM's unsigned-saturating add instruction (UADD16)

add     r1, r1, r0        @ tmp114, a
movw    r3, #65535      @ tmp116,
uxth    r1, r1  @ c, tmp114
cmp     r0, r1    @ a, c
ite     ls        @
movls   r0, r1        @,, c
movhi   r0, r3        @,, tmp116
bx      lr  @
ib.
  • 25,324
  • 10
  • 71
  • 97
MSalters
  • 159,923
  • 8
  • 140
  • 320
  • 2
    This generates optimal code on x86 with clang (`mov eax,-1` / `add` / `cmovnc`), and [about the same with gcc](http://goo.gl/fQ2YI1), unlike all the other answers. It's the only one that gets gcc to use the flags result from the add, instead of doing another test afterwards (except for DGentry's answer, but gcc doesn't realize both tests are the same). So one could say it's the only one where gcc "understands" what's going on. Even inline asm can't do better on x86: the compiler knows what's going on with yours, so it knows it's associative, and can choose which reg to destroy. – Peter Cordes Mar 13 '16 at 17:43
  • @PeterCordes: Care to comment on the behaviour of more recent clang/gcc versions? Since clang 3.9 and gcc 6.1, the 16-bit version gets quite a lot bulkier. I convinced clang to produce the same code as you show by disabling `likely` but gcc seems more insistent. The 32-bit versions work as expected (again, disabling likely for clang) but I need a 16-bit saturating add. – rici Mar 09 '18 at 15:42
  • @rici: For unsigned 16-bit, if the compiler already has values zero-extended in registers, it might be optimal to do a 32-bit addition and just check `sum & (1UL<<16)` for carry-out. Compilers don't do an optimal job with this (by any means), but clang6.0's branchy version is interesting if the normal case is no overflow. https://godbolt.org/g/qrpPze. (It should use `lea` to copy-and-add, though.) If partial-register stalls for 16-bit regs don't exist (like on Haswell), clang's branchy version of this answer looks ok, too, but gcc's has a silly test (missed optimization should be reported). – Peter Cordes Mar 10 '18 at 02:11
  • These might end up different when inlining; branch layout would very likely be different when it's not just a stand-alone function. – Peter Cordes Mar 10 '18 at 02:11
  • @peter: my actual use case is comparing `z < clamped_subtract(h, 4)` where`z` is a size_t and `h` is a `uint16_t`. The existing code is `z + 4 < h`, but that of course fails if the addition overflows (hugely unlikely, but it's a glitch and I'd like to fix it. It's not in a critical path so I'm not too concerned but I was lookng to see if there was something better than two comparisons. – rici Mar 10 '18 at 02:44
24

In plain C:

uint16_t sadd16(uint16_t a, uint16_t b) {
  return (a > 0xFFFF - b) ? 0xFFFF : a + b;
}
     
uint32_t sadd32(uint32_t a, uint32_t b) {
  return (a > 0xFFFFFFFF - b) ? 0xFFFFFFFF : a + b;
}

which is almost macro-ized and directly conveys the meaning.

ib.
  • 25,324
  • 10
  • 71
  • 97
Remo.D
  • 15,217
  • 5
  • 41
  • 70
  • 11
    Nice. A nitpick--if I saw the name `sadd16` in some code, my first assumption would be that the `s` stands for `signed`. – Craig McQueen Jul 03 '10 at 12:51
  • @Craig McQueen Except for the fact that there would not be much reason to make it a function. – yingted May 10 '11 at 21:32
  • @Anonymous: Why not? Signed overflow/underflow isn't defined. – Joseph Garvin Mar 19 '12 at 22:01
  • @JosephGarvin Signed addition of unsigned ints? – yingted Mar 24 '12 at 21:14
  • 2
    @Anonymous: Craig is speaking from the standpoint of reading code where there is a call to sad16/32. You won't see the signature unless you find and open the header. – Joseph Garvin Mar 26 '12 at 15:18
  • @JosephGarvin `uint32_t x=5,y=6,z=sadd32(x,y);`? The types are visible. – yingted Mar 26 '12 at 17:17
  • @Anonymous: Often when you're reading a diff (e.g. for code review, or if you're sending patches to an open source mailing list) the call is far enough from the declaration of the parameters that you can't tell. – Joseph Garvin Mar 26 '12 at 20:16
  • @JosephGarvin except that most IDE's allow you to hover over a call (or something) to see the deceleration. – Cole Johnson Jun 16 '13 at 21:43
  • Would it make sense to inline this? `attribute((always_inline))` for GCC; `__forceinline` for MSVC – Cole Johnson Jun 16 '13 at 21:46
  • @ColeJohnson: Use `inline` instead. Don't use `__attribute__((always_inline))`, which is usually a way of second-guessing the optimizer. The `inline` keyword does what you actually want and is portable. – Dietrich Epp Jun 16 '13 at 22:13
  • @Dietrich Why not force inline it besides portability? Also with plain `inline`, you _aren't_ portable. MSVC requires `__inline`... – Cole Johnson Jun 17 '13 at 00:01
  • @ColeJohnson: You can `#define inline __inline` easily enough, if you want to support compilers with poor support for C, and as long as you're aware of the semantic differences. There is really no reason to use `always_inline` here, and a good reason not to: it might interfere with debugging. For the same reason, C programmers have ditched declaring `register` variables long ago: there's no point, except in perhaps very specific circumstances. – Dietrich Epp Jun 17 '13 at 02:00
  • 1
    @DietrichEpp Fair enough. I'm not gonna sit here and be given a lecture on something I already know. However, a _smart_ compiler would _not_ inline functions even if forced to when it's in debug mode. An example is MSVC. If you tell it to compiler for debug mode, it won't inline (even forced) functions. – Cole Johnson Jun 17 '13 at 03:55
  • @ColeJohnson: Sorry if my answer seemed like a lecture. But this isn't about how "smart" a compiler is, but just how it behaves, and GCC inlines an `always_inline` even when compiling with debugging symbols. – Dietrich Epp Jun 17 '13 at 04:53
  • 1
    @Dietrich That's stupid. I guess I never noticed because I work in MSVC, then port to GCC when done. – Cole Johnson Jun 17 '13 at 08:29
  • 1
    Just a minor suggestion: The `0xFF..` constants should be changed to the equivalent `UINTN_MAX` constants (or `(uintN_t) -1`). That way, it will only take a single search & replace to write the `sadd8` or `sadd64` functions. (And it doesn't require you to count the number of Fs in `0xFFFFFFFFFFFFFFFF` ;) – Alexandros Dec 04 '13 at 08:52
  • 1
    This produces nice code in gcc 5.1 when targetting armv4t, just 4 branchless instructions (two of them conditional). – Alexandre Pereira Nunes Jul 21 '15 at 18:50
  • 1
    This produces significantly worse code for x86 and ARM than MSalter's answer. Have a look at it for asm output (including a godbolt link comparing this with it.) – Peter Cordes Mar 13 '16 at 18:34
18

In IA32 without conditional jumps:

uint32_t sadd32(uint32_t a, uint32_t b)
{
#if defined IA32
  __asm
  {
    mov eax,a
    xor edx,edx
    add eax,b
    setnc dl
    dec edx
    or eax,edx
  }
#elif defined ARM
  // ARM code
#else
  // non-IA32/ARM way, copy from above
#endif
}
Cole Johnson
  • 8,415
  • 15
  • 44
  • 66
Skizz
  • 64,439
  • 10
  • 63
  • 105
  • The question asked for C, but anyway, nice code. Is `eax` returned by default as the function result? – tzot Sep 23 '08 at 14:36
  • 6
    If the question wanted portability, it shouldn't have specified x86 and ARM ;-) – Steve Jessop Sep 23 '08 at 14:40
  • 3
    That function is still portable - once the elif and else cases are filled in. Portable code doesn't mean that you can't optimise for particular platforms. – Arafangion May 18 '10 at 02:53
  • 3
    A proposed edit by YumeYao (which I've not pushed through, as it changes the nature of the answer): The 3 instructions (xor reg,reg; setne reg; dec reg;) can be replaced with one more efficient instruction (sbb reg,reg). – Marc Gravell Aug 22 '11 at 08:13
  • 1
    Two things: the `__asm` keyword is compiler-dependent. The standard doesn't specify a keyword for inline assembly. So this is _not_ portable in the sense that it is compiler-dependent. For example, the Intel C++ compiler is Windows only, so if you wrote portable code utilizing Itel C++ features, it wouldn't be portable. Another thing: inline assembly prevents compiler inlining. So this optimization doesn't really help if there is still the function call overhead... – Cole Johnson Jun 16 '13 at 21:51
  • "...:...?..." would be a conditional move instead of "jump". – WiSaGaN Mar 19 '15 at 15:43
  • 3
    This kinda sucks: first because it's MSVC inline-asm, so inputs / outputs have to go through memory. (Or if this no-return-statement with a value in eax works, then the function itself can't inline. The inputs have to go through memory regardless). Second, because `cmov` is better: shorter critical path because `mov eax, -1` is off the critical path, unlike `sbb`. – Peter Cordes Mar 13 '16 at 18:28
11

In ARM you may already have saturated arithmetic built-in. The ARMv5 DSP-extensions can saturate registers to any bit-length. Also on ARM saturation is usually cheap because you can excute most instructions conditional.

ARMv6 even has saturated addition, subtraction and all the other stuff for 32 bits and packed numbers.

On the x86 you get saturated arithmetic either via MMX or SSE.

All this needs assembler, so it's not what you've asked for.

There are C-tricks to do saturated arithmetic as well. This little code does saturated addition on four bytes of a dword. It's based on the idea to calculate 32 half-adders in parallel, e.g. adding numbers without carry overflow.

This is done first. Then the carries are calculated, added and replaced with a mask if the addition would overflow.

uint32_t SatAddUnsigned8(uint32_t x, uint32_t y) 
{
  uint32_t signmask = 0x80808080;
  uint32_t t0 = (y ^ x) & signmask;
  uint32_t t1 = (y & x) & signmask;
  x &= ~signmask;
  y &= ~signmask;
  x += y;
  t1 |= t0 & x;
  t1 = (t1 << 1) - (t1 >> 7);
  return (x ^ t0) | t1;
}

You can get the same for 16 bits (or any kind of bit-field) by changing the signmask constant and the shifts at the bottom like this:

uint32_t SatAddUnsigned16(uint32_t x, uint32_t y) 
{
  uint32_t signmask = 0x80008000;
  uint32_t t0 = (y ^ x) & signmask;
  uint32_t t1 = (y & x) & signmask;
  x &= ~signmask;
  y &= ~signmask;
  x += y;
  t1 |= t0 & x;
  t1 = (t1 << 1) - (t1 >> 15);
  return (x ^ t0) | t1;
}

uint32_t SatAddUnsigned32 (uint32_t x, uint32_t y)
{
  uint32_t signmask = 0x80000000;
  uint32_t t0 = (y ^ x) & signmask;
  uint32_t t1 = (y & x) & signmask;
  x &= ~signmask;
  y &= ~signmask;
  x += y;
  t1 |= t0 & x;
  t1 = (t1 << 1) - (t1 >> 31);
  return (x ^ t0) | t1;
}

Above code does the same for 16 and 32 bit values.

If you don't need the feature that the functions add and saturate multiple values in parallel just mask out the bits you need. On ARM you also want to change the signmask constant because ARM can't load all possible 32 bit constants in a single cycle.

Edit: The parallel versions are most likely slower than the straight forward methods, but they are faster if you have to saturate more than one value at a time.

Nils Pipenbrinck
  • 77,289
  • 24
  • 142
  • 216
  • 1
    I didn't see an *unsigned* saturation instruction for 32bit integers, only for [packed16 `UQUADD16` and packed8](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/BABFDJJH.html). There's a 32bit add with signed-saturation, though. Also, unfortunately this C code compiles to horrible code for the 32bit case: all the overhead of doing it SWAR style, but for only one value. It unfortunately doesn't optimize away. See my comment on MSalters's answer: the godbolt link includes your version. – Peter Cordes Mar 13 '16 at 18:23
10

If you care about performance, you really want to do this sort of stuff in SIMD, where x86 has native saturating arithmetic.

Because of this lack of saturating arithmetic in scalar math, one can get cases in which operations done on 4-variable-wide SIMD is more than 4 times faster than the equivalent C (and correspondingly true with 8-variable-wide SIMD):

sub8x8_dct8_c: 1332 clocks
sub8x8_dct8_mmx: 182 clocks
sub8x8_dct8_sse2: 127 clocks
Dark Shikari
  • 7,761
  • 3
  • 24
  • 37
  • 8
    Is using the SSE instructions still faster in cases where you're only ever operating on one variable at a time? – Joseph Garvin Mar 19 '12 at 16:05
  • @JosephGarvin: yes, it *can* be, if you needed saturating 16-bit or 8-bit add or subtract. Or bit-reverse (with SSSE3 pshufb for a per-nibble parallel lookup table). Or with SSE4.1, min or max on 32-bit integers (or abs) with a single instruction. Or 64-bit integer math in 32-bit code. But there's overhead in getting numbers between XMM and integer registers, so use with care. – Peter Cordes Jun 11 '19 at 02:41
10

Zero branch solution:

uint32_t sadd32(uint32_t a, uint32_t b)
{
    uint64_t s = (uint64_t)a+b;
    return -(s>>32) | (uint32_t)s;
}

A good compiler will optimize this to avoid doing any actual 64-bit arithmetic (s>>32 will merely be the carry flag, and -(s>>32) is the result of sbb %eax,%eax).

In x86 asm (AT&T syntax, a and b in eax and ebx, result in eax):

add %eax,%ebx
sbb %eax,%eax
or %ebx,%eax

8- and 16-bit versions should be obvious. Signed version might require a bit more work.

R.. GitHub STOP HELPING ICE
  • 195,354
  • 31
  • 331
  • 669
  • 2
    You'd hope a compiler would spot that, but they don't. clang/gcc/icc all do a crap job on [everything except MSalter's answer](http://goo.gl/lcrN1X). Yours compiles to `lea eax, [rdi+rsi]/ mov edx, edi / mov ecx, esi / add rdx, rcx / shr rdx, 32 / neg edx / or eax, edx` – Peter Cordes Mar 13 '16 at 18:19
7
uint32_t saturate_add32(uint32_t a, uint32_t b)
{
    uint32_t sum = a + b;
    if ((sum < a) || (sum < b))
        return ~((uint32_t)0);
    else
        return sum;
} /* saturate_add32 */

uint16_t saturate_add16(uint16_t a, uint16_t b)
{
    uint16_t sum = a + b;
    if ((sum < a) || (sum < b))
        return ~((uint16_t)0);
    else
        return sum;
} /* saturate_add16 */

Edit: Now that you've posted your version, I'm not sure mine is any cleaner/better/more efficient/more studly.

DGentry
  • 15,552
  • 7
  • 48
  • 65
  • Your answer looks like what I thought we should be doing, but like you said I'm not really sure which is better, which is why I figured I'd open it up to voting here. – Frank Szczerba Sep 23 '08 at 14:23
  • They both seem correct, therefore efficiency should decide. An extra comparison isn't obviously slower (or faster) than oversizing the addition. Do some efficiency tests for both solutions on both architectures and pick the faster one. – Rafał Dowgird Sep 23 '08 at 14:27
  • 1
    Is checking the sum against both inputs necessary? The limit case is (uint16_t)(0xffff + 1) which is both < 1 and < 0xffff, so it seems the second check can be avoided. – Frank Szczerba Sep 23 '08 at 15:05
  • You are right, the lost overflow bit is worth MAXINT+1, so the result of overflown addition is equal to a+b-(MAXINT+1), which is both less than a and less than b. – Rafał Dowgird Sep 23 '08 at 20:07
  • Why use `~((uint32_t)0)`? You're already including `` to get the `uint32_t` deceleration, so why not just use `UINT32_MAX`? – Cole Johnson Jun 16 '13 at 21:52
3

The current implementation we are using is:

#define sadd16(a, b)  (uint16_t)( ((uint32_t)(a)+(uint32_t)(b)) > 0xffff ? 0xffff : ((a)+(b)))
#define sadd32(a, b)  (uint32_t)( ((uint64_t)(a)+(uint64_t)(b)) > 0xffffffff ? 0xffffffff : ((a)+(b)))
Frank Szczerba
  • 4,750
  • 3
  • 29
  • 30
3

I'm not sure if this is faster than Skizz's solution (always profile), but here's an alternative no-branch assembly solution. Note that this requires the conditional move (CMOV) instruction, which I'm not sure is available on your target.


uint32_t sadd32(uint32_t a, uint32_t b)
{
    __asm
    {
        movl eax, a
        addl eax, b
        movl edx, 0xffffffff
        cmovc eax, edx
    }
}
Adam Rosenfield
  • 360,316
  • 93
  • 484
  • 571
  • 2
    ARM has "C-everything". Not just jump and move. But it doesn't have support for 32 bits constants. So you'd want a conditional mov 0, followed by a conditional sub 1 – MSalters Oct 03 '08 at 11:17
  • ARM can create small negative numbers with `mvn` (mov-NOT) with an immediate. Assemblers know how to use this for you, e.g. `adds r0, r1` (add and set flags) / ``movCS r0, #-1` (mvn 0 = -1 if Carry Set). xD, MSalter's own answer posted later shows that compilers already do exactly that. And also emit this for x86, so you don't have to. And in a way that can inline and constant-propagate. – Peter Cordes Jun 11 '19 at 02:37
2

The best performance will usually involve inline assembly (as some have already stated).

But for portable C, these functions only involve one comparison and no type-casting (and thus I believe optimal):

unsigned saturate_add_uint(unsigned x, unsigned y)
{
    if (y > UINT_MAX - x) return UINT_MAX;
    return x + y;
}

unsigned short saturate_add_ushort(unsigned short x, unsigned short y)
{
    if (y > USHRT_MAX - x) return USHRT_MAX;
    return x + y;
}

As macros, they become:

SATURATE_ADD_UINT(x, y) (((y)>UINT_MAX-(x)) ? UINT_MAX : ((x)+(y)))
SATURATE_ADD_USHORT(x, y) (((y)>SHRT_MAX-(x)) ? USHRT_MAX : ((x)+(y)))

I leave versions for 'unsigned long' and 'unsigned long long' as an exercise to the reader. ;-)

ib.
  • 25,324
  • 10
  • 71
  • 97
Kevin
  • 22,827
  • 15
  • 51
  • 56
2

Just in case someone wants to know an implementation without branching using 2's complement 32bit integers.

Warning! This code uses the undefined operation: "shift right by -1" and therefore exploits the property of the Intel Pentium SAL instruction to mask the count operand to 5 bits.

int32_t sadd(int32_t a, int32_t b){
    int32_t sum = a+b;
    int32_t overflow = ((a^sum)&(b^sum))>>31;
    return (overflow<<31)^(sum>>overflow);
 }

It's the best implementation known to me

IKavanagh
  • 5,704
  • 11
  • 38
  • 44
Hannodje
  • 21
  • 1
  • You can write `overflow&31`, and it will still [compile without a wasted `and ecx, 31`](http://goo.gl/AhGE9D), because gcc and clang know how the shift instruction works (the ISA defines it to work that way, on every CPU since 286. See the Intel insn ref manual linked from the [x86 tag wiki](http://stackoverflow.com/tags/x86/info). On targets where the shift works differently way, they will emit the necessary instructions to make it work. Of course, this still relies on right-shift of a signed integer using an arithmetic shift, which the C standard doesn't guarantee. – Peter Cordes Mar 13 '16 at 16:53
  • 1
    This also uses the undefined operation of `a+b` overflowing! Signed overflow is UB in C and C++. – Peter Cordes Jun 10 '19 at 16:45
1

I suppose, the best way for x86 is to use inline assembler to check overflow flag after addition. Something like:

add eax, ebx
jno @@1
or eax, 0FFFFFFFFh
@@1:
.......

It's not very portable, but IMHO the most efficient way.

Igor Semenov
  • 1,548
  • 9
  • 7
  • I think the answer for ARM is similar (and even more efficient with conditional ops), but I'm hoping someone knows a pattern that will trick GCC into generating something close to this. – Frank Szczerba Sep 23 '08 at 14:26
  • @Frank, which GCC versin are you using? (gcc --version). The newer versions do such tricks. – Nils Pipenbrinck Sep 23 '08 at 14:31
  • `jno` checks for *signed* overflow. `jnc` would check for unsigned wraparound like this Q wants, which would match with `mov eax, -1` (or your short form with a false dependency; `or eax, -1`). But if you're going to introduce a data dependency on the add, defeating the benefit for branch-prediction + speculative execution, you might use `sbb edx,edx` / `or eax, edx` to broadcast CF to all bits and OR that in. But CMOVC would be more efficient, only 1 or 2 uops on the critical path instead of 2 or 3. – Peter Cordes Jun 11 '19 at 02:31
1

An alternative to the branch free x86 asm solution is (AT&T syntax, a and b in eax and ebx, result in eax):

add %eax,%ebx
sbb $0,%ebx
Ian Rogers
  • 19
  • 2
  • 1
    `sbb $0, %ebx` subtracts 1 or not. This gives the wrong answer if the add overflowed more than 1. What does work (as suggested by others) is using `sbb same,same` to produce as 0 or -1 mask, and OR the add result with that. However, that has a longer critical-path latency [than `add %edi, %esi / mov $-1, %eax / cmovnc %esi, %edi`](http://goo.gl/6vKnrr). (sbb and cmov have the same latency on all CPUs: 2 on Intel pre-Broadwell, and 1 otherwise.) – Peter Cordes Mar 13 '16 at 16:39
1
int saturating_add(int x, int y)
{
    int w = sizeof(int) << 3;
    int msb = 1 << (w-1);

    int s = x + y;
    int sign_x = msb & x;
    int sign_y = msb & y;
    int sign_s = msb & s;

    int nflow = sign_x && sign_y && !sign_s;
    int pflow = !sign_x && !sign_y && sign_s;

    int nmask = (~!nflow + 1);
    int pmask = (~!pflow + 1);

    return (nmask & ((pmask & s) | (~pmask & ~msb))) | (~nmask & msb);
}

This implementation doesn't use control flows, campare operators(==, !=) and the ?: operator. It just uses bitwise operators and logical operators.

Shangchih Huang
  • 299
  • 1
  • 11
0

Using C++ you could write a more flexible variant of Remo.D's solution:

template<typename T>
T sadd(T first, T second)
{
    static_assert(std::is_integral<T>::value, "sadd is not defined for non-integral types");
    return first > std::numeric_limits<T>::max() - second ? std::numeric_limits<T>::max() : first + second;
}

This can be easily translated to C - using the limits defined in limits.h. Please also note that the Fixed width integer types might not been available on your system.

0xbadf00d
  • 14,584
  • 15
  • 60
  • 93
0
//function-like macro to add signed vals, 
//then test for overlow and clamp to max if required
#define SATURATE_ADD(a,b,val)  ( {\
if( (a>=0) && (b>=0) )\
{\
    val = a + b;\
    if (val < 0) {val=0x7fffffff;}\
}\
else if( (a<=0) && (b<=0) )\
{\
    val = a + b;\
    if (val > 0) {val=-1*0x7fffffff;}\
}\
else\
{\
    val = a + b;\
}\
})

I did a quick test and seems to work, but not extensively bashed it yet! This works with SIGNED 32 bit. op : the editor used on the web page does not let me post a macro ie its not understanding non-indented syntax etc!

0

Saturation arithmetic is not standard for C, but it's often implemented via compiler intrinsics, so the most efficient way will not be the cleanest. You must add #ifdef blocks to select the proper way. MSalters's answer is the fastest for x86 architecture. For ARM you need to use __qadd16 function (ARM compiler) of _arm_qadd16 (Microsoft Visual Studio) for 16 bit version and __qadd for 32-bit version. They'll be automatically translated to one ARM instruction.

Links:

phuclv
  • 27,258
  • 11
  • 104
  • 360