Probleme mit M_PI und dem Visual Studio 2010



  • @dot: Im Ernst? Du schreibst lieber math::constants<T>::one() als einfach 1.0? Und warum gibst Du solche Zahlengewusel ein, statt Dir pi und e durch die entsprechenden Mathefunktionen berechnen zu lassen? Jeder halbwegs aktuelle Compiler sollte doch 4.0*atan(1.0) bzw. exp(1.0) direkt durch die entsprechenden Werte ersetzen können, oder? Den Sinn von math::constants<T>::e() verstehe ich eh nicht. Wann braucht man die Konstante schon mal explizit?



  • Walli schrieb:

    @dot: Im Ernst? Du schreibst lieber math::constants<T>::one() als einfach 1.0?

    1.0 ist ein double. math::constants<T>::one() dagegen, ist ein T.
    Wenn ich mit doubles rechne, dann schreib ich natürlich 1.0. Aber in generischem Code kann ich das nicht, da ich ja nicht weiß mit welchem Typ ich grad rechne.
    Kannst ja mal ausprobieren was passiert wenn du z.B. auf x64 grad mit floats rechnest und dann eine double Konstante für pi in den Ausdruck reinwirfst. Abgesehen davon dass ich es als furchtbar unsauber empfinde, kann das richtig böse Auswirkungen auf den generierten Code haben, weil der Compiler plötzlich an allen Ecken und Enden unnötige float <-> double Konvertierungen emittieren muss...

    Walli schrieb:

    Und warum gibst Du solche Zahlengewusel ein, statt Dir pi und e durch die entsprechenden Mathefunktionen berechnen zu lassen? Jeder halbwegs aktuelle Compiler sollte doch 4.0*atan(1.0) bzw. exp(1.0) direkt durch die entsprechenden Werte ersetzen können, oder?

    Und wer garantiert mit das? Schreib ich einmal die Zahlenwurst hin, bin ich nichtmehr davon abhängig dass auch wirklich jeder Compiler tatsächlich, immer, überall und unter allen Umständen entsprechendes Constant Folding durchführt...

    Walli schrieb:

    Den Sinn von math::constants<T>::e() verstehe ich eh nicht. Wann braucht man die Konstante schon mal explizit?

    Selten, das stimmt. Aber es schadet auch sicher nicht, sie dabei zu haben...



  • dot schrieb:

    1.0 ist ein double. math::constants<T>::one() dagegen, ist ein T.
    Wenn ich mit doubles rechne, dann schreib ich natürlich 1.0. Aber in generischem Code kann ich das nicht, da ich ja nicht weiß mit welchem Typ ich grad rechne.
    Kannst ja mal ausprobieren was passiert wenn du z.B. auf x64 grad mit floats rechnest und dann eine double Konstante für pi in den Ausdruck reinwirfst. Abgesehen davon dass ich es als furchtbar unsauber empfinde, kann das richtig böse Auswirkungen auf den generierten Code haben, weil der Compiler plötzlich an allen Ecken und Enden unnötige float <-> double Konvertierungen emittieren muss...

    static_cast<T>(1.0) vs. math::constants<T>::one()?

    dot schrieb:

    Und wer garantiert mit das? Schreib ich einmal die Zahlenwurst hin, bin ich nichtmehr davon abhängig dass auch wirklich jeder Compiler tatsächlich, immer, überall und unter allen Umständen entsprechendes Constant Folding durchführt...

    Gut, ich entwickle halt unter der Annahme, dass niemand meinen Code mit uralten Compilern kompilieren will, eben weil ich mir den Luxus leisten kann. Im schlimmsten Fall würde der Code einmal atan unnötig berechnen, was wohl selbst auf einem Taschenrechner zu verschmerzen wäre.



  • Walli schrieb:

    static_cast<T>(1.0) vs. math::constants<T>::one()?

    Ich find die Lösung per Cast unschön. Abgesehen davon ändert der Cast nix dran, dass der Compiler (MSVC) unnötige Konvertierungen emittiert...

    Walli schrieb:

    dot schrieb:

    Und wer garantiert mit das? Schreib ich einmal die Zahlenwurst hin, bin ich nichtmehr davon abhängig dass auch wirklich jeder Compiler tatsächlich, immer, überall und unter allen Umständen entsprechendes Constant Folding durchführt...

    Gut, ich entwickle halt unter der Annahme, dass niemand meinen Code mit uralten Compilern kompilieren will, eben weil ich mir den Luxus leisten kann. Im schlimmsten Fall würde der Code einmal atan unnötig berechnen, was wohl selbst auf einem Taschenrechner zu verschmerzen wäre.

    Nein. Im schlimmsten Fall würde er wohl einmal atan() unnötig berechnen (völlig egal) und dann den unnötig berechneten Wert ständig aus dem Speicher laden (alles andere als egal)...



  • dot schrieb:

    Meine Lösung:
    [...]

    Hmm ... irgendwie so ... kompliziert.

    namespace math_constants {
    
    const long double piL = 3.14159265358979323846L;
    const      double pi  = piL;
    
    }
    

    müsste doch auch schon brauchbar sein.

    Für "generische" Einsen und Nullen schreibe ich auch einfach nur T(1) und T(0).



  • dot schrieb:

    Nein. Im schlimmsten Fall würde er wohl einmal atan() unnötig berechnen (völlig egal) und dann den unnötig berechneten Wert ständig aus dem Speicher laden (alles andere als egal)...

    Ja, wenn man mit uralt-Compilern auf Plattformen arbeitet, wo sich das bemerkbar macht. Aber wozu brauchst Du dann gleich noch generische Programmierung? Irgendwie löst Du scheinbar gerne Probleme, die keine sind.



  • Ich finde dots Lösung elegant.



  • Allow me to demonstrate (Deklaration von math::constants aus Gründen der Übersichtlichkeit weggelassen):

    #include <iostream>
    
    const double pi = 4.0 * atan(1.0);
    
    template <typename T>
    T area(T r)
    {
      return r * r * static_cast<T>(pi);
    }
    
    //template <typename T>
    //T area(T r)
    //{
    //  return r * r * math::constants<T>:: pi();
    //}
    
    int main()
    {
      float r;
      std::cin >> r;
    
      float A = area(r);
    
      std::cout << A;
    }
    

    Aktueller MSVC generiert folgenden Code (x64 Release Build):

    movss       xmm1,dword ptr [r]
     cvtsd2ss    xmm0,mmword ptr [_fmode+4 (13F9935E0h)]
     mulss       xmm1,xmm1
     mulss       xmm1,xmm0
    

    Ohne den static_cast sieht der generierte Code sogar noch wesentlich schlimmer aus (klar, denn der C++ Standard zwingt den Compiler dazu):

    movss       xmm5,dword ptr [r]
     mulss       xmm5,xmm5
     unpcklps    xmm5,xmm5
     cvtps2pd    xmm0,xmm5
     mulsd       xmm0,mmword ptr [_fmode+4 (13F4035E0h)]
     cvtsd2ss    xmm1,xmm0
    

    Für die Variante mit math::constants dagegen, emittiert der Compiler optimalen Code:

    movss       xmm1,dword ptr [r]
     mulss       xmm1,xmm1
     mulss       xmm1,dword ptr [__real@40490fdb (13FBF21E0h)]
    

    Abgesehen davon dass ich die anderen hier präsentierten Lösungen rein auf ästhethischer Ebene unbefriedigend finde, macht es also auf aktuellen Compilern sehr wohl einen realen Unterschied.

    Ja, der Compiler lädt in beiden Fällen den float aus dem Speicher weil der Befehlssatz keine Immediates bietet, es macht also in dem Fall keinen Unterschied wie man pi genau definiert. Das war aber auch nur der denkbare Worst Case...



  • Ich habe hier keinen MSVC, aber ich kann mir irgendwie nicht vorstellen, dass man das cvtsd2ss mit den richtigen Compilerflags nicht wegbekommt. Wenn ich demnaechst mal Zeit habe, probiere ich mal aus, was der gcc daraus macht.



  • Also den MSVC bring ich nichtmal mit dem lockersten Floating Point Model und Optimierung rein auf Geschwindigkeit dazu die Konvertierung zu meiden...


Anmelden zum Antworten