Warum abs() oder fabs() anstelle der bedingten Negation verwenden?

Lesezeit: 9 Minuten

Benutzeravatar von Subhranil
Subhranil

Warum sollte man in C/C++ verwenden abs() oder fabs() um den absoluten Wert einer Variablen zu finden, ohne den folgenden Code zu verwenden?

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

Hat es etwas mit weniger Anweisungen auf niedrigerem Niveau zu tun?

  • c/c++ warum? warum benutzt du beides so?

    – Benutzer2736738

    4. Februar 2018 um 14:18 Uhr

  • Bessere Lesbarkeit und Compiler-Optimierung.

    – iBug

    4. Februar 2018 um 14:18 Uhr


  • abs() ist eine vordefinierte und bekannte Funktion, daher verwenden sie fast alle Programmierer. Dies führte zu einer Vereinheitlichung der Codes.

    – HH

    4. Februar 2018 um 14:20 Uhr

  • Warum etwas neu erfinden, das getestet und der Standardbibliothek hinzugefügt wurde, besonders wenn Sie sich vielleicht irren oder Sonderfälle übersehen?

    – Codebändiger

    4. Februar 2018 um 14:50 Uhr

  • Warum würde nicht benutzt du abs oder fabs?

    – Benutzer253751

    4. Februar 2018 um 22:44 Uhr

Benutzeravatar von Baum mit Augen
Baum mit Augen

Der von Ihnen vorgeschlagene “bedingte Bauchmuskel” ist nicht äquivalent std::abs (oder fabs) für Fließkommazahlen, siehe zB

#include <iostream>
#include <cmath>

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

Ausgang:

-0 -0 0

Gegeben -0.0 und 0.0 dieselbe reelle Zahl ‘0’ darstellen, kann dieser Unterschied von Bedeutung sein oder auch nicht, je nachdem, wie das Ergebnis verwendet wird. Die abs-Funktion, wie sie von IEEE754 spezifiziert ist, verlangt jedoch, dass das Vorzeichen des Ergebnisses 0 ist, was das Ergebnis verbieten würde -0.0. Ich persönlich denke, dass alles, was zur Berechnung eines “absoluten Werts” verwendet wird, diesem Verhalten entsprechen sollte.

Für Integer sind beide Varianten sowohl in Laufzeit als auch Verhalten gleichwertig. (Live-Beispiel)

Aber std::abs (oder die passenden C-Äquivalente) sind bekanntermaßen korrekt und leichter zu lesen, Sie sollten diese einfach immer bevorzugen.

  • “Negative Null” in Fließkommazahlen ist wirklich etwas, das man berücksichtigen sollte.

    – iBug

    4. Februar 2018 um 14:32 Uhr

  • Diese Antwort ist informativ. Jedoch für eine Funktion wie abs()selbst Standardgeräte sind nicht perfekt. INT_MIN < 0 && abs(INT_MIN) < 0 ist wahr.

    – llllllllll

    4. Februar 2018 um 19:22 Uhr

  • @liliscent abs(INT_MIN) ist UB sogar, zumindest in C++, aber das ist eine grundlegende Einschränkung der Sprache. Sie möchten den Rückgabetyp so fixieren, dass er mit dem Quelltyp übereinstimmt (z. B. wie in IEEE754 angegeben), sodass das Laufzeitproblem der Eingabe besteht INT_MIN kann sowieso nicht wirklich umgangen werden.

    – Baum mit Augen

    4. Februar 2018 um 19:27 Uhr

  • @BaummitAugen Du hast Recht, auch in C99 ist es auch UB port70.net/~nsz/c/c99/n1256.html#7.20.6.1p2.

    – llllllllll

    4. Februar 2018 um 19:35 Uhr

  • Ich weiß nicht, ob das wirklich passieren kann, aber wenn Sie 1 durch 0 teilen und unendlich erhalten, erhalten Sie möglicherweise -unendlich, wenn Sie 1 durch -0 teilen.

    – Therm

    5. Februar 2018 um 9:08 Uhr

Das erste, was mir in den Sinn kommt, ist die Lesbarkeit.

Vergleichen Sie diese beiden Codezeilen:

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

  • Whoa, bei allem Respekt, aber das sind eine Menge positiver Stimmen für einen Low-Ball-Nickerchen. Die Frage bezieht sich eindeutig nicht auf die Syntax, sondern auf die Implementierung. Ihr “Problem” ist trivial zu lösen … Und Ihre Antwort ist gut für die Frage “Warum sollten wir Einzeiler in Funktionen verpacken?”

    – luk32

    4. Februar 2018 um 17:45 Uhr


  • @ luk32 Die Frage war “warum eine Sache einer anderen vorziehen?” Eine Antwort ist die Lesbarkeit, denn sie unterscheiden sich ziemlich stark. Ich würde das nicht als “Geizhals” bezeichnen.

    – SH7890

    4. Februar 2018 um 21:21 Uhr

  • @SH7890 Lesbarkeit ist kein Problem, da Sie eine Funktion schreiben können, um dies zu verhindern, oder sogar ein Makro. Es dauert eine Zeile und sieht genauso aus. Hier ist ein Fix für die Lesbarkeit: int cabs(int a) {return a > 0 ? a : -a;}. Es gibt sogar einen Hinweis, dass es um die Umsetzung geht. Niemand, der bei Verstand ist, wird die gesamte Implementierung für jeden Anwendungsfall kopieren und einfügen. Komm schon.

    – luk32

    4. Februar 2018 um 22:08 Uhr


  • @ luk32 Sicher, aber die Autoren der Standardbibliothek haben hilfreich vorausgesehen, dass Sie dies tun möchten, und haben diese Funktion für Sie geschrieben, sodass Sie sie nicht selbst schreiben müssen. Es heißt abs. Warum sich die Mühe machen, selbst zu schreiben cabs Funktion, die genau dasselbe macht? Ein Grund könnte sein, dass Sie es nicht wissen abs existiert, aber da Sie es jetzt tun, ist es nicht so, dass Sie etwas davon haben, es auszuspucken.

    – Benutzer253751

    4. Februar 2018 um 22:39 Uhr


  • @ luk32 du hast mich verstanden. Die Lesbarkeit ist sicherlich kein Problem, da ich mit dieser Codezeile Makros/Funktionen definieren kann. Ich suchte nach Äquivalenz oder Dissymmetrie auf niedrigerer Ebene.

    – Subhranil

    5. Februar 2018 um 2:21 Uhr

Benutzeravatar von Mats Petersson
Matt Petersson

Der Compiler wird höchstwahrscheinlich für beide auf der untersten Ebene dasselbe tun – zumindest ein moderner kompetenter Compiler.

Zumindest für Gleitkommazahlen werden Sie jedoch am Ende ein paar Dutzend Zeilen schreiben, wenn Sie alle Sonderfälle von unendlich, keine Zahl (NaN), negative Null usw. behandeln möchten.

Außerdem ist es einfacher, das zu lesen abs nimmt den absoluten Wert, als zu lesen, dass, wenn er kleiner als Null ist, er negiert wird.

Wenn der Compiler “dumm” ist, kann er am Ende schlechteren Code für a = (a < 0)?-a:aweil es eine erzwingt if (auch wenn es versteckt ist), und das könnte durchaus schlimmer sein als der eingebaute Gleitkomma-ABS-Befehl auf diesem Prozessor (abgesehen von der Komplexität spezieller Werte).

Sowohl Clang (6.0-Vorabversion) als auch gcc (4.9.2) generieren SCHLECHTEREN Code für den zweiten Fall.

Ich habe dieses kleine Beispiel geschrieben:

#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 macht diesen Code für 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

Beachten Sie, dass beide Fälle in der zweiten Form deutlich komplexer sind und im gcc-Fall eine Verzweigung verwendet wird. Clang verwendet mehr Anweisungen, aber keine Verzweigung. Ich bin mir nicht sicher, was auf welchen Prozessormodellen schneller ist, aber mehr Anweisungen sind selten besser.

  • Diese Antwort besagt, dass ein moderner kompetenter Compiler höchstwahrscheinlich dasselbe für beide tun wird, und zeigt dann Assemblercode, der zeigt, dass die ausgewählten Compiler nicht dasselbe getan haben. Das ist eine widersprüchliche, verwirrende Botschaft. Sind die ausgewählten Compiler inkompetent oder unmodern? Warum sie dann als Beispiele verwenden? Oder war die Aussage, dass ein moderner kompetenter Compiler höchstwahrscheinlich für beide dasselbe tun wird, falsch? Warum gibt es einen Unterschied im generierten Code?

    – Eric Postpischil

    4. Februar 2018 um 16:54 Uhr

  • Ganz klar mehr Anleitung ist selten besser.„Da bin ich anderer Meinung, besonders wenn man es mit Branching vergleicht. Es löst Prefetching und Out-of-Order-Execution aus. Es ist komplex und kann wahrscheinlich nicht verallgemeinert werden.

    – luk32

    4. Februar 2018 um 17:43 Uhr


  • Ist das bei -O3?

    – Kalkas

    5. Februar 2018 um 10:28 Uhr

  • @EricPostpischil, für den ganzzahligen Fall, clang erzeugt identischen Code für beide. gccDer Code von unterscheidet sich geringfügig darin, dass die abs() -Version berechnet und speichert das Ergebnis in einer einzigen Operation, während die bedingte Version das Ergebnis in ein Register berechnet und es dann in den Speicher kopiert.

    – Markieren

    6. Februar 2018 um 0:10 Uhr

  • @Calchas: Ich habe -O2 verwendet, was ich normalerweise für “optimierten” Code verwende. Aber -O3 hat den Code nicht merklich verändert. Eine neuere Version von gcc kann anders sein – aber ich habe gerade keine davon – mein Computer wird gerade aktualisiert, also wird er auch einen neuen Compiler bekommen [it wasn’t starting this morning, got it going this evening, replacement bits on the way].

    – Mats Petersson

    7. Februar 2018 um 0:39 Uhr


chux – Stellt Monicas Benutzeravatar wieder her
Chux – Wiedereinsetzung von Monica

Warum abs() oder fabs() anstelle der bedingten Negation verwenden?

Verschiedene Gründe wurden bereits genannt, dennoch betrachten Sie die Vorteile des bedingten Codes als abs(INT_MIN) sollte vermieden werden.


Es gibt einen guten Grund, den Bedingungscode anstelle von zu verwenden abs() wenn der Negativ Absolutwert einer ganzen Zahl gesucht

// 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`
}

Wenn eine positive absolute Funktion benötigt wird und value == INT_MIN ist eine reale Möglichkeit, abs(), trotz all seiner Klarheit und Geschwindigkeit, scheitert an einem Eckfall. Diverse Alternativen

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

Benutzeravatar von Davislor
Davislor

Bei einer bestimmten Architektur kann es eine effizientere Low-Level-Implementierung als eine bedingte Verzweigung geben. Zum Beispiel könnte die CPU eine haben abs Anweisung oder eine Möglichkeit, das Vorzeichenbit ohne den Overhead einer Verzweigung zu extrahieren. Angenommen, eine arithmetische Rechtsverschiebung kann ein Register füllen r mit -1, wenn die Zahl negativ ist, oder 0, wenn sie positiv ist, abs x könnte werden (x+r)^r (und wenn man die Antwort von Mats Petersson sieht, tut g++ dies tatsächlich auf x86).

Andere Antworten haben die Situation für IEEE-Gleitkomma behandelt.

Der Versuch, den Compiler anzuweisen, eine bedingte Verzweigung durchzuführen, anstatt der Bibliothek zu vertrauen, ist wahrscheinlich eine verfrühte Optimierung.

Aganjus Benutzeravatar
Aganju

Bedenken Sie, dass Sie einen komplizierten Ausdruck eingeben könnten abs(). Wenn Sie es mit codieren expr > 0 ? expr : -exprmüssen Sie den gesamten Ausdruck dreimal wiederholen, und er wird zweimal ausgewertet.
Außerdem könnten sich die beiden Ergebnisse (vor und nach dem Doppelpunkt) als unterschiedliche Typen herausstellen (wie z signed int / unsigned int), wodurch die Verwendung in einer return-Anweisung deaktiviert wird. Natürlich könnten Sie eine temporäre Variable hinzufügen, aber das löst nur Teile davon und ist auch in keiner Weise besser.

…und würden Sie es in ein Makro machen, können Sie mehrere Auswertungen haben, die Sie möglicherweise nicht möchten (Nebenwirkungen). In Betracht ziehen:

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

und verwenden:

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

was sich erweitern würde

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

Funktionsaufrufe haben diese unbeabsichtigten Nebeneffekte nicht.

  • Richtig, aber im Grunde schon von stackoverflow.com/a/48611734/2757035 angegeben. Ich bin auch neugierig, wer überhaupt etwas über Makros gesagt hat. 😛

    – Unterstrich_d

    6. Februar 2018 um 11:54 Uhr

1412480cookie-checkWarum abs() oder fabs() anstelle der bedingten Negation verwenden?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy