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?
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;
}
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.
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);
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;
}
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.
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.
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.
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;
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.
et ça:
bsum = a + b;
bsum = (bsum < a || bsum < b) ? 255 : bsum;
bsub = a - b;
bsub = (bsub > a || bsub > b) ? 0 : bsub;
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