Particularidades del trabajo con números del tipo double en MQL4
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
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.
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
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
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?
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.
La variable es de tipo double sabe cuál es el límite de los valores de este tipo?
Lo he implementado pero no me ha dado resultado
Igualmente lo he implementado pero el resultado hace que desconozca el resultado original de la variable