59 votes

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

En C / C ++, pourquoi 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?

125voto

Baum mit Augen Points 3571

Le "conditionnel abs" que vous proposez n'est pas équivalent à std::abs (ou fabs) pour les nombres à virgule flottante, voir par 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

Compte tenu de -0.0 et 0.0 représentent le même nombre réel '0', cette différence peut ou ne peut pas d'importance, en fonction de la façon dont le résultat est utilisé. Cependant, la fonction abs comme spécifié par IEEE754 mandats la signbit du résultat à 0, ce qui interdirait le résultat -0.0. Personnellement, je pense que tout utilisé pour calculer certains "valeur absolue" doit correspondre à ce comportement.

Pour les entiers, les deux variantes seront équivalents à la fois dans l'exécution et le comportement. (Exemple vivant)

Mais comme std::abs (ou le raccord C équivalents) sont connus pour être correct et plus facile à lire, il faut toujours préférer ces.

88voto

iBug Points 20818

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);
 

31voto

Mats Petersson Points 70074

Le compilateur va probablement faire la même chose pour les deux au fond de la couche d'au moins un moderne compétente compilateur.

Cependant, au moins pour la virgule flottante, vous finirez par l'écriture de quelques dizaines de lignes, si vous le souhaitez traiter tous les cas particuliers de l'infini, et non pas un nombre (NaN), négatif zéro et ainsi de suite.

Ainsi que c'est plus facile à lire qu' abs est de prendre la valeur absolue de la lecture que s'il fait moins de zéro, plus le nier.

Si le compilateur est "stupide", il peut bien faire de pire code pour a = (a < 0)?-a:a,, parce qu'il impose une if (même si elle est cachée), et qui pourrait bien être pire que dans le haut-virgule flottante abs instruction sur ce processeur (en dehors de la complexité de valeurs spéciales)

Les deux Clang (6.0-pre-release) et gcc (4.9.2) génère PIRE code pour le deuxième 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 fait 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 complexe dans la deuxième forme, et dans le ccag cas, il utilise une branche. Clang utilise plus d'instructions, mais aucune branche. Je ne suis pas sûr de ce qui est plus rapide sur le processeur modèles, mais de toute évidence plus d'instructions est rarement mieux.

13voto

chux Points 13185

Pourquoi utiliser l'abs() ou fabs() au lieu de la condamnation de la négation?

Diverses raisons ont déjà été indiqué, encore envisager de code conditionnel avantages qu' abs(INT_MIN) doit être évitée.


Il y a une bonne raison pour utiliser le code conditionnel à la place de abs() lorsque le négatif de la valeur absolue d'un nombre entier est demandé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`
}

Lorsqu'un positif absolu de la fonction et de l' value == INT_MIN est une possibilité réelle, abs(), pour l'ensemble de sa clarté et de la vitesse d'échec, un coin de cas. Diverses solutions de rechange

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

7voto

Lorehead Points 953

Il y a peut être plus efficace, à faible niveau de mise en œuvre qu'une branche conditionnelle, sur une architecture donnée. Par exemple, le PROCESSEUR peut avoir un abs instruction, ou un moyen d'extraire le bit de signe sans l'aide d'une branche. En supposant une arithmétique décalage à droite pouvez remplir un registre r avec -1 si le nombre est négatif, ou 0 si le résultat est positif, abs x pourrait devenir (x+r)^r (et de voir Tapis de Petersson réponse, g++ en fait cela sur l'architecture x86).

D'autres réponses ont disparu au cours de la situation pour virgule flottante IEEE.

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

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