Números de punto flotante
En la vida cotidiana utilizamos números con coma (o punto) decimal, o números reales, con la misma frecuencia que los enteros. El propio nombre, «real», indica que, con dichos números, se puede expresar algo tangible del mundo real, como el peso, la longitud o la temperatura del cuerpo; es decir, todo aquello que se puede medir con una cantidad de unidades no entera, sino con «un poco más».
En trading también utilizamos a menudo los números reales. Por ejemplo, estos números se utilizan para expresar volúmenes o precios de símbolos en órdenes de trading (normalmente permiten las partes fraccionarias de un lote de tamaño completo).
En MQL5 se ofrecen 2 tipos reales: float para una precisión normal y double para una precisión doble.
En el código fuente, los valores constantes de los tipos float y double suelen registrarse como un número entero y una parte fraccionaria (cada uno de ellos es una secuencia de dígitos), separados por el carácter '.', como 1.23 o -789.01. Puede que no haya entero ni fracción (pero no ambos a la vez), pero el punto es obligatorio. Por ejemplo, .123 significa 0,123, mientras que 123. significa 123,0. 123 solo creará una constante de tipo entero.
No obstante, existe otra forma de registrar las constantes reales: la exponencial. En ella, la parte entera y la fraccionaria van seguidas de 'E' o 'e' (es indiferente que sea mayúscula o minúscula) y de un número entero que representa la potencia a la que hay 10 debe elevarse para obtener un factor adicional. Por ejemplo, las siguientes representaciones muestran el mismo número, 0.57, de forma exponencial:
.0057e2
|
Cuando se registran constantes reales, estas últimas se definen de forma predeterminada como tipo double (consumen 8 bytes). Para establecer el tipo float debe añadirse el sufijo 'F' (o 'f') a la constante de la derecha.
Los tipos float y double se diferencian por sus tamaños, rangos de valores y precisión de representación numérica. Todo ello se muestra en el cuadro siguiente.
Tipo |
Tamaño (bytes) |
Mínimo |
Máximo |
Precisión (órdenes de dígitos) |
---|---|---|---|---|
float |
4 |
±1.18 * 10-38 |
±3.4 * 1038 |
6-9, normalmente 7 |
double |
8 |
±2.23 * 10-308 |
±1.80 * 10308 |
15-18, normalmente 16 |
El rango de valores se muestra para ellos en términos absolutos: el mínimo y el máximo determinan la amplitud de los valores permitidos en las regiones positivas y negativas. De forma similar a los tipos enteros, existen constantes con nombre integradas para estos valores límite: FLT_MIN, FLT_MAX, DBL_MIN, DBL_MAX.
Tenga en cuenta que los números reales siempre tienen signo, es decir, no existen análogos sin signo para ellos.
Por precisión se entenderá la cantidad de dígitos significativos (dígitos decimales) que el número real del tipo correspondiente es capaz de almacenar sin distorsión.
De hecho, los números de los tipos reales no son tan precisos como los de los tipos enteros. Este es el precio que hay que pagar por su universalidad y una gama mucho más amplia de valores potenciales. Por ejemplo, si un entero de 4 bytes sin signo (uint) tiene el valor más alto de 4294967295, es decir, unos 4 millones, o 4.29*109, entonces el real de 4 bytes (float) tiene 3.4 * 1038, que es 29 órdenes de magnitud superior. Para los tipos de 8 bytes, la diferencia es aún más perceptible: ulong puede guardar 18446744073709551615 (18.44*1018, o ~18 quintillones), mientras que double puede guardar 1.80 * 10308, es decir, 289 órdenes de magnitud más. La inserción proporciona más detalles sobre la precisión.
Mantisa y exponente
La representación interna de los números reales en la memoria (en los bytes asignados a ellos) es bastante complicada. El bit de orden superior se utiliza como marcador del signo negativo (algo que también hemos visto en los tipos enteros). Los demás bits se dividen en dos grupos. El mayor contiene la mantisa del número, es decir, los dígitos significativos (nos referimos a dígitos binarios, es decir, bits). El menor guarda la potencia (exponente) a la que hay que elevar 10 para obtener el número almacenado al multiplicarlo por la mantisa. En concreto, para el tipo float, la mantisa tiene un tamaño de 24 bits (FLT_MANT_DIG), mientras que para double es de 53 (DBL_MANT_DIG). En términos de decimales convencionales (dígitos), obtendremos la misma precisión que se ha mostrado en la tabla anterior: 6 (FLT_DIG) es la menor cantidad de dígitos significativos para float, mientras que 15 (DBL_DIG) lo es para double. Sin embargo, dependiendo del número de que se trate, puede tener combinaciones «afortunadas» de bits, en correspondencia con una mayor cantidad de dígitos decimales. Los tamaños de los parámetros son de 8 y 11 bits para float y double, respectivamente.
Debido al exponente, los números reales tienen un rango de valores mucho mayor. Al mismo tiempo, con el aumento del exponente, también aumenta el «peso específico» del dígito de orden inferior de la mantisa. Esto significa que dos números reales vecinos que pueden representarse en la memoria del ordenador son sustancialmente diferentes. Por ejemplo, para el número 1.0, el «peso específico» del bit de orden inferior es 1.192092896e-07 (FLT_EPSILON) en el caso de float, y 2.2204460492503131e-016 (DBL_EPSILON) en el caso de double. En otras palabras: 1.0 es indistinguible de cualquier número cercano a él si dicho número es inferior a 1.192092896e-07. Esto puede parecer poco importante o que «no es para tanto», pero esta región de incertidumbre se agranda para los números más grandes. Si guarda en float un número de unos 1000 millones (1*109), los 2 últimos dígitos dejarán de almacenarse o restaurarse de forma segura desde la memoria (véase más abajo el ejemplo de código). No obstante, básicamente, el problema no es el valor absoluto de un número, sino la cantidad máxima de dígitos que contiene, que deben recuperarse sin pérdidas. Igualmente «bien» podemos intentar encajar un número representado como 1234.56789 (que estructuralmente se parece mucho al precio de un instrumento financiero) en float; y sus dos últimos dígitos «flotarán» debido a la falta de precisión de su representación interna.
En el caso de double se empezará a ver una situación similar para números mucho mayores (o para una cantidad mucho mayor de dígitos significativos), pero sigue siendo posible y ocurre a menudo en la práctica. Debe tener esto en cuenta cuando trabaje con números reales muy grandes o muy pequeños, y escribir sus programas con comprobaciones adicionales para evitar posibles pérdidas de precisión. En especial, debe comparar un número real con cero de una manera especial. Abordaremos esta cuestión en la sección sobre operadores de comparación.
A un lector atento le puede parecer que los tamaños de la mantisa y el exponente anteriores están mal especificados. Vamos a explicarlo con el ejemplo de float. Se almacena en la celda de memoria de 4 bytes de tamaño; es decir, consume 32 bits. Al mismo tiempo, los tamaños de la mantisa (24) y el exponente (8) suman ya 32. Entonces, ¿dónde está el bit con signo? La cuestión es que los profesionales informáticos han dispuesto almacenar la mantisa de forma «normalizada». Será más fácil entender en qué consiste eso si tenemos en cuenta en primer lugar la forma exponencial de registrar un número decimal normal. Digamos que el número 123.0 podría representarse como 1.23E2, 12.3E1, o 0.123E3. Se considera que una designación es la forma normalizada, en la que se coloca solo un dígito significativo (es decir, no cero) antes del punto. Para este número se trata de 1.23E2. Por definición, los dígitos del 1 al 9 se consideran dígitos significativos en notación decimal. Pasamos ahora sin dificultades a la notación binaria. Aquí sólo hay un dígito significativo, el 1. Resulta que la forma normalizada en notación binaria siempre empieza por 1, y se puede omitir (para no gastar memoria). De este modo, se puede ahorrar un bit en la mantisa. De hecho, contiene 23 bits (una unidad de orden superior más está implícita y se añade automáticamente al reconstruir el número y recuperarlo de la memoria). Reducir la mantisa en 1 bit deja espacio para el bit con signo.
Predominantemente, allí donde se debe utilizar el tipo de punto flotante, elegimos double como más preciso. El tipo float sólo se utiliza para ahorrar memoria, como cuando se trabaja con arrays de datos muy grandes.
Algunos ejemplos de utilización de las constantes de tipos reales se muestran en el script MQL5/Scripts/MQL5Book/p2/TypeFloat.mq5.
void OnStart()
|
Las variables a0, a1, a2 y a3 contienen los mismos números (123.0) escritos con métodos diferentes.
En la constante de la variable b, el cero insignificante se omite delante del punto. Además, aquí está la demostración de cómo registrar un número negativo utilizando el signo menos, '-'.
Se intenta almacenar el mayor número entero en la variable q. En este lugar, el compilador emite una advertencia, porque double no puede representar LONG_MAX con precisión: en lugar de 9223372036854775807, habrá 9223372036854776000. Ello obviamente demuestra que, aunque los rangos de los valores de double superan ampliamente a los de los enteros, se logra debido a la pérdida de los dígitos de orden inferior.
A modo de comparación, el número entero máximo que el tipo double es capaz de guardar sin distorsiones se da como valor de la variable d. En la secuencia de enteros irá seguido de saltos esporádicos, si utilizamos double para ellos.
La variable z nos recuerda de nuevo la limitación en la cantidad máxima de dígitos significativos (16): una constante más larga se truncará.
Las variables y1 y y2, en las que el mismo número se registra en formatos diferentes (double y float), permiten ver la pérdida de precisión debida a la transición a float.
De hecho, las variables m y n serán iguales, porque 999999975.0 se almacena de forma aproximada en la representación interna y se convierte en 1000000000.0.
Los tipos numéricos se suelen utilizar para calcular mediante fórmulas; para ellos se define un amplio conjunto de operaciones (véase Expresiones).
A veces, los cálculos pueden conducir a resultados incorrectos, es decir, que no pueden representarse como un número. Por ejemplo, no se puede definir la raíz de un número negativo o el logaritmo de cero. En estos casos, los tipos reales pueden guardar un valor especial denominado NaN (Not A Number, o No es un Número). De hecho, existen varios tipos de valores de este tipo que permiten, por ejemplo, diferenciar entre más infinito y menos infinito. MQL5 proporciona una función especial, MathIsValidNumber, que comprueba si el valor double es un número o uno de los valores NaN.