22. Números de punto flotante. Estándar C99. FLOAT.H
Para saber detalles sobre los
tipos de punto flotante en nuestra
implementación local de
C, debemos estudiar el archivo estándar de biblioteca
float.h.
El estándar
C99, en las
secciones 5.2.4.2.2, y
apéndices E y F de su documento oficial, establece las normativas relativas a los
tipos de punto flotante, así como el contenido de
<float.h> y su significado.
Tengamos en cuenta que, para los parámetros que se indican a continuación, el C99 no presupone un sistema ajustado al estándar ISO/IEC/IEEE 60559 (IEEE 754). El archivo
<float.h> contiene sólo macros que son constantes numéricas.
Algunas son
enteras y otras de
punto flotante.
La implementación local debe especificar valores constantes para las siguientes macros:
FLT_EVAL_METHOD
FLT_ROUNDS
Así que podemos estar seguros de que esas macros están definidas para nuestra versión del compilador.
La macro
FLT_EVAL_METHOD se refiere a los
métodos de evaluación de las operaciones aritméticas en relación a los
tipos de punto flotante que hay en el sistema.
No daremos muchos detalles de esto aquí ya que aún no hemos visto nada sobre las operaciones aritméticas.
Digamos tan sólo que al realizar operaciones aritméticas con
tipos en punto flotante, lo común es
perder exactitud, debido a los redondeos automáticos necesarios para que el resultado quepa en el
tipo de punto flotante elegido.
No obstante, el resultado final, con redondeo y todo, puede diferir según la
precisión utilizada en los cálculos intermedios.
Los
métodos de evaluación se refieren a esto, y los valores posibles para
FLT_EVAL_METHOD son:
-1 indeterminable (no se puede determinar el método de evaluación implementado).
0 evaluar todas las operaciones y constantes tan sólo según el rango de valores y la precisión del tipo (de los operandos).
1 evaluar operaciones y constantes de tipos float y double al rango de valores y precisión del tipo double, evaluar operaciones y constantes de tipo long double al rango de valores y precisión de long double
2 evaluar todas las operaciones y constantes al rango de valores y precisión del tipo long double.
Otros valores negativos deben ser especificados claramente por la
implementación local.
La macro
FLT_ROUNDS indica el
método de redondeo, como ya explicamos en posts anteriores, y que aquí repetimos:
-1 indeterminable: no se puede determinar cuál es el método de redondeo empleado.
0 hacia 0.
1 to nearest: hacia el más cercano.
2 hacia \( +\infty \).
3 hacia \( -\infty \).
Cuando se está bajo la normativa del estándar
IEEE 754 (por ejemplo, poniendo la macro
__STDC_IEC_559__ igual a
1), el método
to nearest es
hacia par (en caso de ambigüedad, el redondeo se resuelve hacia el valor más cercano en el
tipo de punto flotante usado, tal que la
mantisa es par, que en la representación binaria significa un 0 en el último dígito significativo).
Son posibles otros valores para
FLT_ROUNDS, pero su significado depende de la
implementación local, que debe documentar apropiadamente al respecto.
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC Los documentos de los estándares
IEEE 754 y
C99 no usan la palabra
mantisa, por considerarla imprecisa.
Utilizan en su lugar el término
significando.
Ni pienso explicar este detalle pedante...
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC La
implementación local debe indicar valores para las siguientes macros, algunas de las cuales tienen exigencias indicadas por el estándar
C99, y cuyo significado explicamos.
FLT_RADIX Debe ser un número entero \( \geq 2 \).
Indica la
base \( b \) en la cual se representarán los números de
punto flotante.
Lo típico es, por supuesto, \( b = 2 \).
Pero hay casos en que \( b \) puede ser, a veces, otra potencia de 2 (lo más típico sería \( b = 16 \)).
También puede que algún sistema en el futuro prefiera base decimal (b = 10).
Formalmente, cualquier entero \( \geq 2 \) está admitido.
FLT_MANT_DIGCantidad \( p \) de dígitos de precisión (\( p \)) en la mantisa de un número de tipo
float.
DBL_MANT_DIGCantidad \( p \) de dígitos de
precisión (\( p \)) en la
mantisa de un número de tipo
double.
LDBL_MANT_DIGCantidad \( p \) de dígitos de
precisión (\( p \)) en la
mantisa de un número de tipo
long double.
El estándar no establece restricciones para esos números.
Es muy posible que se obtengan como resultado de operaciones con otras
constantes que definen el
formato a usar en los tipos
float,
double,
long double, las que vienen a continuación.
DECIMAL_DIG Cantidad \( n \) de dígitos tal que: cualquier número \( x \) de
punto flotante en base \( b \) (o sea,
FLT_RADIX, que casi siempre es 2) en el tipo de
punto flotante más ancho que haya en la
implementación local (o sea, de
mantisa con la mayor cantidad \( p=p_{max} \) de dígitos de
precisión), redondeado a \( n \) dígitos en base decimal, y luego convertido otra vez a base \( b \), da como resultado el mismo valor \( x \) original.
El estándar establece que
DECIMAL_DIG debe ser \( \geq 10 \).
Para entender esto, debemos recordar que en el sistema pueden existir
tipos de punto flotante con mayor precisión y rango de valores que
long double.
Esto quedará especificado por la
implementación local.
Si esto no ocurre así, entonces este
tipo flotante máximo coincide con
long double, como es lógico.
Matemáticamente, esta cantidad \( n \) de dígitos (óptima) se calcula por:
\( \displaystyle n=
\begin{cases}
p_{max}\log_{10} b,&\text{si $b$ es potencia de 10}\\
\lceil 1+p_{max}\log_{10} b\rceil,\qquad&\text{en otro caso}.
\end{cases}
\)
Si hacemos el cálculo para el formato
Single Precision, con \( p = 24, b = 2 \), nos da \( n = 9 \).
Así que, el hecho de que el mínimo exigido por el estándar sea \( n = 10 \), obliga a que en la
implementación local exista al menos un
tipo de punto flotante que sea estrictamente mayor (en
precisión de la mantisa al menos) que el formato
Single Precision.
(Para ser exactos, aplicando la fórmula de arriba, debemos tener \( p_{max} \geq 27 \)
).
FLT_DIG Número \( q \) de dígitos tal que cualquier número \( x \) de
punto flotante con \( q \) dígitos en base decimal (10) se puede redondear a un número en base \( b \) (o sea,
FLT_RADIX, que casi siempre es 2) de tipo
float, y luego redondearlo otra vez a \( q \) dígitos en base decimal, de tal manera que el número obtenido quede igual que el \( x \) original.
El estándar establece que
FLT_DIG debe ser \( \geq 6 \).
DBL_DIG Número \( q \) de dígitos tal que cualquier número \( x \) de
punto flotante con \( q \) dígitos en base decimal (10) se puede redondear a un número en base \( b \) (o sea,
FLT_RADIX, que casi siempre es 2) de tipo
double, y luego redondearlo otra vez a \( q \) dígitos en base decimal, de tal manera que el número obtenido quede igual que el \( x \) original.
El estándar establece que
DBL_DIG debe ser \( \geq 10 \).
LDBL_DIG Número \( q \) de dígitos tal que cualquier número \( x \) de
punto flotante con \( q \) dígitos en base decimal (10) se puede redondear a un número en base \( b \) (o sea,
FLT_RADIX, que casi siempre es 2) de tipo
long double, y luego redondearlo otra vez a \( q \) dígitos en base decimal, de tal manera que el número obtenido quede igual que el \( x \) original.
El estándar establece que
LDBL_DIG debe ser \( \geq 10 \).
Matemáticamente, si tomamos un
tipo flotante con \( p \) dígitos de
precisión (en base \( b \) dada por
FLT_RADIX), la cantidad \( q \) de dígitos que permiten ir de base decimal a base \( b \), y luego volver, y recuperando el valor exacto original, está dado por:
\( \displaystyle q=
\begin{cases}
p\log_{10} b,&\text{si $b$ es potencia de 10}\\
\lfloor(p-1)\log_{10} b\rfloor,\qquad&\text{en otro caso}.
\end{cases}.
\)
Por ejemplo, para
Single Precision (\( p = 24 \)) y base \( b = 2 \), tenemos \( q = \lfloor(24-1)\log_{10} 2\rfloor =\lfloor 6.92\rfloor = 6 \).
En realidad, si se usa un valor de \( q \) más pequeño que 6, la conversión de base 10 a base \( b \) y luego de nuevo a base 10, sigue dando resultados exactos hasta \( q \) dígitos decimales significativos.
FLT_MIN_EXP Representa el mínimo exponente
entero negativo \( e \), tal que \( b^{e-1} \) es todavía un número
normalizado en el tipo
float, en donde \( b \) es la base para los dígitos de mantisa (especificada en
FLT_RADIX, típicamente \( b = 2 \)) .
DBL_MIN_EXP Representa el mínimo exponente
entero negativo \( e \), tal que \( b^{e-1} \) es todavía un número
normalizado en el tipo
double, en donde \( b \) es la base para los dígitos de mantisa (especificada en
FLT_RADIX, típicamente \( b = 2 \)).
LDBL_MIN_EXP Representa el mínimo exponente
entero negativo \( e \), tal que \( b^{e-1} \) es todavía un número
normalizado en el tipo
long double, en donde \( b \) es la base para los dígitos de mantisa (especificada en
FLT_RADIX, típicamente \( b = 2 \)) .
Pongamos el ejemplo de un
float en formato de
Single Precision. Tendríamos \( e = -125 \), con base \( b = 2 \), ya que \( 2^{-126} \) es el mínimo número normalizado en
Single Precision.
(Intencionalmente no he usado la notación \( e_{min} \) porque ambos estándares le dan una connotación diferente, por más que los valores
normalizados obtenidos son los mismos para ambos).
FLT_MAX_EXP Representa el máximo exponente
entero positivo \( e \), tal que \( b^{e-1} \) es todavía un número representable en el tipo
float, en donde \( b \) es la base para los dígitos de mantisa (especificada en
FLT_RADIX, típicamente \( b = 2 \)) .
DBL_MAX_EXP Representa el máximo exponente
entero positivo \( e \), tal que \( b^{e-1} \) es todavía un número representable en el tipo
double, en donde \( b \) es la base para los dígitos de mantisa (especificada en
FLT_RADIX, típicamente \( b = 2 \)) .
LDBL_MAX_EXP Representa el máximo exponente
entero positivo \( e \), tal que \( b^{e-1} \) es todavía un número representable en el tipo
long double, en donde \( b \) es la base para los dígitos de mantisa (especificada en
FLT_RADIX, típicamente \( b = 2 \)) .
Pongamos el ejemplo de un
float en formato de
Single Precision. Tendríamos \( e = +128 \), con base \( b = 2 \), ya que \( 2^{128-1} \) es la máxima potencia de 2 representable en
Single Precision (potencias mayores ya darían
overflow).
(Intencionalmente no he usado la notación \( e_{max} \) porque ambos estándares le dan una connotación diferente, por más que los valores
normalizados obtenidos son los mismos para ambos).
El estándar no especifica un valor máximo para estos últimos seis exponentes.
Sólo indica que los primeros tres deben ser
enteros negativos y los últimos tres
enteros positivos.
Sin embargo, pueden quedar restringidos o implicados por las seis constantes que siguen a continuación.
FLT_MIN_10_EXP Denota el mínimo
entero negativo \( d \) tal que \( 10^d \) aún está en el rango de los valores
normalizados del tipo
float.
El estándar estipula que esta constante debe ser \( \leq -37 \).
DBL_MIN_10_EXP Denota el mínimo
entero negativo \( d \) tal que \( 10^d \) aún está en el rango de los valores
normalizados del tipo
double.
El estándar estipula que esta constante debe ser \( \leq -37 \).
LDBL_MIN_10_EXP Denota el mínimo
entero negativo \( d \) tal que \( 10^d \) aún está en el rango de los valores
normalizados del tipo
long double.
El estándar estipula que esta constante debe ser \( \leq -37 \).
FLT_MAX_10_EXP Denota el máximo
entero negativo \( d \) tal que \( 10^d \) aún está en el rango de los valores representables del tipo
float.
El estándar estipula que esta constante debe ser \( \geq +37 \).
DBL_MAX_10_EXP Denota el máximo
entero negativo \( d \) tal que \( 10^d \) aún está en el rango de los valores representables del tipo
float.
El estándar estipula que esta constante debe ser \( \geq +37 \).
LDBL_MAX_10_EXP Denota el máximo
entero negativo \( d \) tal que \( 10^d \) aún está en el rango de los valores representables del tipo
float.
El estándar estipula que esta constante debe ser \( \geq +37 \).
FLT_MIN Representa el mínimo número
normalizado de punto flotante del tipo
float. El estándar exige que este valor sea \( \leq 10^{-37}. \)
DBL_MIN Representa el mínimo número
normalizado de punto flotante del tipo
double.
El estándar exige que este valor sea \( \leq 10^{-37}. \)
LDBL_MIN Representa el mínimo número
normalizado de punto flotante del tipo
long double.
El estándar exige que este valor sea \( \leq 10^{-37}. \)
Estas tres últimas constantes han de coincidir exactamente con el número \( b^{e-1} \), donde \( e \) es el exponente negativo mínimo que se define en
FLT_MIN_EXP, DBL_MIN_EXP ó LDBL_MIN_EXP, según corresponda.
FLT_MAX Representa el máximo número de
punto flotante que aún es representable en el tipo
float.
El estándar exige que este valor sea \( \geq 10^{+37}. \)
DBL_MAX Representa el máximo número de
punto flotante que aún es representable en el tipo
double.
El estándar exige que este valor sea \( \geq 10^{+37}. \)
LDBL_MAX Representa el máximo número de
punto flotante que aún es representable en el tipo
long double.
El estándar exige que este valor sea \( \geq 10^{+37}. \)
Estas tres últimas constantes han de coincidir exactamente con el número \( (1-b^{-p})b^{e} \), donde \( p \) es la
precisión indicada por
FLT_MANT_DIG, DBL_MANT_DIG, LDBL_MANT_DIG, \( e \) es el exponente positivo máximo que se define en
FLT_MAX_EXP,
DBL_MAX_EXP ó
LDBL_MAX_EXP, según corresponda en cada caso.
FLT_EPSILON Indica la diferencia exacta entre el número
1.0 y el mínimo número de tipo punto flotante mayor que
1, representable en el tipo
float.
El estándar exige que este valor sea \( \leq 10^{-5} \).
DBL_EPSILON Indica la diferencia exacta entre el número
1.0 y el mínimo número de tipo punto flotante mayor que
1, representable en el tipo
double.
El estándar exige que este valor sea \( \leq 10^{-9} \).
LDBL_EPSILON Indica la diferencia exacta entre el número
1.0 y el mínimo número de tipo punto flotante mayor que
1, representable en el tipo
long double.
El estándar exige que este valor sea \( \leq 10^{-9} \).
Para estas tres últimas constantes, el valor coincide exactamente con \( \epsilon = b^{1-p} \), donde \( p \) es la cantidad de dígitos en la mantisa, indicada según cada caso por
FLT_MANT_DIG,
DBL_MANT_DIG,
LDBL_MANT_DIG.
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC Veamos una tabla resumida de las macros de
float.h, con sus restricciones:
FLT_ROUNDS (un número entero)
FLT_EVAL_METHOD (un número entero)
FLT_RADIX \( \geq \) 2 (un número entero)
FLT_MANT_DIG (un número entero positivo)
DBL_MANT_DIG (un número entero positivo)
LDBL_MANT_DIG (un número entero positivo)
DECIMAL_DIG \( \geq \) 10 (entero)
FLT_DIG \( \geq \) 6 (entero)
DBL_DIG \( \geq \) 10 (entero)
LDBL_DIG \( \geq \) 10 (entero)
FLT_MIN_EXP (un número entero negativo)
DBL_MIN_EXP (un número entero negativo)
LDBL_MIN_EXP (un número entero negativo)
FLT_MAX_EXP (un número entero positivo)
DBL_MAX_EXP (un número entero positivo)
LDBL_MAX_EXP (un número entero positivo)
FLT_MIN_10_EXP \( \leq \) -37 (entero)
DBL_MIN_10_EXP \( \leq \) -37 (entero)
LDBL_MIN_10_EXP \( \leq \) -37 (entero)
FLT_MAX_10_EXP \( \geq \) +37 (entero)
DBL_MAX_10_EXP \( \geq \) +37 (entero)
LDBL_MAX_10_EXP \( \geq \) +37 (entero)
FLT_MIN \( \leq 10^{-37} \) (número positivo de punto flotante)
DBL_MIN \( \leq 10^{-37} \) (número positivo de punto flotante)
DBL_MIN \( \leq 10^{-37} \) (número positivo de punto flotante)
FLT_MAX \( \geq 10^{+37} \) (número de punto flotante)
DBL_MAX \( \geq 10^{+37} \) (número de punto flotante)
LDBL_MAX \( \geq 10^{+37} \) (número de punto flotante)
FLT_EPSILON \( \leq 10^{-5} \) (número positivo de punto flotante)
DBL_EPSILON \( \leq 10^{-9} \) (número positivo de punto flotante)
LDBL_EPSILON \( \leq 10^{-9} \) (número positivo de punto flotante)
Esas constantes deben además cumplir algunas relaciones aritméticas entre sí:
Denotemos:
\( b = \) FLT_RADIX,
\( n = \) DECIMAL_DIG,
\( p_1 = \) FLT_MANT, \( p_2 = \) DBL_MANT, \( p_3 = \) LDBL_MANT,
\( q_1 = \) FLT_DIG, \( q_2 = \) DBL_DIG, \( q_3 = \) LDBL_DIG,
\( e_1 \) = FLT_MIN_EXP, \( e_2 = \) DBL_MIN_EXP, \( e_3 = \) LDBL_MIN_EXP,
\( e_1' = \) FLT_MAX_EXP, \( e_2' = \) DBL_MAX_EXP, \( e_3' = \) LDBL_MAX_EXP,
\( d_1 = \) FLT_MIN_10_EXP, \( d_2 = \) DBL_MIN_10_EXP, \( d_3 = \) LDBL_MIN_10_EXP,
\( d_1' = \) FLT_MAX_10_EXP, \( d_2' = \) DBL_MAX_10_EXP, \( d_3' = \) LDBL_MAX_10_EXP,
\( m_1 \) = FLT_MIN, \( m_2 = \) DBL_MIN, \( m_3 = \) LDBL_MIN,
\( M_1 = \) FLT_MAX, \( M_2 = \) DBL_MAX, \( M_3 = \) LDBL_MAX,
\( \epsilon_1 = \) FLT_EPSILON, \( \epsilon_2 = \) DBL_EPSILON, \( \epsilon_3 = \) LDBL_EPSILON.
Para \( i=1,2,3 \), podemos escribir:
\( \displaystyle
\begin{align*}
b&\geq 2\\
m_i &= b^{e_i-1}\\
b^{e_i'}\leq M_i &= b^{e_i'}(1-b^{-p_i})\\
\epsilon_i &= b^{1-p_i}\\
m_i &\leq 10^{d_i}\\
10^{d_i'}&\leq M_i\\
0< q_i & \leq\begin{cases}
p_i\log_{10} b,&\text{si $b$ es potencia de 10}\\
\lfloor(p_i-1)\log_{10} b\rfloor,\qquad&\text{en otro caso};
\end{cases}\\
\end{align*}
\)
Además, el estándar establece que el rango de valores de
float es subconjunto del de
double, y a su vez éste es subconjunto del de
long double.
En particular, la precisión de
long double tiene que ser al menos tan buena como la de
double, y éste a su vez tan buena como la de
float.
Por lo tanto: \( m_3\leq m_2\leq m_1 < M_1\leq M_2\leq M_3 \), y \( p_1\leq p_2\leq p_3 \).
En particular, esto implica \( e_3\leq e_2\leq e_1 <0 \), \( e_1'\leq e_2'\leq e_3' \), \( d_3\leq d_2\leq d_1 <0 \) y \( d_1'\leq d_2'\leq d_3' \).
Si bien las constantes \( p_i, e_i, e_i' \), no se especifican directamente, se pueden calcular.
Veamos cómo hacerlo.
\( e_i \) se puede obtener a partir de \( d_i \), definiéndolo como el máximo entero \( e \) tal que \( b^{e-1}\leq 10^{d_i} \).
Luego obtenemos fácilmente la
precisión:
\( p_i = 1-\log_b e_i. \)
\( e_i' \) se puede obtener a partir de \( d_i' \), definiéndolo como el máximo entero \( e \) tal que \( b^{e}(1-b^{-p_i})\leq M_i \).
Si observamos minuciosamente, vemos que también es posible definir \( e_i \) como el mínimo entero \( e \) tal que \( 10^{d_i}\leq b^{e}(1-b^{-p}) \).
Esas dos caracterizaciones debieran coincidir...
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC Notemos que algunas constantes en
float.h se exigen como
enteras, y otras como
punto flotante.
El estándar no especifica de qué tipos específicos tienen que ser esas constantes.
Sin embargo, se exige que los valores de
punto flotante especificados sean
finitos, en el tipo que estén especificadas.
De las definiciones de los números \( q_i \) parece apropiado exigir que \( 0< q_1\leq q_2\leq q_3 \), pero no parece que esto sea exigido directa o indirectamente por el estándar.
Si los números \( q_i \) tomaran sus valores máximos, acorde a la
precisión \( p_i \) correspondiente, entonces sí ocurriría obligadamente que \( q_1\leq q_2\leq q_3 \).
De otro modo, esto no estaría asegurado.
Sin embargo, si de verdad fuera así, lo considero una inconsistencia, y cabe esperar que esa desigualdad de los números \( q_i \) valga en toda implementación basada en el estándar.
(En realidad, en el estándar aparece la fórmula que hemos mostrado arriba para calcular \( q \) respecto de \( p \), como una igualdad, y esto podría interpretarse como un valor obligado de \( q \), sin opción a que la implementación elija un número menor).
Usando fórmulas exactas, tendríamos siempre que \( n> q_3 \).
Otros detalles del estándar se muestran en el último apartado de nuestro post:
Números de punto flotante. Estándar C99. Constantestitulado:
Normativa ISO/IEC/IEEE 60559 (ó IEEE 754).
Se observa que los
37s que andan dando vueltas por ahí, así como otras constantes mínimas exigidas, obligan a que el tipo
float sea al menos tan grande como un formato
Single Precision, tanto en
precisión como en exponentes.
Obsérvese además que un valor de \( \epsilon \leq 10^{-9} <10^{-5} \) para
double obliga indirectamente a la
implementación local a proveer al menos un
tipo de punto flotante que tenga estrictamente más
precisión que un formato
Single Precision (aunque no necesariamente exponentes más amplios).
Finalmente, el estándar
recomienda aunque
no obliga, a que un número de tipo
double, en base \( b \), convertido a decimal considerando allí sólo \( n = \)
DECIMAL_DIG dígitos decimales de
exactitud, y convirtiendo esto de nuevo a base \( b \), se obtenga exactamente el número original.
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC Hasta aquí las especificaciones del
C99 y del
IEEE 754.
La gran pregunta es:
¿nuestro compilador se ajusta a estos estándares de punto flotante? Analizaremos esto en el siguiente post.
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCOrganizaciónComentarios y Consultas