Bibliotheken: CDouble & CDoubleVector

 

CDouble & CDoubleVector:

Eine Bibliothek für gängige Rundungsmethoden in der MQL-Entwicklung, primitive Wrapper-Klasse für Typ (double) und Vektor für CDouble-Objekte. MQL5 und MQL4 kompatibel!

Eine Bibliothek für gängige Rundungsmethoden in der MQL-Entwicklung, primitive Wrapper-Klasse für Typ (double) und Vektor für CDouble-Objekte. MQL5 und MQL4 kompatibel!

Version 1.02: (2018.02.18)

  • Es wurde ein Fehler behoben, bei dem ein gerundetes Ergebnis vom erwarteten Ergebnis abweichen konnte. DANKE AMRALI!

Version 1.01:

  • Fehler behoben, bei dem arithmetische Operatoren keine gerundeten Werte zurückgaben.
  • Symbol Setter Methode hinzugefügt, um das Symbol nach dem Aufruf des Konstruktors zu setzen.


Autor: nicholi shen

 

Versuchen Sie, die Einschränkung zu entfernen

Note: Only one arithmetic operator can be used per statement.

Und versuchen Sie, Vergleichsoperatoren "statisch" zu machen.

 
fxsaber:

Versuchen Sie, die Einschränkung zu entfernen

Und versuchen Sie, Vergleichsoperatoren "statisch" zu machen.


Operatoren können nicht als "statisch" deklariert werden.

Es kann mehr als eine (überladene) arithmetische Operation pro Anweisung geben, vorausgesetzt, dass jeder Satz von zwei Operanden in der richtigen Reihenfolge in Klammern eingeschlossen ist. Ich empfehle dies trotzdem nicht.

CDouble foo = 3, bar = 4, spam = 3;

CDouble error   = foo+bar+spam; //ERROR

CDouble error_too = (pi2 + pi5)+pi2; //ERROR

CDouble correct = foo+(bar+spam);// OK!

 

Um mögliche Unklarheiten zu beseitigen, ist die korrekte Art und Weise, die Arithmetik für Anweisungen mit mehr als einem Operator zu behandeln, nicht die überladenen Operatoren zu verwenden, sondern stattdessen eine der anwendbaren value get-Methoden.

CDouble sum, foo=3, bar=2, spam=1;
sum = foo.AsRawDouble() + bar.AsRounded() + spam.AsRoundedTick();
Print(sum.ToString());
//6
 
nicholishen:

Operatoren können nicht als "statisch" deklariert werden.

Es kann mehr als eine arithmetische Operation (Überladung) pro Anweisung geben, vorausgesetzt, dass jeder Satz von zwei Operanden in der richtigen Reihenfolge in Klammern eingeschlossen wird. Ich empfehle dies trotzdem nicht.

class CDouble2 : public CDouble
{
private:
  static CDouble2 TmpDouble;
  
public:
  const CDouble2* const operator +( const double Value ) const
  {
    CDouble2::TmpDouble = this.m_value + Value;
    
    return(&CDouble2::TmpDouble);
  }
  
  const CDouble2* const operator +( const CDouble2 &Other ) const 
  {
    return(this + Other.m_value);
  }
  
  static CDouble2* const Compare2( const double Value )
  {
    CDouble2::TmpDouble = Value;
    
    return(&CDouble2::TmpDouble);
  }
  
  static CDouble2* const Compare2( const CDouble2 &Other )
  {
    CDouble2::TmpDouble = Other;
    
    return(&CDouble2::TmpDouble);
  }  
};

static CDouble2 CDouble2::TmpDouble;

#define _CP(A) CDouble2::Compare2(A)

#define  PRINT(A) Print(#A + " = " + (string)(A));

void OnStart()
{
  CDouble2 foo = 3, bar = 4, spam = 3;
  CDouble2 error   = foo+bar+spam + foo+bar+spam; //OK!
  
  PRINT(error.ToString()); // 10
  PRINT(_CP(foo + error + 5) > 2);
  PRINT(_CP(25) > foo + bar + 7 +spam);
  PRINT((foo + bar + spam + 9).ToString());
  PRINT((_CP(9) + foo).ToString());
  PRINT(foo + 7 > 11)
}

Ergebnis

error.ToString() = 20
_CP(foo+error+5)>2 = true
_CP(25)>foo+bar+7+spam = false
(foo+bar+spam+9).ToString() = 19
(_CP(9)+foo).ToString() = 12
foo+7>11 = false
 
fxsaber:

Ergebnis


Das ist sehr clever und gefällt mir sehr gut, aber es ist zu clever für die meisten Benutzer... (das wurde uns beiden in den Foren schon vorgeworfen ;) Ich werde Ihre Änderungen in meine persönliche Bibliothek übernehmen, und andere können das auch, aber zum Nutzen einer größeren Zahl von Benutzern werde ich es einfach halten und bei der offiziellen Empfehlung bleiben, eine der Getter-Methoden aufzurufen. ( z.B. num1.AsRounded() * num2.AsRounded() + num3.AsRounded() )


FWIW Ich persönlich mag (num1*num2+num3).AsRounded()


Herausforderungen mit CDouble2 wie vorgeschlagen:

void Func(double param)
{
}

void OnStart()
{
   CDouble2 foo = 2, bar = 3;
   double number = foo+bar; //ERROR
   Func(foo+bar); //ERROR
}
 

* Version 1.01:

  • Fehler behoben, bei dem arithmetische Operatoren keine gerundeten Werte zurückgaben.
  • Symbol Setter Methode hinzugefügt, um das Symbol zu setzen, nachdem der Konstruktor aufgerufen wurde.
 

Hallo, nicholishen. Ich habe deine Bibliothek für einige Zeit getestet. Sie ist großartig und macht das Runden von Preisen und Mengen zu einer einfachen Aufgabe.

Aber ich habe einige Bedenken bezüglich der Genauigkeit Ihrer Rundungsmethoden. Ich habe eine Menge Rundungsfehler in den Funktionen RoundToStep(), RoundToStepUp(), RoundToStepDown() und RoundToTick() gefunden. Diese Fehler treten immer bei Randzahlen auf, die auf 5 enden (wie 1.12345).

Zum Beispiel gibt CDouble::RoundToStep(1.700605, 0.00001) 1.70060 zurück, anstatt des korrekten Ergebnisses 1.70061

Die Gleichung round(number / point) * point sollte in round(number * power) / power korrigiert werden, wobei sowohl point als auch power von der Anzahl der Dezimalstellen abgeleitet sind, auf die gerundet werden soll.

Denn der Wert von 1 Punkt, der eigentlich 0,00001 sein sollte, ist als 64-Bit-Gleitkommazahl mit doppelter Genauigkeit tatsächlich als 0,0000100000000000000008180305391403130954586231382563710 kodiert. Dies führt dazu, dass das Endergebnis Ihrer Rundungsmethode, round(number / point) * point, sehr oft um 1 Punkt (0,00001) vom richtigen Ergebnis abweicht.

Um eine korrekte "arithmetische" Rundung durchzuführen (Midpoint Rounding away from zero), ist es außerdem eine gute Methode, ein halbes Epsilon als Korrektur zu addieren oder zu subtrahieren. (Dies gleicht die vom Prozessor vorgenommene Halb-zu-Gleich-Rundung aus, wie sie in den IEEE-754-Spezifikationen vorgeschrieben ist, insbesondere bei den Midpoint-Randfällen).

Die Funktion NormalizeDouble() von mql behandelt all diese Probleme korrekt, Sie sollten sie verwenden, um eine korrekte "arithmetische" Rundung durchzuführen.

Hier ist auch der Quellcode einer Funktion, die ich für die arithmetische Rundung geschrieben habe, Sie können sie selbst testen. Diese Funktion hat genau die gleichen Ergebnisse wie NormalizeDouble(). Meine Funktion läuft sogar schneller und unterstützt eine höhere Rundungsgenauigkeit. (MQLs NormalizeDouble() ist auf 8 Dezimalstellen beschränkt).

/**
 * MidpointRounding away from zero ('arithmetische' Rundung)
 * Verwendet ein Halb-Epsilon zur Korrektur. (Dies gleicht die IEEE-754
 * Halb-zu-gerade-Rundung aus, die in den Randfällen angewendet wurde).
 */
double RoundCorrect(double num, int precision) {
        double c = 0.5 * DBL_EPSILON * num;
// double p = MathPow(10, Genauigkeit); //slow
        double p = 1; while (precision--> 0) p *= 10;
        if (num < 0)
                p *= -1;
        return MathRound((num + c) * p) / p;
}
 

Außerdem finden Sie hier ein Skript, das Sie verwenden können, um die Rundungsgenauigkeit in der CDouble-Bibliothek zu testen. Ich hoffe, es ist nützlich für Sie.

#property strict

#define  PRINT(A) Print(#A + " = ", (A))
#define  forEach(element, array)  for (int __i = 0, __max = ArraySize((array)); __i < __max && ((element) = array[__i]) == (element); __i++)

#include "CDouble.mqh"

//+------------------------------------------------------------------+
//||
//+------------------------------------------------------------------+
string DoubleToFixed(double number, int decimals = 55) {

        return StringFormat(StringFormat("%%#.%if", decimals), number);
}

//+------------------------------------------------------------------+
//||
//+------------------------------------------------------------------+
void OnStart() {

        // Test der Rundung einiger Randfälle
        double numbers_3[] = {1.005, 2.175, 5.015, 16.025};
        double numbers_6[] = {1.011885, 1.113325, 1.143355, 1.700605};

        double num;

        forEach (num, numbers_3) {

                Print("----------------------------------------------");
                PRINT( num );
                // 3 Funktionen vergleichen (auf 2 Stellen runden)
                PRINT( CDouble::RoundToStep(num, 0.01) );
                PRINT( NormalizeDouble(num, 2) );
                PRINT( RoundCorrect(num, 2) );
        }

        forEach (num, numbers_6) {

                Print("----------------------------------------------");
                PRINT( num );
                // 3 Funktionen vergleichen (auf 5 Stellen runden)
                PRINT( CDouble::RoundToStep(num, 0.00001) );
                PRINT( NormalizeDouble(num, 5) );
                PRINT( RoundCorrect(num, 5) );
        }

        // Die Ursache für Rundungsprobleme in der CDouble-Bibliothek
        Print("----------------------------------------------");

        PRINT( DoubleToFixed(0.01, 55) );      // 0.0000100000000000000008180305391403130954586231382563710
        PRINT( DoubleToFixed(0.00001, 55) );   // 0.0100000000000000002081668171172168513294309377670288086

        // Vergleich von NormalizeDouble und RoundCorrect durch exakte Gleichheit
        Print("----------------------------------------------");

        PRINT( NormalizeDouble(numbers_6[0], 5) == RoundCorrect(numbers_6[0], 5) );  // wahr
        PRINT( NormalizeDouble(numbers_6[0], 4) == RoundCorrect(numbers_6[0], 4) );  // wahr
        PRINT( NormalizeDouble(numbers_6[0], 3) == RoundCorrect(numbers_6[0], 3) );  // wahr
        PRINT( NormalizeDouble(numbers_6[0], 2) == RoundCorrect(numbers_6[0], 2) );  // wahr
        PRINT( NormalizeDouble(numbers_6[0], 1) == RoundCorrect(numbers_6[0], 1) );  // wahr

}

 
amrali:

Hallo, nicholishen. Ich habe deine Bibliothek für einige Zeit getestet. Sie ist toll und macht das Runden von Preisen und Losen zu einer einfachen Aufgabe.

Aber ich habe einige Bedenken bezüglich der Genauigkeit Ihrer Rundungsmethoden. Ich habe eine Menge Rundungsfehler in den Funktionen RoundToStep(), RoundToStepUp(), RoundToStepDown() und RoundToTick() gefunden. Diese Fehler treten immer bei Randzahlen auf, die auf 5 enden (wie 1.12345).

Zum Beispiel gibt CDouble::RoundToStep(1.700605, 0.00001) 1.70060 zurück, anstatt des korrekten Ergebnisses 1.70061

Die Gleichung round(number / point) * point sollte in round(number * power) / power korrigiert werden, wobei sowohl point als auch power von der Anzahl der Dezimalstellen abgeleitet sind, auf die gerundet werden soll.

Denn der Wert von 1 Punkt, der eigentlich 0,00001 sein sollte, ist als 64-Bit-Gleitkommazahl mit doppelter Genauigkeit tatsächlich als 0,0000100000000000000008180305391403130954586231382563710 kodiert. Dies führt dazu, dass das Endergebnis Ihrer Rundungsmethode, round(number / point) * point, sehr oft um 1 Punkt (0,00001) vom richtigen Ergebnis abweicht.

Um eine korrekte "arithmetische" Rundung durchzuführen (Midpoint Rounding away from zero), ist es außerdem eine gute Methode, ein halbes Epsilon als Korrektur zu addieren oder zu subtrahieren. (Dadurch wird eine vom Prozessor vorgenommene Halb-zu-Gerade-Rundung ausgeglichen, wie sie in den IEEE-754-Spezifikationen vorgeschrieben ist, insbesondere bei den Midpoint-Randfällen).

Die Funktion NormalizeDouble() von mql behandelt all diese Probleme korrekt, Sie sollten sie verwenden, um eine korrekte "arithmetische" Rundung durchzuführen.

Hier ist auch der Quellcode einer Funktion, die ich für die arithmetische Rundung geschrieben habe, Sie können sie selbst testen. Diese Funktion hat genau die gleichen Ergebnisse wie NormalizeDouble(). Meine Funktion läuft sogar schneller und unterstützt eine höhere Rundungsgenauigkeit. (MQLs NormalizeDouble() ist auf 8 Dezimalstellen beschränkt).

Vielen Dank für diesen Hinweis. Ich werde den Code aktualisieren, um NormalizeDouble anstelle von round zu verwenden.

step * NormalizeDouble(number_to_round / step, 0)
 
amrali:

Hallo, nicholishen. Ich habe deine Bibliothek für einige Zeit getestet. Sie ist toll und macht das Runden von Preisen und Losen zu einer einfachen Aufgabe.

Aber ich habe einige Bedenken bezüglich der Genauigkeit Ihrer Rundungsmethoden. Ich habe eine Menge Rundungsfehler in den Funktionen RoundToStep(), RoundToStepUp(), RoundToStepDown() und RoundToTick() gefunden. Diese Fehler treten immer bei Randzahlen auf, die auf 5 enden (wie 1.12345).

Zum Beispiel gibt CDouble::RoundToStep(1.700605, 0.00001) 1.70060 zurück, statt des richtigen Ergebnisses 1.70061

Die Gleichung round(number / point) * point sollte in round(number * power) / power korrigiert werden, wobei sowohl point als auch power von der Anzahl der Dezimalstellen abgeleitet sind, auf die gerundet werden soll.

Denn der Wert von 1 Punkt, der eigentlich 0,00001 sein sollte, ist als 64-Bit-Gleitkommazahl mit doppelter Genauigkeit tatsächlich als 0,0000100000000000000008180305391403130954586231382563710 kodiert. Dies führt dazu, dass das Endergebnis Ihrer Rundungsmethode, round(number / point) * point, sehr oft um 1 Punkt (0,00001) vom richtigen Ergebnis abweicht.

Vom Standpunkt des Handels aus gesehen brauchen Sie jedoch entweder 1,70060 oder 1,70061, beide sind korrekt. Vielleicht möchten Sie also die beste Lösung für Ihre Handelsoperationen wählen, anstatt sich auf ein mathematisches Rundungsschema zu verlassen.