Comment faire l'addition saturante en C?

Quel est le meilleur moyen (le plus propre et le plus efficace) d'écrire l'addition saturante en C?

La fonction ou la macro doit ajouter deux entrées non signées (nécessite des versions 16 et 32 bits) et renvoyer all - bits-one (0xFFFF ou 0xFFFFFFFF) si la somme déborde.

La cible est x86 et ARM en utilisant gcc (4.1.2) et Visual Studio (pour la simulation seulement, donc une implémentation de secours est OK là).

38

17 réponses

Vous voulez probablement du code C portable ici, que votre compilateur transformera en un assemblage ARM approprié. ARM a des mouvements conditionnels, et ceux-ci peuvent être conditionnels au débordement. L'algorithme devient alors add, et conditionnellement définir la destination à unsigned (-1) si un dépassement a été détecté.

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;
}

Notez que cela diffère des autres algorithmes en ce sens qu'il corrige le débordement, au lieu de s'appuyer sur un autre calcul pour détecter le débordement.

X86-64 clang 3.7 - O3 sortie pour adds32: nettement mieux que toute autre réponse:

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

ARMv7: gcc 4.8 -O3 -mcpu=cortex-a15 -fverbose-asm sortie pour adds32:

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

16bit: n'utilisez pas de BRAS non signé-saturation ajouter l'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  @
16
répondu MSalters 2016-03-13 18:17:14

En C Simple:

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;} 

Qui est presque macro-isé et transmet directement le sens.

24
répondu Remo.D 2008-09-23 17:09:27

Dans IA32 sans sauts conditionnels:

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
}
17
répondu Skizz 2013-06-16 21:47:48

Dans ARM, vous avez peut-être déjà intégré l'arithmétique saturée. Les ARMv5 DSP-extensions peuvent saturer les registres à n'importe quelle longueur de bits. Aussi sur la saturation du bras est généralement pas cher parce que vous pouvez excuser la plupart des instructions conditionnelles.

ARMv6 a même l'addition saturée, la soustraction et tous les autres trucs pour 32 bits et des nombres emballés.

Sur le x86, vous obtenez une arithmétique saturée via MMX ou SSE.

Tout cela a besoin d'assembleur, donc ce n'est pas ce que vous avez demandé pour.

Il y a aussi des astuces C pour faire de l'arithmétique saturée. Ce petit code fait une addition saturée sur quatre octets d'un dword. Il est basé sur l'idée de calculer 32 demi-additionneurs en parallèle, par exemple en ajoutant des nombres sans débordement de report.

Ceci est fait en premier. Ensuite, les carries sont calculés, ajoutés et remplacés par un masque si l'addition débordait.

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;
}

Vous pouvez obtenir la même chose pour 16 bits (ou tout type de champ de bits) en changeant la constante signmask et le se déplace en bas comme ceci:

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;
}

Le code ci-dessus fait de même pour les valeurs de 16 et 32 bits.

Si vous n'avez pas besoin de la fonctionnalité que les fonctions ajoutent et saturent plusieurs valeurs en parallèle, masquez simplement les bits dont vous avez besoin. Sur ARM, vous voulez également changer la constante signmask car ARM ne peut pas charger toutes les constantes 32 bits possibles en un seul cycle.

Edit: les versions parallèles sont probablement plus lentes que les méthodes simples, mais elles sont plus rapides si vous avez saturer plus d'une valeur à la fois.

11
répondu Nils Pipenbrinck 2008-09-23 14:39:02

Si vous vous souciez des performances, vous voulez vraiment faire ce genre de choses dans SIMD, où x86 a une arithmétique saturante native.

En raison de ce manque d'arithmétique saturante en mathématiques scalaires, on peut obtenir des cas dans lesquels les opérations effectuées sur SIMD à 4 variables sont Plus que 4 fois plus rapides que l'équivalent C (et par conséquent vrai avec SIMD à 8 variables):

sub8x8_dct8_c: 1332 clocks
sub8x8_dct8_mmx: 182 clocks
sub8x8_dct8_sse2: 127 clocks
10
répondu Dark Shikari 2008-09-24 07:09:37

Solution de branche Zéro:

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

Un bon compilateur optimisera cela pour éviter de faire une arithmétique 64 bits réelle ({[2] } sera simplement le drapeau de portage, et -(s>>32) est le résultat de sbb %eax,%eax).

En asm x86 (AT&T syntaxe, a et b dans eax et ebx, résultat dans eax):

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

Les versions 8 et 16 bits devraient être évidentes. Version signée peut nécessiter un peu plus de travail.

9
répondu R.. 2010-08-07 19:12:30
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: Maintenant que vous avez posté votre version, Je ne suis pas sûr que la mienne soit plus propre / meilleure / plus efficace/plus studly.

7
répondu DGentry 2008-09-23 14:17:08

Je ne suis pas sûr que ce soit plus rapide que la solution de Skizz (toujours profil), Mais voici une solution alternative d'assemblage sans branche. Notez que cela nécessite l'instruction conditionnelle move (CMOV), que je ne suis pas sûr d'être disponible sur votre cible.


uint32_t sadd32(uint32_t a, uint32_t b)
{
    __asm
    {
        movl eax, a
        addl eax, b
        movl edx, 0xffffffff
        cmovc eax, edx
    }
}
3
répondu Adam Rosenfield 2008-09-23 17:30:24

L'implémentation actuelle que nous utilisons est:

#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)))
2
répondu Frank Szczerba 2008-09-23 14:18:30

Je suppose que la meilleure façon pour x86 est d'utiliser l'assembleur en ligne pour vérifier l'indicateur de débordement après l'addition. Quelque chose comme:

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

Ce n'est pas très portable, mais à mon humble avis le moyen le plus efficace.

2
répondu Igor Semenov 2008-09-23 14:24:19

Les meilleures performances impliquent généralement un assemblage en ligne (comme certains l'ont déjà indiqué).

Mais pour le portable C, ces fonctions n'impliquent qu'une seule comparaison et aucun type-casting (et donc je crois 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;
}

En tant que macros, elles deviennent:

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)))

Je laisse les versions pour "unsigned long" et "unsigned long long" comme un exercice pour le lecteur. ;-)

2
répondu Kevin 2008-09-24 00:40:56

Juste au cas où quelqu'un voudrait connaître une implémentation sans brancher en utilisant les entiers 32 bits du complément 2.

Attention! Ce code utilise l'opération indéfinie: "shift right by -1" et exploite donc la propriété de l'instruction Intel Pentium SAL pour masquer l'opérande count à 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);
 }

C'est la meilleure implémentation que je connaisse

2
répondu Hannodje 2015-10-01 08:57:21

En utilisant C++ , vous pouvez écrire une variante plus flexible de Remo.Solution de D :

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;
}

Cela peut être facilement traduit en C-en utilisant les limites définies dans limits.h. Veuillez également noter que les types entiers à Largeur fixe peuvent ne pas être disponibles sur votre système.

0
répondu 0xbadf00d 2014-06-17 12:08:27

Une alternative à la solution ASM x86 sans branche est (syntaxe AT&T, A et B dans EAX et ebx, résultat dans eax):

add %eax,%ebx
sbb $0,%ebx
0
répondu Ian Rogers 2015-01-21 18:28:59
//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;\
}\
})

J'ai fait un test rapide et semble fonctionner, mais pas beaucoup défoncé encore! Cela fonctionne avec signé 32 bits. op: l'éditeur utilisé sur la page web ne me laisse pas poster une macro, c'est-à-dire ne pas comprendre la syntaxe Non indentée, etc.!

0
répondu twostickes 2016-03-08 20:58:30
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);
}

Cette implémentation n'utilise pas de flux de contrôle, les opérateurs campare(==, !=) et l'opérateur ?:. Il utilise simplement des opérateurs binaires et des opérateurs logiques.

0
répondu Shangchih Huang 2017-09-22 06:49:05

L'arithmétique de Saturation n'est pas standard pour C, mais souvent elle est implémentée via les intrinsèques du compilateur, donc le moyen le plus efficace ne sera pas le plus propre. Vous devez ajouter des blocs # ifdef pour sélectionner la bonne façon. La réponse de MSalters est la plus rapide pour l'architecture x86. Pour ARM, vous devez utiliser la fonction _ _ qadd16 (compilateur ARM) de _arm_qadd16 (Microsoft Visual Studio) pour la version 16 bits et __qadd pour la version 32 bits. Il sera automatiquement traduit à un bras instruction.

Liens:

__qadd16 http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0491c/CJAICDDF.html

_arm_qadd16 https://msdn.microsoft.com/en-US/library/hh875058.aspx

__qadd http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0472m/chr1359125002575.html

0
répondu Alexei Shcherbakov 2018-09-19 17:57:03