51 votes

Comment faire une addition saturante non signée en C ?

Quelle est la meilleure façon (la plus propre, la plus efficace) d'écrire une addition saturante en C ?

La fonction ou la macro doit additionner deux entrées non signées (il faut des versions 16 et 32 bits) et renvoyer la valeur un (0xFFFF ou 0xFFFFFF) si la somme dépasse la limite.

La cible est x86 et ARM et utilise gcc (4.1.2) et Visual Studio (pour la simulation seulement, donc une implémentation de repli est OK ici).

2 votes

La réponse de MSalters se résume à de loin le meilleur code sur x86 Le compilateur comprend ce qui se passe et peut choisir l'opérande qui sera la destination de l'addition. C'est également assez bon sur ARM. gcc ne semble pas utiliser l'instruction add with unsigned saturation d'ARM, cependant. La réponse de MSalters devrait être la réponse acceptée. .

0 votes

Malheureusement la victoire semble disparaître avec GCC 6 pour les adds16_msalters 16 bits, avec les sauts conditionnels et tout.

0 votes

En rapport : saturation signée : Addition saturée signée d'ints 64 bits ? est un problème plus difficile. Ma réponse avait besoin d'une fonction intégrée à GCC pour compiler efficacement ; contrairement au drapeau de report, il est difficile d'obtenir des compilateurs qu'ils utilisent la sortie du drapeau de dépassement signé.

26voto

Remo.D Points 9841

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

qui est presque macroscopique et transmet directement le sens.

12 votes

Joli. Un petit détail si je voyais le nom sadd16 dans un certain code, ma première hypothèse serait que la s signifie signed .

0 votes

@Craig McQueen A part le fait qu'il n'y aurait pas beaucoup de raisons d'en faire une fonction.

0 votes

@Anonymous : Pourquoi pas ? Le débordement/sous-débordement signé n'est pas défini.

18voto

Skizz Points 30682

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

0 votes

La question demandait du C, mais bon, joli code. Est-ce que eax retourné par défaut comme résultat de la fonction ?

6 votes

Si la question voulait la portabilité, elle n'aurait pas dû spécifier x86 et ARM ;-)

3 votes

Cette fonction est toujours portable - une fois que les cas elif et else sont remplis. Un code portable ne signifie pas que vous ne pouvez pas l'optimiser pour des plateformes particulières.

13voto

Nils Pipenbrinck Points 41006

En ARM, vous avez peut-être déjà intégré l'arithmétique saturée. Les extensions DSP ARMv5 peuvent saturer les registres à n'importe quelle longueur de bit. De plus, sur ARM, la saturation est généralement bon marché car vous pouvez excuter la plupart des instructions conditionnelles.

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

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

Tout cela nécessite un assembleur, ce n'est donc pas ce que vous avez demandé.

Il existe également 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 pour additionner des nombres sans débordement de retenue.

Ceci est fait en premier. Ensuite, les reports sont calculés, additionnés et remplacés par un masque si l'addition déborde.

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 n'importe quel type de champ de bits) en modifiant la constante signmask et les décalages 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 la même chose pour les valeurs de 16 et 32 bits.

Si vous n'avez pas besoin de la fonction d'addition et de saturation de plusieurs valeurs en parallèle, masquez simplement les bits dont vous avez besoin. Sur ARM, vous devez également modifier la constante signmask car ARM ne peut pas charger toutes les constantes 32 bits possibles en un seul cycle.

Editar: Les versions parallèles sont très probablement plus lentes que les méthodes directes, mais elles sont plus rapides si vous devez saturer plus d'une valeur à la fois.

1 votes

Je n'ai pas vu de non signé instruction de saturation pour les entiers 32 bits, uniquement pour les emballé16 UQUADD16 et emballé8 . Il y a une addition 32bit avec une saturation signée, cependant. De plus, malheureusement, ce code C se compile en un code horrible pour le cas 32bit : toute la surcharge du style SWAR, mais pour une seule valeur. Malheureusement, il n'est pas optimisé. Voir mon commentaire sur la réponse de MSalters : le lien godbolt inclut votre version.

10voto

Dark Shikari Points 6178

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

En raison de cette absence de saturation arithmétique dans les mathématiques scalaires, on peut obtenir des cas dans lesquels les opérations effectuées sur des SIMD à 4 variables sont les suivantes plus plus de 4 fois plus rapide que le C équivalent (et de manière correspondante avec 8-variables SIMD) :

sub8x8_dct8_c: 1332 clocks
sub8x8_dct8_mmx: 182 clocks
sub8x8_dct8_sse2: 127 clocks

8 votes

L'utilisation des instructions SSE est-elle toujours plus rapide dans les cas où l'on n'agit que sur une seule variable à la fois ?

0 votes

@JosephGarvin : oui, c'est peut être, si vous aviez besoin d'une saturation d'addition ou de soustraction 16 bits ou 8 bits. Ou d'inverser les bits (avec SSSE3 pshufb pour une table de conversion parallèle par bit). Ou avec SSE4.1, min ou max sur des entiers 32 bits (ou abs) avec une seule instruction. Ou des calculs sur des entiers 64 bits dans du code 32 bits. Mais il y a une surcharge dans le transfert des nombres entre les registres XMM et les registres entiers, donc à utiliser avec précaution.

7voto

DGentry Points 10759
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 */

Editar: Maintenant que vous avez posté votre version, je ne suis pas sûr que la mienne soit plus propre/meilleure/efficace/étonnante.

0 votes

Votre réponse ressemble à ce que je pensais que nous devrions faire, mais comme vous l'avez dit, je ne suis pas vraiment sûr de ce qui est le mieux, c'est pourquoi je me suis dit que j'allais ouvrir le vote ici.

0 votes

Les deux semblent corrects, c'est donc l'efficacité qui doit décider. Une comparaison supplémentaire n'est pas manifestement plus lente (ou plus rapide) que le surdimensionnement de l'addition. Faites quelques tests d'efficacité pour les deux solutions sur les deux architectures et choisissez la plus rapide.

1 votes

Est-il nécessaire de vérifier la somme par rapport aux deux entrées ? Le cas limite est (uint16_t)(0xffff + 1) qui est à la fois < 1 et < 0xffff, il semble donc que la deuxième vérification puisse être évitée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X