English Русский 中文 Deutsch 日本語 Português
Particularidades del trabajo con números del tipo double en MQL4

Particularidades del trabajo con números del tipo double en MQL4

MetaTrader 4Ejemplos | 13 febrero 2012, 09:49
2 831 3
MetaQuotes
MetaQuotes

Introducción

Al programar en el lenguaje MQL4, los principiantes a veces se encuentran con situaciones en las que los resultados de ciertos cálculos matemáticos se diferencian de los esperados. Así y con todo, el programa se compila y funciona, pero no de la forma necesaria. Entonces los principiantes empiezan a verificar el programa, encuentran nuevos "errores" en el lenguaje, en la implementación de las funciones, etc. En la mayoría de los casos, el análisis posterior muestra que todo va bien en el lenguaje y el compilador, mientras que el texto del código ocultaba un error enojoso cuya búsqueda puede llevar mucho tiempo.

En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.


1 Control de los valores numéricos

Para comprobar los resultados de los cálculos y la depuración de programas, se puede usar la función string DoubleToStrMorePrecision(double number, int precision); de la biblioteca estándar stdlib.mq4, que permite controlar los valores numéricos de los números del tipo double hasta el dígito indicado.

Esto permitirá ahorrar tiempo al buscar posibles errores.

Ejemplo de uso:
#include <stdlib.mqh>
int start()
  {
   double a=2.0/3;
   Alert("Standard output:",a,", 8 digits precision:",DoubleToStr(a,8),", 15 digits precision:", DoubleToStrMorePrecision(a,15));
   return(0);
  }  

Resultado:

Standard output:0.6667, 8 digits precision:0.66666667, 15 digits precision:0.666666666666667


En muchos casos, al introducir los valores numéricos de los números con punto flotante (por ejemplo, al usar Print, Alert, Comment) en lugar de recurrir a la muestra estándar (solo los 4 primeros números tras la coma), es mejor utilizar la función DoubleToStrMorePrecision para controlar con mayor precisión los valores numéricos.

Ejemplo del código:

#include <stdlib.mqh>
int start()
  {
   double a=2.0/100000;
   Alert("Standard output=",a,", More precise output=",DoubleToStrMorePrecision(a,15));
   return(0);
  }

como resultado, muestra: "Standard output=0, More precise output=0.000020000000000".


2. La precisión al trabajar con números del tipo double

Las especificidades del formato de guardado de números double en la computadora provocan la limitación de la precisión de su almacenamiento y la aparición de imprecisiones al trabajar con ellos.

Por ejemplo, al usar la precisión ilimitada de cálculo, para cualquier número A y B siempre serán válidas las expresiones:

(A/B)*(B)=A,

A-(A/B)*B=0,

(A/B)*(B/A)=1, etcétera.

En la computadora, la precisión de guardado de la cantidad de dígitos decimales de los números del tipo doube viene definida por el tamaño de la mantisa y está limitada a 52 bits.

Veamos el siguiente ejemplo, que ilustra la pérdida de precisión indicada. El programa mostrado más abajo calcula en el ciclo (i) el producto de los números enteros hasta 23 (23!=25852016738884976640000), el resultado de los cálculos se guarda en una variable (a) del tipo double. En el siguiente ciclo (j) se divide el número (a) por cada uno de los números enteros hasta 23. Como resultado, lo lógico sería esperar a=1.

#include <stdlib.mqh>
int start()
  {
   int maxfact=23;
   double a=1;
   for (int i=2; i<=maxfact; i++) { a=a*i; }
   for (int j=maxfact; j>=2; j--) { a=a/j; }
   Alert(" a=",DoubleToStrMorePrecision(a,16));
   return(0);
  }

Y sin embargo, tenemos:

a=1.0000000000000002

De esta forma, al trabajar con números enteros, tenemos una imprecisión en el 16 dígito.

Si aumentamos el cálculo a 35!, obtendremos a=0.9999999999999998.

En el lenguaje MQL existe la función NormalizeDouble, que permite redondear un número del tipo double hasta la precisión indicada.

Puesto que las constantes del tipo doble se guardan en la memoria de una forma parecida a los números del tipo double, al definir una constante es necesario tener en cuenta la limitación de 15 dígitos de las cifras tras la coma.

Sin embargo, no es conveniente confundir la precisión de representación de los números double, analizada más arriba, con los límites de sus cambios, que son bastante más amplios: de -1.7*e-308 a 1.7*e308.

Podemos valorar de forma aproximada el exponente mínimo del número double, que será indistinguible de 0, con la ayuda del código siguiente:

int start()
  {
  double R=1;
  int minpwr=0;
  while (R>0) {R=R/10; minpwr--;}
  Alert(minpwr);
  return(0);
  }



3. La función NormalizeDouble

La función double NormalizeDouble (double value, int digits) redondea el número value con una precisión de digits dígitos.

En el ejemplo:

int start()
  {
   double a=3.141592663589;
   Alert("a=",DoubleToStr(NormalizeDouble(a,5),8));
   return(0);
  }

el resultado será

a=3.14159000

Es imprescindible usar NormalizeDouble() al utilizar números del tipo double como argumentos de las funciones para ejecutar operaciones comerciales. En las operaciones comerciales no se deben utilizar precios no normalizados cuya precisión supere aunque sea en un dígito la requerida por el servidor comercial.

Los valores calculados StopLoss, TakeProfit, así como los valores del precio de apertura de las órdenes pendientes deberán ser normalizados con una precisión cuyo valor se guardará en una variable Digits predeterminada.


4. Peculiaridades en la comparación de números del tipo double

Es recomendable realizar la operación de comparación de la igualdad de dos números double con la ayuda de la función bool CompareDoubles(double number1,double number2) de la biblioteca estándar stdlib.mq4, que tiene el aspecto:

//+------------------------------------------------------------------+
//| correct comparison of 2 doubles                                  |
//+------------------------------------------------------------------+
bool CompareDoubles(double number1,double number2)
  {
   if(NormalizeDouble(number1-number2,8)==0) return(true);
   else return(false);
  }

Esta función compara los números number1 y number2 del tipo double con una precisión de 8 dígitos decimales.

Ejemplo:

#include <stdlib.mqh>
int start()
  {double a=0.123456781;
   double b=0.123456782; 
   if (CompareDoubles(a,b)) {Alert("They are equal");}
   else {Alert("They are different");}
  }


mostrará

They are equal

puesto que los números a y b se diferencian solo en el 9 dígito.

En caso necesario, el uso de una precisión más elevada se puede escribir de forma análoga en su propia función de comparación hasta la precisión necesaria.


5. División de números enteros

Debemos recordar que al dividir dos números enteros, el resultado será un número entero.

Por eso el código:

int start()
  {
   Alert(70/100);
   return(0);
  }


mostrará 0, puesto que 70 y 100 son valores enteros. Al igual que en el lenguaje C, en MQL4 el resultado de la división de un número entero por otro no será un número entero, en este caso, será 0.

Sin embargo, si uno de sus valores es un número del tipo double (es decir, tiene una parte fraccionada), el resultado de la división será un número del tipo double. Por eso, Alert(70/100.0); dará el número 0.7. Asimismo, debemos recordar las normas de conversión de tipos y formalizar con más cuidado la expresión.

Ejemplo del código:

int start()
  { double a=1/3;
    double b=1.0/3;
   Alert("a=",a,", b=",b);
   return(0);
  }


dará a=0, b=0.3333


6. Conversión de tipos para integer y double

Vamos a ver este segmento de código:
double xbaseBid=1.2972;
double xBid=1.2973;
double xPoint=0.0001;
int i = 100 + (xBid - xbaseBid)/xPoint;
Alert(i);


Como resultado del trabajo, se mostrará el número 100, aunque parece que (i) debería ser igual a 101, dado que 0.0001/0.0001=1.

Un ejemplo análogo en C/C++:

double baseBid=1.2972,Bid=1.2973,Point=0.0001;
int i = 100 + (Bid - baseBid)/Point;
printf("%d\n",i);

también dara 100.

Para investigar los motivos de esta circunstancia, analizaremos el código:
double a=0.99999999999999;
int i = 100 + a;
Alert(i);

El resultado del funcionamiento será la muestra del número i=100.

Sin embargo, si mejoramos la precisión de la cifra a:

double a=0.999999999999999;
int i = 100 + a;
Alert(i);

obtendremos 101. La causa es el uso mezclado de números enteros y números con punto flotante, el motivo empeora por la reducida precisión de los valores.

Por eso, al realizar operaciones de este tipo, se recomienda redondear estas expresiones con la ayuda de la función double MathRound(double value), que retorna un valor redondeado hasta el número entero más próximo al valor numérico indicado:

double baseBid=1.2972;
double xBid=1.2973;
double xPoint=0.0001;
int i = 100 + MathRound((xBid - baseBid)/xPoint);
Alert(i);

En este caso, obtendremos el valor correcto 101.

Con frecuencia nos encontramos con un error (sobre todo en los fragmentos de código responsables del Trailing Stop), cuando no se realiza correctamente la comparación de números del tipo double y se usan posteriormente al llamar la función OrderModify(), que cuando se intentan cambiar los mismos parámetros ya establecidos, da el error número 1: ERR_NO_RESULT.

Por eso debemos comprobar minuciosamente las operaciones de comparación (recuerde la normalización) y las expresiones semejantes para calcular la cantidad de puntos. Es conveniente recordar que el terminal permite cambiar las órdenes con la función OrderModify solo en el caso de que los nuevos valores numéricos se diferencian de los antiguos aunque sea en 1 punto.


7. Peculiaridades de la función MathMod

El resultado del funcionamiento de la función MathMod(double v1, double v2) en el lenguaje MQL4 se corresponde totalmente con el resultado del funcionamiento de la función fmod(double v1, double v2) de la biblioteca matemática MSVC6, puesto que al ser ejecutada se usa la llamada directa de esta función en C Runtime Library. En algunos casos, la función fmod en MSVC6 (y por consiguiente MathMod), da un resultado incorrecto.

Si en sus programas se usa esta función, sustituya la llamada de MathMod por la llamada de la siguiente función, que siempre retorna el resultado correcto:

double MathModCorrect(double a, double b)
{ int tmpres=a/b;
return(a-tmpres*b);
}


Es necesario destacar que esta peculiaridad solo tiene lugar en MQL4, en MQL5 el cálculo de esta función se realiza de acuerdo con la definición matemática de la función.


Conclusión

Es necesario aclarar que la lista mostrada aquí no es exhaustiva, si usted ha detectado alguna nueva situación no descrita en estos apuntes, mire los comentarios más abajo. Si aún no ha encontrado una solución, describa la situación usted mismo en los comentarios, adjunte el fragmento de código correspondiente y los especialistas de la comunidad MQL4 intentarán ayudarle.

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/1561

Rafacontrol95
Rafacontrol95 | 10 feb. 2024 en 04:49
Tengo una situación a la que no puedo implementar alguna solución nombrada anteriormente. Al hacer una multiplicación por 10 dentro de un ciclo “while”, en la segunda iteración me genera un número extraordinario, no puedo precisar los dígitos que quiero visualizar porque el ciclo precisamente se detiene cuando llega al último con la intención de conocer los dígitos que existen en algún número determinado que se genera a través de una división. Si los precioso a través de un NormalizeDouble no sabré cuanto dígitos originales habían. Puse la función MathAbs para que generara el valor absoluto porque supuse podría funcionar pero tampoco me funciona, alguien que me pueda ayudar?
Enrique Enguix
Enrique Enguix | 10 feb. 2024 en 13:31
Rafacontrol95 #:
Tengo una situación a la que no puedo implementar alguna solución nombrada anteriormente. Al hacer una multiplicación por 10 dentro de un ciclo “while”, en la segunda iteración me genera un número extraordinario, no puedo precisar los dígitos que quiero visualizar porque el ciclo precisamente se detiene cuando llega al último con la intención de conocer los dígitos que existen en algún número determinado que se genera a través de una división. Si los precioso a través de un NormalizeDouble no sabré cuanto dígitos originales habían. Puse la función MathAbs para que generara el valor absoluto porque supuse podría funcionar pero tampoco me funciona, alguien que me pueda ayudar?
Entiendo que estás teniendo problemas al implementar una solución en MQL4 donde al realizar una multiplicación por 10 dentro de un bucle "while", en la segunda iteración obtienes un número inesperadamente grande, y no puedes precisar los dígitos que deseas visualizar. Además, mencionas que utilizas NormalizeDouble para tratar de conocer los dígitos originales pero no estás obteniendo los resultados deseados.

Si estás tratando de trabajar con valores numéricos grandes y quieres evitar que se generen cantidades excesivas de dígitos, la función NormalizeDouble no te ayudará en este caso, ya que su propósito principal es redondear los valores de doble precisión a un número específico de dígitos decimales.

Para abordar tu problema, es importante entender por qué se está generando un número extraordinariamente grande en la segunda iteración de tu bucle "while". Algunas cosas a  considerar:

1. Desbordamiento de variables: Si estás utilizando variables de tipo entero en MQL4, ten en cuenta que tienen límites en sus valores (pueden ser de -2,147,483,648 a 2,147,483,647 para int). Asegúrate de que tu variable puede contener el resultado de la multiplicación por 10 sin desbordarse.

2. Uso de variables temporales: Puedes intentar utilizar variables temporales para almacenar valores intermedios durante la multiplicación por 10, de esta manera puedes controlar y monitorear los cambios en los valores.

3. Utilizar funciones de redondeo: Dependiendo de tus necesidades, puedes considerar el uso de funciones de redondeo como MathRound, MathCeil o MathFloor para ajustar el resultado de la multiplicación a un número manejable.
Rafacontrol95
Rafacontrol95 | 12 feb. 2024 en 20:03
@Enrique Enguix, Muchas gracias por la pronta respuesta.
Enrique Enguix #: 1. Desbordamiento de variables: Si estás utilizando variables de tipo entero en MQL4, ten en cuenta que tienen límites en sus valores (pueden ser de -2,147,483,648 a 2,147,483,647 para int). Asegúrate de que tu variable puede contener el resultado de la multiplicación por 10 sin desbordarse.

La variable es de tipo double sabe cuál es el límite de los valores de este tipo?

Enrique Enguix #: 2. Uso de variables temporales: Puedes intentar utilizar variables temporales para almacenar valores intermedios durante la multiplicación por 10, de esta manera puedes controlar y monitorear los cambios en los valores.

Lo he implementado pero no me ha dado resultado

Enrique Enguix #: 3. Utilizar funciones de redondeo: Dependiendo de tus necesidades, puedes considerar el uso de funciones de redondeo como MathRound, MathCeil o MathFloor para ajustar el resultado de la multiplicación a un número manejable.

Igualmente lo he implementado pero el resultado hace que desconozca el resultado original de la variable

Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Redes neuronales: así de sencillo (Parte 65): Aprendizaje supervisado ponderado por distancia (DWSL) Redes neuronales: así de sencillo (Parte 65): Aprendizaje supervisado ponderado por distancia (DWSL)
En este artículo, le presentaremos un interesante algoritmo que se basa en la intersección de los métodos de aprendizaje supervisado y por refuerzo.
Una rápida inmersión en MQL5 Una rápida inmersión en MQL5
¿Ha decidido estudiar el lenguaje de programación de estrategias de trading MQL5 pero no sabe nada sobre él? Hemos intentado describir el terminal de MQL5 y Meta Trader 5 desde el punto de vista de una persona iniciada y para ello hemos escrito este corto artículo introductorio. En este artículo encontrará una breve descripción de las posibilidades de este lenguaje, así como algunos consejos sobre cómo trabajar con MetaEditor 5 y el terminal.
Algoritmos de optimización de la población: Evolución diferencial (Differential Evolution, DE) Algoritmos de optimización de la población: Evolución diferencial (Differential Evolution, DE)
En este artículo, hablaremos del algoritmo que muestra los resultados más controvertidos entre todos los anteriormente analizados, el algoritmo de Evolución Diferencial (DE).