c++ - vergleichen - floating point comparison




Hat es einen Vorteil, pow(x, 2) anstelle von x*x zu verwenden, mit x double? (6)

Diese Frage berührt eine der Hauptschwächen der meisten Implementierungen von C und C ++ in Bezug auf wissenschaftliche Programmierung. Nachdem ich von Fortran nach C ungefähr zwanzig Jahre und später zu C ++ gewechselt habe, bleibt dies einer dieser wunden Stellen, die mich gelegentlich fragen lassen, ob dieser Wechsel eine gute Sache war.

Das Problem auf den Punkt gebracht:

  • Der einfachste Weg, pow zu implementieren, ist Type pow(Type x; Type y) {return exp(y*log(x));}
  • Die meisten C- und C ++ - Compiler nehmen den einfachen Ausweg.
  • Einige mögen "das Richtige tun", aber nur bei hohen Optimierungsstufen.
  • Im Vergleich zu x*x ist der einfache Weg mit pow(x,2) sehr rechenintensiv und verliert an Genauigkeit.

Vergleichen Sie mit Sprachen, die auf wissenschaftliche Programmierung abzielen:

  • Du schreibst nicht pow(x,y) . Diese Sprachen haben einen eingebauten Potenzierungsoperator. Dass C und C ++ sich unerschütterlich geweigert haben, einen Potenzierungsoperator zu implementieren, lässt das Blut vieler Programmierer von wissenschaftlichen Programmierern zum Kochen bringen. Für einige hartgesottene Fortran-Programmierer ist dies der Grund, niemals zu C. zu wechseln.
  • Fortran (und andere Sprachen) müssen "das Richtige tun" für alle kleinen Integer-Potenzen, wobei klein eine ganze Zahl zwischen -12 und 12 ist. (Der Compiler ist nicht konform, wenn er nicht das Richtige tun kann) .) Darüber hinaus sind sie verpflichtet, dies mit Optimierung zu tun.
  • Viele Fortran-Compiler wissen auch, wie man einige rationale Wurzeln extrahiert, ohne auf den einfachen Ausweg zurückgreifen zu müssen.

Es gibt ein Problem, sich auf hohe Optimierungsstufen zu verlassen, um "das Richtige zu tun". Ich habe für mehrere Organisationen gearbeitet, die die Verwendung von Optimierung in sicherheitskritischer Software verboten haben. Erinnerungen können sehr lang sein (mehrere Jahrzehnte lang), nachdem man hier 10 Millionen Dollar verloren hat, 100 Millionen dort, alles aufgrund von Fehlern in einigen optimierenden Compilern.

IMHO sollte man niemals pow(x,2) in C oder C ++ benutzen. Ich bin nicht allein in dieser Meinung. Programmierer, die pow(x,2) Regel während der Code-Reviews stark geplättet.

Gibt es einen Vorteil, diesen Code zu verwenden?

double x;
double square = pow(x,2);

an Stelle von?

double x;
double square = x*x;

Ich bevorzuge x * x und schaue auf meine Implementierung (Microsoft) Ich finde keine Vorteile in pow, weil x * x ist einfacher als pow für den jeweiligen quadratischen Fall.

Gibt es einen besonderen Fall, wo Powder überlegen ist?


FWIW, mit gcc-4.2 auf MacOS X 10.6 und -O3 Compilerflags,

x = x * x;

und

y = pow(y, 2);

Ergebnis in demselben Assembler-Code:

#include <cmath>

void test(double& x, double& y) {
        x = x * x;
        y = pow(y, 2);
}

Montiert zu:

    pushq   %rbp
    movq    %rsp, %rbp
    movsd   (%rdi), %xmm0
    mulsd   %xmm0, %xmm0
    movsd   %xmm0, (%rdi)
    movsd   (%rsi), %xmm0
    mulsd   %xmm0, %xmm0
    movsd   %xmm0, (%rsi)
    leave
    ret

Solange Sie einen anständigen Compiler verwenden, schreiben Sie, was immer sinnvoller für Ihre Anwendung ist, aber bedenken Sie, dass pow(x, 2) niemals optimaler sein kann als die einfache Multiplikation.


In C ++ 11 gibt es einen Fall, in dem es einen Vorteil gibt, x * x über std::pow(x,2) und in diesem Fall müssen Sie ihn in einem constexpr :

constexpr double  mySqr( double x )
{
      return x * x ;
}

Wie wir sehen können, ist std::pow nicht als constexpr gekennzeichnet und daher in einer constexpr- Funktion unbrauchbar.

godbolt den folgenden Code aus Performance-Perspektive in godbolt , werden folgende Funktionen godbolt :

#include <cmath>

double  mySqr( double x )
{
      return x * x ;
}

double  mySqr2( double x )
{
      return std::pow( x, 2.0 );
}

identische Baugruppe erzeugen:

mySqr(double):
    mulsd   %xmm0, %xmm0    # x, D.4289
    ret
mySqr2(double):
    mulsd   %xmm0, %xmm0    # x, D.4292
    ret

und wir sollten ähnliche Ergebnisse von jedem modernen Compiler erwarten.

Bemerkenswert ist, dass gcc derzeit pow einen conetexpr betrachtet , auch here aber dies ist eine nicht konforme Erweiterung und sollte nicht verlässlich sein und wird sich wahrscheinlich in späteren Versionen von gcc ändern.


MEINER BESCHEIDENEN MEINUNG NACH:

  • Code Lesbarkeit
  • Code-Robustheit - einfacher zu pow(x, 6) wechseln pow(x, 6) , eventuell ein Fließkomma-Mechanismus für einen bestimmten Prozessor implementiert usw.
  • Leistung - wenn es eine klügere und schnellere Möglichkeit gibt, dies zu berechnen (mit Assembler oder einem speziellen Trick), macht pow das. du wirst nicht .. :)

Prost


std :: pow ist aussagekräftiger, wenn Sie x² meinen, x x ist aussagekräftiger, wenn Sie x x meinen , besonders wenn Sie zB eine wissenschaftliche Arbeit niederschreiben und die Leser Ihre Implementierung im Vergleich zum Papier verstehen sollten. Der Unterschied ist vielleicht subtil für x * x / x², aber ich denke, wenn Sie benannte Funktionen im Allgemeinen verwenden, erhöht das Code-Experiency und Lesbarkeit.

Auf modernen Compilern, wie zB g ++ 4.x, wird std :: pow (x, 2) inline sein, wenn es nicht einmal ein Compiler-Built-In ist und die Stärke auf x * x reduziert ist. Wenn nicht standardmäßig, und Sie nicht auf IEEE Floating-Typ-Konformität achten, überprüfen Sie das Handbuch des Compilers für einen schnellen mathematischen Schalter (g ++ == -ffast-math).

Randnotiz: Es wurde erwähnt, dass die Einbeziehung von math.h die Programmgröße erhöht. Meine Antwort war:

In C ++ enthalten Sie #include <cmath> , nicht math.h. Wenn Ihr Compiler noch nicht alt ist, wird er Ihre Programmgröße nur um das erhöhen, was Sie verwenden (im allgemeinen Fall), und wenn Ihre Implementierung von std :: pow nur in entsprechende x87-Anweisungen und ein modernes g ++ integriert ist wird Stärke-reduzieren x² mit x * x, dann gibt es keine relevante Größenzunahme. Außerdem sollte die Programmgröße niemals vorschreiben, wie aussagekräftig Ihr Code ist.

Ein weiterer Vorteil von cmath gegenüber math.h ist, dass man mit cmath für jeden Gleitkommatyp eine std :: pow-Überladung erhält, während man mit math.h im globalen Namensraum pow, powf usw. erhält, so dass cmath die Anpassungsfähigkeit erhöht vor allem beim Schreiben von Vorlagen.

Als allgemeine Regel gilt: Lieber aussagekräftigen und eindeutigen Code über zweifelhaft geerdete Leistung und binärgrö- ßigen Code.

Siehe auch Knuth:

"Wir sollten kleine Wirkungsgrade vergessen, sagen wir etwa 97% der Zeit: vorzeitige Optimierung ist die Wurzel allen Übels"

und Jackson:

Die erste Regel der Programmoptimierung: Tu es nicht. Die zweite Regel der Programmoptimierung (nur für Experten!): Tun Sie es noch nicht.


x * x wird immer zu einer einfachen Multiplikation kompiliert. pow(x, 2) ist wahrscheinlich, aber keineswegs garantiert, auf dasselbe optimiert. Wenn es nicht optimiert ist, wird es wahrscheinlich eine langsame allgemeine Raise-to-Power-Mathe-Routine verwenden. Wenn also die Leistung Ihr Anliegen ist, sollten Sie immer x * x bevorzugen.





floating-point