Trouble with the Double

 

Hallo Community,

das Problem mit der Double (Float). Ein Thame, dass immer wieder in den Foren auftaucht. 

Warum gibt es eigentlich Probleme mit Doubles oder Float Datentypen? - An und für sich ist das einfach zu beantworten: Sie werden falsch benutzt. Eine recht harte These, doch aus mathematischer Sicht ist das ganz einfach zu erklären: Unendliche Genauigkeit bei endlicher Speicherfläche.


Nun zum Thema: Double und Float Variablen sind Speicherplätze im (Haupt)Speicher, die eine "Interpretationsinformation" und eine Datengröße erhalten.

Wenn wir uns nun Vorstellen, eine Zahl mit Nachkommastellen, so ist diese Zahl (je nach Genauigkeit) mit einer bestimmten Menge an Nullen nach dem Komma versehen. - An einer dieser Stellen, irgendwo zwischen den Nachkommastellen, hört der Speicherplatz auf. Genau an dieser Stelle tritt das Problem mit der Genauigkeit auf. Um das nun "technisch" in den Griff zu bekommen, wurde der Speicherplatz für die Zahl in Zwei (eigentlich Drei) Teile zerlegt. Hier die Definition (Wikipedia):

Bei der Gleitkommadarstellung werden 11 Bit für den Exponenten verwendet und ein Bit für das Vorzeichen, die restlichen 52 Bit stehen für die eigentliche Zahlendarstellung (Mantisse) zur Verfügung. Eine Präzision von 53 Bit bedeutet umgerechnet ins Dezimalsystem eine ungefähre Genauigkeit auf 16 Stellen im Dezimalsystem ( {\displaystyle 53\log _{10}(2)\approx 15.955} 53\log _{{10}}(2)\approx 15.955).

Somit stehen bestimmte Mengen an Zahlen zu Verfügung, jedoch leider nicht alle zwischen den speicherbaren Stellen, denn ein halbes Bit kann man nicht speichern. :-)

Das führt zu Nebeneffekten im Umgang mit double/float Werten in der Programmierung. (Besonders beim Vergleich und im Besonderen in Kombination mit Integer-Werten). Da ich immer wieder über diese Ungenauigkeit stolperte, war es mir ein Anliegen, dieses "Problem" ganzheitlich zu lösen...


Mein erster Entwurf ist hier und ich würde diesen gerne mit euch diskutieren, sofern es denn Interesse daran gibt. Ziel ist, das Konstrukt zu optimieren und noch weiter zu generalisieren. 

Findige Köpfe haben sicher einige gute Ideen zu diesem Ansatz. 

Dateien:
ea_double.mqh  46 kb
 

Base class:


// Basic double representation class
class ea_dbl
{
    // Internal storage
    private:
        double  m_dbl;
        double  m_rounding;
        bool    m_bPRound;
        bool    m_bEmpty;
        long    m_long;
        long    m_precision;
        long    m_saved;
        long    m_points;
        long    m_pips;
        long    m_ticks;


    // Object handlers
    public:
                void    ea_dbl()                                                 { m_dbl = 0; m_saved = Digits() + 3; SetPrecision((int)m_saved); SetRounding(); m_bEmpty = true; }
                void    ea_dbl(const double inVal)                               { m_dbl = inVal; m_saved = Digits() + 3; SetPrecision((int)m_saved); SetRounding(); }
                void    ea_dbl(const ea_dbl& inVal)                              { m_dbl = inVal.Get(); m_saved = inVal.GetDigits(); SetPrecision((int)m_saved); SetRounding(inVal.GetRounding()); }
                void    ~ea_dbl()                                                { return; }



    // Public interface
    public:

        // Virtual interface
        virtual double  Get()                                              const { return(GetRawDouble()); }
        virtual void    SetRounding(const bool bPrecise = false)                 { double r = 0; m_bPRound = bPrecise; if(m_bPRound) { r = (4.9 / ((double)m_points*10)); } m_rounding = r; }
        virtual bool    GetRounding()                                      const { return(m_bPRound); }


        // Default interface
                void    SetPrecision(const int inVal = 8)                        {  m_precision = (long)inVal;
                                                                                    m_points = (long)MathPow(10, m_precision);
                                                                                    m_pips = (long)MathPow(10, (m_precision + 1) - Digits());
                                                                                    m_ticks = (long)MathPow(10, m_precision - Digits()); update(); }
                long    GetLong()                                          const { return(m_long); }
                double  GetDouble()                                        const { return(to_double(m_long)); }
                double  GetRawDouble()                                     const { return(m_dbl); }
                long    GetDigits()                                        const { return(m_precision); }
                void    CopyPrecision(const ea_dbl &inVal)                       { SetPrecision((int)inVal.GetDigits()); }


        // Sync provider (digit-sync between objects)
                void    _sync()                                                  { SetPrecision((int)m_saved); }
                long    _sync(ea_dbl &inVal)                                     { return(_sync(inVal._sync(m_precision))); }
                long    _sync(const long inVal)                                  { m_saved = m_precision; if(inVal < m_precision) { SetPrecision((int)inVal); } return(m_precision); }
                void    _unsync(ea_dbl &inVal)                                   { _sync(); inVal._sync(); }


        // Set and get different types of digits/values
                void    ticks(const long inVal)                                  { m_dbl = to_double((inVal*m_ticks)); update(); }
                long    ticks()                                            const { return(m_long/m_ticks); }
                void    pips(const long inVal)                                   { m_dbl = to_double((inVal*m_pips)); update(); }
                long    pips()                                             const { return(m_long/m_pips); }
                void    points(const long inVal)                                 { m_dbl = to_double((inVal*m_points)); update(); }
                long    points()                                           const { return(m_long/m_points); }



    // Internal helper functions
    private:

        // Keep internal data updated
                void    update()                                                 {  m_bEmpty = false; m_long = to_long(m_dbl); return; }


    protected:
        // Helpers, securing correct digit count handling
                long    to_ticks(const long inVal)                         const { return(inVal/m_ticks); }
                long    to_pips(const long inVal)                          const { return(inVal/m_pips); }
                long    to_points(const long inVal)                        const { return(inVal/m_points); }
                long    from_ticks(const long inVal)                       const { return(inVal*m_ticks); }
                long    from_pips(const long inVal)                        const { return(inVal*m_pips); }
                long    from_points(const long inVal)                      const { return(inVal*m_points); }
                long    to_long(const double inVal)                        const {  string str = DoubleToString(MathRound((double)m_points * (inVal + m_rounding)), 0);
                                                                                   return( StringToInteger(str) ); }
                double  to_double(const long inVal)                        const {  string str = IntegerToString(inVal, (int)m_precision, 0x30);
                                                                                    bool bNeg = false;
                                                                                    if(StringReplace(str, "-", "") > 0) { bNeg = true; }
                                                                                    string missing_zeroes = "";
                                                                                    for(int pos = StringLen(str) - (int)m_precision; pos <= 0; pos++)
                                                                                    { StringAdd(missing_zeroes, "0"); }
                                                                                    StringAdd(missing_zeroes, str);
                                                                                    StringConcatenate(str, bNeg?"-":"", StringSubstr(missing_zeroes, 0, StringLen(missing_zeroes) - (int)m_precision),
                                                                                                        ".", StringSubstr(missing_zeroes, StringLen(missing_zeroes) - (int)m_precision));
                                                                                   return(StringToDouble(str)); }


    // Operators
    public:

    // Generic Operators: Operators: !, ++, --

        // ea_dbl selfreferencing operators
            virtual bool    operator !  ()                                 const { return((m_bEmpty) || (m_long == 0)); }
            virtual void    operator ++ ()                                       { m_dbl += 1.0/(MathPow(10, Digits())); update(); }
            virtual void    operator -- ()                                       { m_dbl -= 1.0/(MathPow(10, Digits())); update(); }


    // Operators: ==, !=, <, >, <=, >=, +, -, *, /, %, =, +=, -=, *=, /=, %=

        // dbl class object reference
            virtual bool    operator == (ea_dbl &inVal)                          { _sync(inVal); bool bEqual = (m_long == inVal.GetLong()); _unsync(inVal); return(bEqual); }
            virtual bool    operator != (ea_dbl &inVal)                          { return(!operator == (inVal)); }
            virtual bool    operator >  (ea_dbl &inVal)                          { _sync(inVal); bool bEqual = (m_long >  inVal.GetLong()); _unsync(inVal); return(bEqual); }
            virtual bool    operator <  (ea_dbl &inVal)                          { _sync(inVal); bool bEqual = (m_long <  inVal.GetLong()); _unsync(inVal); return(bEqual); }
            virtual bool    operator >= (ea_dbl &inVal)                          { _sync(inVal); bool bEqual = (m_long >= inVal.GetLong()); _unsync(inVal); return(bEqual); }
            virtual bool    operator <= (ea_dbl &inVal)                          { _sync(inVal); bool bEqual = (m_long <= inVal.GetLong()); _unsync(inVal); return(bEqual); }

                    ea_dbl  operator +  (ea_dbl &inVal)                          { _sync(inVal); long lRetval = (m_long + inVal.GetLong()); _unsync(inVal); return(to_double(lRetval)); }
                    ea_dbl  operator -  (ea_dbl &inVal)                          { _sync(inVal); long lRetval = (m_long - inVal.GetLong()); _unsync(inVal); return(to_double(lRetval)); }
                    ea_dbl  operator *  (ea_dbl &inVal)                          { _sync(inVal); long lRetval = (m_long * inVal.GetLong()); _unsync(inVal); return(to_double(lRetval)); }
                    ea_dbl  operator /  (ea_dbl &inVal)                          { _sync(inVal); long lRetval = (m_long / inVal.GetLong()); _unsync(inVal); return(to_double(lRetval)); }
                    ea_dbl  operator %  (ea_dbl &inVal)                          { _sync(inVal); long lRetval = (m_long % inVal.GetLong()); _unsync(inVal); return(to_double(lRetval)); }

                    void    operator =  (ea_dbl &inVal)                          { _sync(inVal); m_long =  inVal.GetLong(); m_dbl = to_double(m_long); _unsync(inVal); return; }
                    void    operator += (ea_dbl &inVal)                          { _sync(inVal); m_long += inVal.GetLong(); m_dbl = to_double(m_long); _unsync(inVal); return; }
                    void    operator -= (ea_dbl &inVal)                          { _sync(inVal); m_long -= inVal.GetLong(); m_dbl = to_double(m_long); _unsync(inVal); return; }
                    void    operator *= (ea_dbl &inVal)                          { _sync(inVal); m_long *= inVal.GetLong(); m_dbl = to_double(m_long); _unsync(inVal); return; }
                    void    operator /= (ea_dbl &inVal)                          { _sync(inVal); m_long /= inVal.GetLong(); m_dbl = to_double(m_long); _unsync(inVal); return; }
                    void    operator %= (ea_dbl &inVal)                          { ticks((ticks()%inVal.ticks())); }


        // double as input
            virtual bool    operator == (const double inVal)               const { return(m_long == to_long(inVal)); }
            bool            operator != (const double inVal)               const { return(!operator == (inVal)); }
            virtual bool    operator >  (const double inVal)               const { return(m_long >  to_long(inVal)); }
            virtual bool    operator <  (const double inVal)               const { return(m_long <  to_long(inVal)); }
            virtual bool    operator >= (const double inVal)               const { return(m_long >= to_long(inVal)); }
            virtual bool    operator <= (const double inVal)               const { return(m_long <= to_long(inVal)); }

                    double  operator +  (const double inVal)               const { return(m_dbl+inVal); }
                    double  operator -  (const double inVal)               const { return(m_dbl-inVal); }
                    double  operator *  (const double inVal)               const { return(m_dbl*inVal); }
                    double  operator /  (const double inVal)               const { return(m_dbl/inVal); }
                    double  operator %  (const double inVal)               const { return((double)((m_long/m_ticks)%(to_long(inVal)/m_ticks))); }

                    void    operator =  (const double inVal)                     { m_dbl =  inVal; update(); }
                    void    operator += (const double inVal)                     { m_dbl += inVal; update(); }
                    void    operator -= (const double inVal)                     { m_dbl -= inVal; update(); }
                    void    operator *= (const double inVal)                     { m_dbl *= inVal; update(); }
                    void    operator /= (const double inVal)                     { m_dbl /= inVal; update(); }
                    void    operator %= (const double inVal)                     { m_dbl = (double)((m_long/m_ticks)%(to_long(inVal)/m_ticks)); update(); }
};


 

Ehrlich gesagt - ich glaube niemand braucht das!

Für die optische Darstellung gibt es DoubleToString(), StringFormate(), Printformate().

Für die interne Berechnung NormalizeDouble().

und für Vergleiche müsste man sich halt angewöhnen

statt zB.: if( double1 == double2)

einfach:   if ( abs(double1-double2)> _Point*0.1 )

Fehlt noch etwas?

 
Carl Schreiber:

Ehrlich gesagt - ich glaube niemand braucht das!

Für die optische Darstellung gibt es DoubleToString(), StringFormate(), Printformate().

Für die interne Berechnung NormalizeDouble().

und für Vergleiche müsste man sich halt angewöhnen

statt zB.: if( double1 == double2)

einfach:   if ( abs(double1-double2)> _Point*0.1 )

Fehlt noch etwas?

Danke für die Antwort. 

Ein sehr nützlicher Ansatz, den du da empfiehlst. - Ein sehr Pragmatischer. Das würde ich gerne adaptieren.

Allerdings empfinde ich die Handhabung von Double und Integer sowie die Einheiten Ticks, Pips und Points immer wieder verwirrend. Als Erweiterungsbeispiel habe ich eine Art "Magnet" angefügt.


Danke für deine Anregung.