web-dev-qa-db-fra.com

Pourquoi utiliser abs () ou fabs () au lieu de la négation conditionnelle?

En C/C++, pourquoi devrait-on utiliser abs() ou fabs() pour rechercher la valeur absolue d'une variable sans utiliser le code suivant?

int absoluteValue = value < 0 ? -value : value;

Cela a-t-il quelque chose à voir avec moins d'instructions au niveau inférieur?

51
Subhranil

"L'abs conditionnel" que vous proposez n'est pas équivalent à std::abs (ou fabs) pour les nombres en virgule flottante, voir p. ex.

#include <iostream>
#include <cmath>

int main () {
    double d = -0.0;
    double a = d < 0 ? -d : d;
    std::cout << d << ' ' << a << ' ' << std::abs(d);
}

sortie:

-0 -0 0

Donné -0.0 et 0.0 représente le même nombre réel '0', cette différence peut avoir une importance ou non, selon l'utilisation du résultat. Cependant, la fonction abs spécifiée par IEEE754 impose que le bit de signalisation du résultat soit égal à 0, ce qui interdirait le résultat -0.0. Personnellement, je pense que tout ce qui est utilisé pour calculer une "valeur absolue" devrait correspondre à ce comportement.

Pour les entiers, les deux variantes seront équivalentes à la fois en termes d'exécution et de comportement. ( Exemple en direct )

Mais comme std::abs (ou les équivalents de raccord C) sont connus pour être corrects et plus faciles à lire, vous devriez toujours les préférer.

119
Baum mit Augen

La première chose qui me vient à l’esprit est la lisibilité.

Comparez ces deux lignes de codes:

int x = something, y = something, z = something;
// Compare
int absall = (x > 0 ? x : -x) + (y > 0 ? y : -y) + (z > 0 ? z : -z);
int absall = abs(x) + abs(y) + abs(z);
86
iBug

Le compilateur fera probablement la même chose pour les deux couches inférieures - du moins un compilateur compétent moderne.

Cependant, au moins pour les virgules flottantes, vous finirez par écrire quelques dizaines de lignes si vous souhaitez gérer tous les cas particuliers de l'infini, du nombre non (NaN), du zéro négatif, etc.

De plus, il est plus facile de lire que abs prend la valeur absolue plutôt que de lire que si elle est inférieure à zéro, annulez-la.

Si le compilateur est "stupide", il se pourrait bien qu'il ait un code pire pour a = (a < 0)?-a:a, car il force un if (même s'il est caché), ce qui pourrait être pire que le instruction abs intégrée en virgule flottante sur ce processeur (en dehors de la complexité des valeurs spéciales)

Clang (6.0-pre-release) et gcc (4.9.2) génèrent un code WORSE pour le second cas.

J'ai écrit ce petit échantillon:

#include <cmath>
#include <cstdlib>

extern int intval;
extern float floatval;

void func1()
{
    int a = std::abs(intval);
    float f = std::abs(floatval);
    intval = a;
    floatval = f;
}


void func2()
{
    int a = intval < 0?-intval:intval;
    float f = floatval < 0?-floatval:floatval;
    intval = a;
    floatval = f;
}

clang crée ce code pour func1:

_Z5func1v:                              # @_Z5func1v
    movl    intval(%rip), %eax
    movl    %eax, %ecx
    negl    %ecx
    cmovll  %eax, %ecx
    movss   floatval(%rip), %xmm0   # xmm0 = mem[0],zero,zero,zero
    andps   .LCPI0_0(%rip), %xmm0
    movl    %ecx, intval(%rip)
    movss   %xmm0, floatval(%rip)
    retq

_Z5func2v:                              # @_Z5func2v
    movl    intval(%rip), %eax
    movl    %eax, %ecx
    negl    %ecx
    cmovll  %eax, %ecx
    movss   floatval(%rip), %xmm0   
    movaps  .LCPI1_0(%rip), %xmm1 
    xorps   %xmm0, %xmm1
    xorps   %xmm2, %xmm2
    movaps  %xmm0, %xmm3
    cmpltss %xmm2, %xmm3
    movaps  %xmm3, %xmm2
    andnps  %xmm0, %xmm2
    andps   %xmm1, %xmm3
    orps    %xmm2, %xmm3
    movl    %ecx, intval(%rip)
    movss   %xmm3, floatval(%rip)
    retq

g ++ func1:

_Z5func1v:
    movss   .LC0(%rip), %xmm1
    movl    intval(%rip), %eax
    movss   floatval(%rip), %xmm0
    andps   %xmm1, %xmm0
    sarl    $31, %eax
    xorl    %eax, intval(%rip)
    subl    %eax, intval(%rip)
    movss   %xmm0, floatval(%rip)
    ret

g ++ func2:

_Z5func2v:
    movl    intval(%rip), %eax
    movl    intval(%rip), %edx
    pxor    %xmm1, %xmm1
    movss   floatval(%rip), %xmm0
    sarl    $31, %eax
    xorl    %eax, %edx
    subl    %eax, %edx
    ucomiss %xmm0, %xmm1
    jbe .L3
    movss   .LC3(%rip), %xmm1
    xorps   %xmm1, %xmm0
.L3:
    movl    %edx, intval(%rip)
    movss   %xmm0, floatval(%rip)
    ret

Notez que les deux cas sont nettement plus complexes dans la seconde forme, et dans le cas de gcc, il utilise une branche. Clang utilise plus d'instructions, mais pas de branche. Je ne sais pas ce qui est le plus rapide sur quels modèles de processeur, mais il est clair que plus d'instructions est rarement préférable.

28
Mats Petersson

Pourquoi utiliser abs () ou fabs () au lieu de la négation conditionnelle?

Diverses raisons ont déjà été indiquées, mais considérons les avantages du code conditionnel comme abs(INT_MIN) à éviter.


Il y a une bonne raison d'utiliser le code conditionnel au lieu de abs() lorsque la valeur absolue négative d'un entier est recherchée.

// Negative absolute value

int nabs(int value) {
  return -abs(value);  // abs(INT_MIN) is undefined behavior.
}

int nabs(int value) {
  return value < 0 ? value : -value; // well defined for all `int`
}

Quand une fonction absolue positive est nécessaire et que value == INT_MIN Est une possibilité réelle, abs(), pour toute sa clarté et sa rapidité, échoue dans un cas critique. Diverses alternatives

unsigned absoluteValue = value < 0 ? (0u - value) : (0u + value);
10
chux

Il peut y avoir une implémentation de bas niveau plus efficace qu'une branche conditionnelle, sur une architecture donnée. Par exemple, le processeur peut avoir une instruction abs ou un moyen d'extraire le bit de signe sans le temps système d'une branche. Supposons qu'un décalage arithmétique à droite puisse remplir un registre r avec -1 si le nombre est négatif ou 0 si positif, abs x pourrait devenir (x+r)^r (et voyant la réponse de Mats Petersson, g ++ le fait réellement sur x86).

D'autres réponses ont exploré la situation en virgule flottante IEEE.

Essayer de dire au compilateur d’effectuer une branche conditionnelle au lieu de faire confiance à la bibliothèque est probablement une optimisation prématurée.

6
Davislor

Considérez que vous pouvez introduire une expression compliquée dans abs(). Si vous le codez avec expr > 0 ? expr : -expr, vous devez répéter toute l’expression trois fois et elle sera évaluée deux fois.
De plus, les deux résultats (avant et après les deux points) pourraient s’avérer être de types différents (comme signed int/unsigned int), qui désactive l’utilisation dans une instruction return. Bien sûr, vous pouvez ajouter une variable temporaire, mais cela n'en résout que certaines parties, et ce n'est pas mieux non plus.

4
Aganju

En supposant que le compilateur ne sera pas en mesure de déterminer que abs () et la négation conditionnelle tentent d'atteindre le même objectif, la négation conditionnelle compile en une instruction de comparaison, une instruction de saut conditionnel et une instruction de déplacement, alors que abs () compile en une instruction de valeur absolue réelle, dans des jeux d’instructions qui prennent en charge une telle chose, ou au niveau du bit, et qui reste identique, à l’exception du bit de signe. Chaque instruction ci-dessus correspond généralement à 1 cycle. Par conséquent, l'utilisation de abs () sera au moins aussi rapide ou plus rapide que la négation conditionnelle (car le compilateur peut toujours reconnaître que vous essayez de calculer une valeur absolue lorsque vous utilisez la négation conditionnelle, et générer une instruction de valeur absolue quand même). Même s'il n'y a pas de changement dans le code compilé, abs () est toujours plus lisible que la négation conditionnelle.

3
Cpp plus 1

L'intention derrière abs () est "de définir (sans condition) le signe de ce nombre sur positif". Même si cela devait être implémenté en tant que condition en fonction de l'état actuel du nombre, il est probablement plus utile de pouvoir le considérer comme un simple "faire ceci", plutôt qu'un "plus complexe" si ... ceci ... cela " .

3
StarWeaver

... et voudriez-vous en faire une macro, vous pouvez avoir plusieurs évaluations que vous ne voulez peut-être pas (effets secondaires). Considérer:

#define ABS(a) ((a)<0?-(a):(a))

et utilise:

f= 5.0;
f=ABS(f=fmul(f,b));

qui s'étendrait à

f=((f=fmul(f,b)<0?-(f=fmul(f,b)):(f=fmul(f,b)));

Les appels de fonction n'auront pas ces effets secondaires inattendus.

3
Paul Ogilvie