Librerías: CDouble & CDoubleVector

 

CDouble & CDoubleVector:

Librería para ejecutar los métodos comunes del redondeo utilizados en el desarrollo de las aplicaciones MQL, clase-envoltorio primitiva para los valores tipo double y vector para los objetos CDouble. ¡Está compatible con MQL5 y MQL4!

Autor: nicholi shen

 

Intenta eliminar la restricción

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

E intenta que los operadores de comparación sean "estáticos".

 
fxsaber:

Intenta eliminar la restricción

E intenta que los operadores de comparación sean "estáticos".


Los operadores no pueden declararse "estáticos".

Puede haber más de una operación aritmética (sobrecarga) por sentencia siempre que cada conjunto de dos operandos esté envuelto entre paréntesis en el orden correcto. Sigo sin recomendar esto.

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);// ¡VALE!

 

Para aclarar cualquier posible confusión, la forma correcta de manejar la aritmética para sentencias con más de un operador es no utilizar los operadores sobrecargados, sino utilizar uno de los métodos de obtención de valores aplicables.

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

Los operadores no pueden declararse "estáticos".

Puede haber más de una operación aritmética (sobrecarga) por sentencia siempre que cada conjunto de dos operandos esté envuelto entre paréntesis en el orden correcto. Sigo sin recomendar esto.

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; //¡VALE!
  
  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)
}

Resultado

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:

Resultado


Esto es muy inteligente y me gusta mucho, pero es demasiado inteligente para la mayoría de los usuarios ... (ambos hemos sido acusados de eso en los foros ;) Voy a confirmar sus cambios a mi biblioteca personal, y otros pueden también, pero para el beneficio de la mayor base de usuarios que voy a mantenerlo simple y se adhieren a la recomendación oficial de llamar a uno de los métodos getter. ( eg. num1.AsRounded() * num2.AsRounded() + num3.AsRounded() )


Personalmente me gusta (num1*num2+num3).AsRounded()


Desafíos con CDouble2 como se propone:

void Func(double param)
{
}

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

* Versión 1.01:

  • Corregido error por el que los operadores aritméticos no devolvían valores redondeados.
  • Añadido el método symbol setter para establecer el símbolo después de llamar al constructor.
 

Hola, nicholishen. He probado su biblioteca durante algún tiempo. Es muy buena y hace que el redondeo de precios y lotes sea un trabajo fácil.

Pero, tengo algunas preocupaciones con respecto a la exactitud de sus métodos de redondeo. He encontrado muchos errores de redondeo en las funciones RoundToStep(), RoundToStepUp(), RoundToStepDown() y RoundToTick(). Estos errores siempre se producen en números de arista que terminan en 5 (como 1.12345).

Por ejemplo CDouble::RoundToStep(1.700605, 0.00001) devuelve 1.70060, en lugar del resultado correcto 1.70061

La ecuación redondear(número / punto) * punto, debe corregirse a redondear(número * potencia) / potencia, donde tanto el punto como la potencia se derivan del número de dígitos decimales a los que se desea redondear.

Porque el valor de 1 punto que se supone = 0,00001, en realidad está codificado como 0,0000100000000000000008180305391403130954586231382563710 como coma flotante de doble precisión de 64 bits. Esto hace que el resultado final de su método de redondeo, round(number / point) * point, se desvíe del resultado correcto en 1 punto (0.00001), muy a menudo.

Además, para hacer un redondeo 'aritmético' apropiado (redondeo de punto medio lejos de cero), un buen método es sumar o restar un medio-epsilon como corrección. (Esto compensará cualquier redondeo medio a cero que haya sido aplicado por el procesador, como lo ordenan las especificaciones IEEE-754, particularmente en los casos del borde del punto medio).

La función NormalizeDouble() de mql maneja todos esos problemas correctamente, deberías usarla para hacer un redondeo 'aritmético' apropiado.

Aquí, también está el código fuente de una función que escribí para hacer el redondeo aritmético, puedes probarlo tú mismo. Esta función tiene exactamente los mismos resultados que NormalizeDouble(). Mi función se ejecuta aún más rápido y soporta un mayor nivel de precisión de redondeo. (MQL's NormalizeDouble() se limita a 8 dígitos decimales).

/**
 * MidpointRounding lejos de cero (redondeo 'aritmético')
 * Utiliza un medio-epsilon para la corrección. (Esto compensa el redondeo IEEE-754
 * medio-epsilon que se aplicó en los casos extremos).
 */
double RoundCorrect(double num, int precision) {
        double c = 0.5 * DBL_EPSILON * num;
// double p = MathPow(10, precisión); //lento
        double p = 1; while (precision--> 0) p *= 10;
        if (num < 0)
                p *= -1;
        return MathRound((num + c) * p) / p;
}
 

Además, aquí tienes un script que puedes usar para depurar la precisión del redondeo en la librería CDouble. Espero que te resulte útil.

#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() {

        // prueba de redondeo de algunos casos extremos
        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 );
                // comparar 3 funciones (redondear a 2 dígitos)
                PRINT( CDouble::RoundToStep(num, 0.01) );
                PRINT( NormalizeDouble(num, 2) );
                PRINT( RoundCorrect(num, 2) );
        }

        forEach (num, numbers_6) {

                Print("----------------------------------------------");
                PRINT( num );
                // comparar 3 funciones (redondear a 5 dígitos)
                PRINT( CDouble::RoundToStep(num, 0.00001) );
                PRINT( NormalizeDouble(num, 5) );
                PRINT( RoundCorrect(num, 5) );
        }

        // La causa de los problemas de redondeo en la biblioteca CDouble
        Print("----------------------------------------------");

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

        // comparar NormalizeDouble y RoundCorrect por igualdad exacta
        Print("----------------------------------------------");

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

}

 
amrali:

Hola, nicholishen. He probado su biblioteca durante algún tiempo. Es una gran biblioteca y hace que el redondeo de los precios y los lotes de un trabajo fácil .

Pero, tengo algunas preocupaciones con respecto a la exactitud de sus métodos de redondeo. He encontrado muchos errores de redondeo en las funciones RoundToStep(), RoundToStepUp(), RoundToStepDown() y RoundToTick(). Estos errores siempre ocurren en números de aristas que terminan en 5 (como 1.12345).

Por ejemplo CDouble::RoundToStep(1.700605, 0.00001) devuelve 1.70060, en lugar del resultado correcto 1.70061

La ecuación redondear(número / punto) * punto, debe corregirse a redondear(número * potencia) / potencia, donde tanto el punto como la potencia se derivan del número de dígitos decimales a los que se desea redondear.

Porque el valor de 1 punto que se supone = 0,00001, en realidad está codificado como 0,0000100000000000000008180305391403130954586231382563710 como coma flotante de doble precisión de 64 bits. Esto hace que el resultado final de su método de redondeo, round(number / point) * point, se desvíe del resultado correcto en 1 punto (0.00001), muy a menudo.

Además, para hacer un redondeo 'aritmético' apropiado (redondeo de punto medio lejos de cero), un buen método es sumar o restar un medio-epsilon como corrección. (Esto compensará cualquier redondeo medio a cero que haya sido aplicado por el procesador, tal y como exigen las especificaciones IEEE-754, especialmente en los casos límite del punto medio).

La función NormalizeDouble() de mql maneja todos esos problemas correctamente, deberías usarla para hacer un redondeo 'aritmético' apropiado.

Aquí, también está el código fuente de una función que escribí para hacer el redondeo aritmético, puedes probarlo tú mismo. Esta función tiene exactamente los mismos resultados que NormalizeDouble(). Mi función se ejecuta aún más rápido y soporta un mayor nivel de precisión de redondeo. (MQL's NormalizeDouble() se limita a 8 dígitos decimales).

Gracias por señalar esto. Voy a actualizar el código para utilizar NormalizeDouble en lugar de redondear.

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

Hola, nicholishen. He probado su biblioteca durante algún tiempo. Es una gran biblioteca y hace que el redondeo de los precios y los lotes de un trabajo fácil .

Pero, tengo algunas preocupaciones con respecto a la exactitud de sus métodos de redondeo. He encontrado muchos errores de redondeo en las funciones RoundToStep(), RoundToStepUp(), RoundToStepDown() y RoundToTick(). Estos errores siempre ocurren en números de aristas que terminan en 5 (como 1.12345).

Por ejemplo CDouble::RoundToStep(1.700605, 0.00001) devuelve 1.70060, en lugar del resultado correcto 1.70061

La ecuación redondear(número / punto) * punto, debe corregirse a redondear(número * potencia) / potencia, donde tanto el punto como la potencia se derivan del número de dígitos decimales a los que se desea redondear.

Porque el valor de 1 punto que se supone = 0,00001, en realidad está codificado como 0,0000100000000000000008180305391403130954586231382563710 como coma flotante de doble precisión de 64 bits. Esto hace que el resultado final de su método de redondeo, round(number / point) * point, se desvíe del resultado correcto en 1 punto (0.00001), muy a menudo.

Sin embargo, desde un punto de vista comercial, necesita 1,70060 o 1,70061, ambos son correctos. Por lo tanto, es posible que desee elegir la mejor opción en función de sus operaciones comerciales, en lugar de confiar en un esquema de redondeo matemático.