web-dev-qa-db-fra.com

Comment faire une addition saturée non signée dans C?

Quelle est la meilleure solution (plus propre, la plus efficace) d'écrire un ajout saturation dans C?

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

La cible est x86 et ARM à l'aide de GCC (4.1.2) et Visual Studio (pour la simulation uniquement, une implémentation de repli est correcte là-bas).

40
Frank Szczerba

Vous voulez probablement du code portable C ici, que votre compilateur se transformera en mode approprié ARM Assemblage. ARM a des mouvements conditionnels et ceux-ci peuvent être utilisés. conditionnel sur le débordement. L'algorithme devient alors ajouter et définir de manière conditionnelle la destination sur non signé (-1) si le débordement 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 : significativement 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'utilise toujours pas l'instruction Ajout d'ajout de Saturature de STRA (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  @
22
MSalters

Dans ordinaire 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 macro-ized et transmet directement la signification.

24
Remo.D

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
}
18
Skizz

In ARM Vous avez peut-être déjà une saturée arithmétique intégrée. Les extensions DSP-ARMV5 peuvent étiser des registres à une longueur de bit. Également sur ARM saturation est généralement pas cher parce que vous pouvez excuser la plupart des instructions conditionnelles.

ARMV6 a même une addition saturée, une soustraction et toutes les autres choses 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é.

Il y a aussi des trucs C-astuces de faire des arithmétiques saturés. Ce petit code saturé sur quatre octets d'un Dword. Il est basé sur l'idée de calculer 32 demi-advecteurs en parallèle, par exemple. Ajout de chiffres sans débordement.

Ceci est fait en premier. Ensuite, les transactions sont calculées, ajoutées et remplacées 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 tout type de champ de bit) en modifiant la constante de signalisation et les décalages dans le 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 16 et 32 ​​bits.

Si vous n'avez pas besoin de la fonctionnalité que les fonctions ajoutez et saturer plusieurs valeurs en parallèle masquez simplement les bits dont vous avez besoin. On ARM Vous souhaitez également modifier la constante de signalisation car ARM Je ne peux pas charger toutes les constantes 32 bits possibles dans un seul cycle.

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

11
Nils Pipenbrinck

Solution zéro branche:

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 ceci pour éviter de faire un arithmétique 64 bits réel (s>>32 Sera simplement le drapeau de transport et -(s>>32) est le résultat de sbb %eax,%eax).

Dans x86 ASM (syntaxe AT & T, a et b in eax et ebx, aboutissez à eax):

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

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

Si vous vous souciez de la performance, vous vraiment Voulez-vous faire ce genre de choses dans SIMD, où X86 a une arithmétique saturation indigène.

En raison de ce manque d'arithmétique saturation dans les mathématiques scalaires, on peut obtenir des cas dans lesquels des opérations effectuées sur une SIMD à 4 variables sont plus que 4 fois plus rapides que l'équivalent c (et correspondant de manière correspondante avec 8- SIMD à large variable):

sub8x8_dct8_c: 1332 clocks
sub8x8_dct8_mmx: 182 clocks
sub8x8_dct8_sse2: 127 clocks
10
Dark Shikari
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 */

Éditer: Maintenant que vous avez posté votre version, je ne suis pas sûr que le mien est un nettoyant/meilleur/plus efficace/plus STANDIER.

7
DGentry

La mise en œuvre actuelle que nous utilisons est la suivante:

#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)))
3
Frank Szczerba

Je ne sais pas si cela est plus rapide que Skizz's Solution (Toujours profiler), mais voici une solution d'assemblage sans branche alternative. Notez que cela nécessite l'instruction de déplacement conditionnel (CMOV), ce que je ne suis pas sûr est 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
Adam Rosenfield

La meilleure performance impliquera généralement l'assemblage en ligne (comme certains déjà énoncés).

Mais pour Portable C, ces fonctions n'impliquent qu'une comparaison et aucun casting de type (et donc je crois que 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, ils 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 des versions pour "non signé longtemps" et "non signé longtemps" comme un exercice au lecteur. ;-)

2
Kevin

Juste au cas où quelqu'un voudrait connaître une mise en œuvre sans ramification en utilisant des entiers de 3 32 bits de 2.

Avertissement! Ce code utilise le fonctionnement non défini: "Décalage à droite par -1" et exploite donc la propriété de INTEL PENTUM SLING INSTRUCTION pour masquer le compteur opérant à 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 mise en œuvre de moi

2
Hannodje

Une alternative à la solution d'ASM X86 sans succursale est (Syntaxe AT & T, A et B dans EAX et EBX, aboutissant à EAX):

add %eax,%ebx
sbb $0,%ebx
1
Ian Rogers

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

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

Ce n'est pas très portable, mais imho la manière la plus efficace.

1
Igor Semenov

La saturation arithmétique n'est pas standard pour C, mais elle est souvent mise en œuvre via le compilateur intrinsèque, la manière la plus efficace ne sera donc pas la plus propre. Vous devez ajouter #ifdef Blocs pour sélectionner la manière appropriée. La réponse de Msalters est la plus rapide de l'architecture X86. Pour ARM Vous devez utiliser __qadd16 fonction (compilateur de bras) de _arm_qadd16 (Microsoft Visual Studio) pour la version 16 bits et __qadd pour la version 32 bits. Ils seront automatiquement traduits en un ARM.

Liens:

0
Alexei Shcherbakov
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 mise en œuvre n'utilise pas les flux de contrôle, les opérateurs de Campare (==, !=) et le ?: Opérateur. Il utilise simplement des opérateurs bitwises et des opérateurs logiques.

0
Shangchih Huang
//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 travailler, mais pas encore beaucoup l'attaquée! Cela fonctionne avec SIGNÉ 32 BITS. OP: l'éditeur utilisé sur la page Web ne me permet pas de poster une macro, c'est-à-dire sa syntaxe non indenter, etc.

0
twostickes

Utilisation de C++, vous pouvez écrire une variante plus flexible de remo.d 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;
}

Cela peut être facilement traduit en C - en utilisant les limites définies dans limits.h. Veuillez également noter que le Les types d'entiers de largeur fixe Pourraient ne pas être disponible sur votre système.

0
0xbadf00d