web-dev-qa-db-fra.com

Saturation soustraire / ajouter pour les octets non signés

Imaginez que j'ai deux octets non signés b et x. J'ai besoin de calculer bsub comme b - x et badd comme b + x. Cependant, je ne veux pas qu'un débordement/débordement se produise pendant ces opérations. Par exemple (pseudo-code):

b = 3; x = 5;
bsub = b - x; // bsub must be 0, not 254

et

b = 250; x = 10;
badd = b + x; // badd must be 255, not 4

La façon la plus évidente de procéder consiste à créer des branches:

bsub = b - min(b, x);
badd = b + min(255 - b, x);

Je me demande simplement s'il existe de meilleures façons de le faire, c'est-à-dire par des manipulations de bits hacky?

82
Oleg

L'article Branchfree Saturating Arithmetic fournit des stratégies pour cela:

Leur solution d'addition est la suivante:

u32b sat_addu32b(u32b x, u32b y)
{
    u32b res = x + y;
    res |= -(res < x);

    return res;
}

modifié pour uint8_t:

uint8_t  sat_addu8b(uint8_t x, uint8_t y)
{
    uint8_t res = x + y;
    res |= -(res < x);

    return res;
}

et leur solution de soustraction est:

u32b sat_subu32b(u32b x, u32b y)
{
    u32b res = x - y;
    res &= -(res <= x);

    return res;
}

modifié pour uint8_t:

uint8_t sat_subu8b(uint8_t x, uint8_t y)
{
    uint8_t res = x - y;
    res &= -(res <= x);

    return res;
}
84
Shafik Yaghmour

Une méthode simple consiste à détecter le débordement et à réinitialiser la valeur en conséquence comme ci-dessous

bsub = b - x;
if (bsub > b)
{
    bsub = 0;
}

badd = b + x;
if (badd < b)
{
    badd = 255;
}

GCC peut optimiser la vérification du débordement en une affectation conditionnelle lors de la compilation avec -O2.

J'ai mesuré le degré d'optimisation par rapport à d'autres solutions. Avec 1000000000+ opérations sur mon PC, cette solution et celle de @ShafikYaghmour ont duré en moyenne 4,2 secondes et celle de @chux en moyenne 4,8 secondes. Cette solution est également plus lisible.

40
user1969104

Pour la soustraction:

diff = (a - b)*(a >= b);

Une addition:

sum = (a + b) | -(a > (255 - b))

Évolution

// sum = (a + b)*(a <= (255-b)); this fails
// sum = (a + b) | -(a <= (255 - b)) falis too

Merci à @ R_Kapp

Merci à @ NathanOliver

Cet exercice montre la valeur d'un simple codage.

sum = b + min(255 - b, a);
16
chux

Si vous utilisez une version assez récente de gcc ou clang (peut-être aussi quelques autres), vous pouvez utiliser built-ins pour détecter le débordement.

if (__builtin_add_overflow(a,b,&c))
{
  c = UINT_MAX;
}
13
erebos

Si vous êtes prêt à utiliser Assembly ou intrinsics, je pense avoir une solution optimale.

Pour la soustraction:

Nous pouvons utiliser l'instruction sbb

Dans MSVC, nous pouvons utiliser la fonction intrinsèque _ subborrow_u64 (également disponible dans d'autres tailles de bits).

Voici comment il est utilisé:

// *c = a - (b + borrow)
// borrow_flag is set to 1 if (a < (b + borrow))
borrow_flag = _subborrow_u64(borrow_flag, a, b, c);

Voici comment nous pourrions l'appliquer à votre situation

uint64_t sub_no_underflow(uint64_t a, uint64_t b){
    uint64_t result;
    borrow_flag = _subborrow_u64(0, a, b, &result);
    return result * !borrow_flag;
}

Pour addition:

Nous pouvons utiliser l'instruction adcx

Dans MSVC, nous pouvons utiliser la fonction intrinsèque _ addcarry_u64 (également disponible dans d'autres tailles de bits).

Voici comment il est utilisé:

// *c = a + b + carry
// carry_flag is set to 1 if there is a carry bit
carry_flag = _addcarry_u64(carry_flag, a, b, c);

Voici comment nous pourrions l'appliquer à votre situation

uint64_t add_no_overflow(uint64_t a, uint64_t b){
    uint64_t result;
    carry_flag = _addcarry_u64(0, a, b, &result);
    return !carry_flag * result - carry_flag;
}

Je n'aime pas celui-ci autant que celui de la soustraction, mais je pense que c'est assez chouette.

Si l'ajout déborde, carry_flag = 1. Ne pas noter que carry_flag Donne 0, donc !carry_flag * result = 0 En cas de débordement. Et puisque 0 - 1 Mettra la valeur intégrale non signée à son maximum, la fonction retournera le résultat de l'addition s'il n'y a pas de report et retournera le maximum de la valeur intégrale choisie s'il y a report.

4
MichaelMitchell

Pour plus:

unsigned temp = a+b;  // temp>>8 will be 1 if overflow else 0
unsigned char c = temp | -(temp >> 8);

Pour la soustraction:

unsigned temp = a-b;  // temp>>8 will be 0xFF if neg-overflow else 0
unsigned char c = temp & ~(temp >> 8);

Aucun opérateur de comparaison ou multiplication requis.

3
supercat

Vous pouvez également utiliser la bibliothèque numérique sûre à Boost Library Incubator . Il fournit des remplacements de remplacement pour int, long, etc ... qui garantissent que vous n'obtiendrez jamais un débordement, un débordement non détecté, etc.

2
Robert Ramey

Tout peut être fait en arithmétique d'octets non signés

// Addition without overflow
return (b > 255 - a) ? 255 : a + b

// Subtraction without underflow
return (b > a) ? 0 : a - b;
2
Yves Daoust

Si vous souhaitez le faire avec deux octets, utilisez le code le plus simple possible.

Si vous voulez le faire avec vingt milliards d'octets, vérifiez quelles instructions vectorielles sont disponibles sur votre processeur et si elles peuvent être utilisées. Vous constaterez peut-être que votre processeur peut effectuer 32 de ces opérations avec une seule instruction.

2
gnasher729

et ça:

bsum = a + b;
bsum = (bsum < a || bsum < b) ? 255 : bsum;

bsub = a - b;
bsub = (bsub > a || bsub > b) ? 0 : bsub;
2
user4580220

Si vous appelez souvent ces méthodes, le moyen le plus rapide ne serait pas la manipulation de bits mais probablement une table de recherche. Définissez un tableau de longueur 511 pour chaque opération. Exemple pour moins (soustraction)

static unsigned char   maxTable[511];
memset(maxTable, 0, 255);           // If smaller, emulates cutoff at zero
maxTable[255]=0;                    // If equal     - return zero
for (int i=0; i<256; i++)
    maxTable[255+i] = i;            // If greater   - return the difference

Le tableau est statique et initialisé une seule fois. Maintenant, votre soustraction peut être définie comme une méthode en ligne ou en utilisant un précompilateur:

#define MINUS(A,B)    maxTable[A-B+255];

Comment ça marche? Eh bien, vous voulez pré-calculer toutes les soustractions possibles pour les caractères non signés. Les résultats varient de -255 à +255, soit un total de 511 résultats différents. Nous définissons un tableau de tous les résultats possibles mais parce qu'en C nous ne pouvons pas y accéder à partir d'indices négatifs, nous utilisons +255 (dans [A-B + 255]). Vous pouvez supprimer cette action en définissant un pointeur vers le centre du tableau.

const unsigned char *result = maxTable+255;
#define MINUS(A,B)    result[A-B];

utilisez-le comme:

bsub  = MINUS(13,15); // i.e 13-15 with zero cutoff as requested

Notez que l'exécution est extrêmement rapide. Une seule soustraction et un seul pointeur de déférence pour obtenir le résultat. Aucune ramification. Les tableaux statiques sont très courts, ils seront donc entièrement chargés dans le cache du processeur pour accélérer encore le calcul

La même chose fonctionnerait pour l'addition mais avec un tableau un peu différent (les 256 premiers éléments seront les indices et les 255 derniers éléments seront égaux à 255 pour émuler la coupure au-delà de 255.

Si vous insistez sur le fonctionnement des bits, les réponses qui utilisent (a> b) sont fausses. Cela pourrait toujours être implémenté en tant que branchement. Utilisez la technique du bit de signe

// (num1>num2) ? 1 : 0
#define        is_int_biggerNotEqual( num1,num2) ((((__int32)((num2)-(num1)))&0x80000000)>>31)

Vous pouvez maintenant l'utiliser pour calculer la soustraction et l'addition.

Si vous souhaitez émuler les fonctions max (), min () sans branchement, utilisez:

inline __int32 MIN_INT(__int32 x, __int32 y){   __int32 d=x-y; return y+(d&(d>>31)); }              

inline __int32 MAX_INT(__int32 x, __int32 y){   __int32 d=x-y; return x-(d&(d>>31)); }

Mes exemples ci-dessus utilisent des entiers 32 bits. Vous pouvez le changer en 64, bien que je pense que les calculs 32 bits s'exécutent un peu plus rapidement. Dépend de vous

1
DanielHsH