Autor Tema: Proyecto de Curso (Dictado - Notas): Programación en C (2013, por Argentinator)

0 Usuarios y 1 Visitante están viendo este tema.

18 Enero, 2013, 05:34 am
Respuesta #20

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,739
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
20. Números de punto flotante. Redondeos y cambio de base

   Aquí vamos a analizar lo que ocurre cuando especificamos constantes que tienen más dígitos (precisión) de los que puede soportar un tipo de punto flotante dado, o bien cómo este problema se puede colar inadvertidamente en las conversiones entre base decimal y binaria (o viceversa).
   Asimismo, veremos qué ocurre cuando los valores se salen del rango previsto para un tipo de punto flotante.
   También vamos a analizar lo que los estándares mencionan al respecto.

   Supongamos que tenemos la macro __STDC_IEC_559__ igual a 1, con lo cual el tipo float de C coincide en precisión y rango con el formato Single Precision de IEEE 754.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

1. Redondeo de constantes decimales.

   ¿Qué ocurre si queremos especificar la siguiente constante?

1.0000000001F

   Matemáticamente equivale a \( 1 + 10^{-10} \). Hemos puesto el sufijo F para forzar al tipo float.

(Normalmente forzaremos a float en los ejemplos, porque al tener el formato Single Precision menos dígitos de precisión, se hace más didáctico mostrar ejemplos realistas).

   La precisión en este caso es de \( p = 24 \) dígitos binarios. Al convertir la cantidad \( 10^{-10} \) a binario, debemos hallar entre cuáles potencias de 2 se halla dicha cantidad. Esto se logra con logaritmos en base 2:

\( \log_2 (10^{-10}) \approx{} -29{.}89 \)

   Esto nos indica que \( 2^{-30}<10^{-10}<2^{-29}. \) Como la precisión del formato Single Precision es de \( p = 24 \) dígitos de base \( b = 2 \), esto quiere decir que el número más pequeño que puede representarse (en ese formato) que sea estrictamente mayor que \( 1{.}0 \) es \( \gamma = 1+ 2^{-23} \).
   En binario, se escribe así: \( \gamma = (1{.}0000'0000'0000'0000'0000'001)_2 \).

   El último dígito binario 1 corresponde a la cantidad \( 2^{-23} \).
   Sin embargo, nuestro número ejemplo \( x = 1+10^{-10} \) satisface que \( 1 < x <\gamma \).
   Por lo tanto: ¡no puede representarse en el formato Single Precision:o

   En ese caso, hay que tomar una decisión. Si queremos representar nuestro número \( x \), tenemos que sacrificar algo.
   Eso que sacrificaremos será la: exactitud:'( :'( :'(
   Se debe reemplazar el número \( x \) por alguno de los dos que tiene "más cerca", ya sea el \( 1 \) o el \( \gamma=1+2^{-23} \).
   Es decir: se sustituye el número conviertiéndolo a otro valor aproximado, el cual es posible representar en el formato requerido, en este caso, Single Precision.

   ¿Cuál de los dos valores cercanos a \( x \) hay que elegir?
   Hay en principio 3 posibles acciones "clásicas" a realizar aquí:

\( \bullet \)   Truncamiento hacia debajo: Directamente se "cercena" la parte que "sobra" respecto del valor inferior (en este caso el 1): se quita la diferencia \( \delta=|x-1| \), y se convierte así el valor \( x \) en 1.

\( \bullet \)   Truncamiento hacia arriba: De nuevo, se desestima la parte que "sobra" de \( x \), pero esta vez la conversión se hace hacia arriba, reemplazando el valor de \( x \) por \( \gamma = (1 . 0000'0000'0000'0000'0000'001)_2 \).

\( \bullet \)   Redondeo: Aquí de nuevo se desestima la parte "sobrante" de \( x \), pero se decide truncar hacia abajo o hacia arriba sólo en función de cuál es el valor más cercano. En este caso, como la diferencia \( |x-1|< 2^{-30} \) es menor que \( |\gamma-x|>2^{-2} \), el truncamiento se hace hacia abajo, reemplazando el valor de \( x \) por \( 1 \).

   El estándar IEEE 754 estipula que la operación a realizar en estos casos es la de redondeo.
   Esta decisión es obvia, dado que el redondeo asegura siempre que ocurre el menor error absoluto posible en los cálculos aritméticos.
   Sin embargo, ocurriría una ambigüedad si el valor \( x \) estuviera justo a la misma distancia del valor inferior \( 1 \) y el valor superior \( \gamma=1+2^{-23} \).
   Veamos por ejemplo este número decimal:

\( x = 1{.}000000'059604'644775'390625 \)

   Al escribirlo en binario, es simplemente \( 1+2^{-25} \), así:

\( x = (1.0000'0000'0000'0000'0000'0000'1)_2 \)

   En este caso, las diferencias \( |x-1| \) y \( |\gamma-x| \) son ambas exactamente iguales a \( 2^{-24} \).
   Así que, ¿redondeamos hacia abajo, reemplazando por 1, o hacia arriba, reemplazando por \( \gamma=1+2^{-23} \)?

   Esta decisión no se puede hacer a la ligera, y depende de las circunstancias.
   A veces quien decide es el procesador de la máquina.
   A veces puede decidirlo el sistema operativo.
   A veces lo decidirá el compilador.
   A veces lo decidirá el programador.
   Y otras veces lo decidirá el usuario mismo del programa.

   Antes de tomar cualquier decisión, o de ver qué hace cada quién, debemos conocer los tipos de redondeo.
   Todo lo que sigue se refiere a la situación en la cual hay que decidir hacia dónde redondear, en el caso de que la distancia del número \( x \) hacia los posibles valores inferior y superior sea la misma:

\( \bullet \)   Redondeo hacia \( \color{blue}-\infty \): Quiere decir que siempre se elige ir "hacia la izquierda", o sea, "hacia el menor de los dos posibles valores". En este caso, nuestro ejemplo \( x \) se convertirá en un 1. No obstante, para números negativos, tendríamos que \( -x \) se convertiría en \( -\gamma=-1-2^{-23} \), porque este valor es menor que \( -1 \).
\( \bullet \)   Redondeo hacia \( \color{blue}+\infty \): Aquí la dirección de redondeo es "hacia la derecha", o sea, "hacia el mayor de los dos posibles valores". Así, nuestro \( x \) redondearía hacia \( \gamma=1+2^{-23} \), mientras que \( -x \) redondearía hacia \( -1 \).
\( \bullet \)   Redondeo hacia \( \color{blue}0 \): Aquí se redondea hacia el valor que tiene menor valor absoluto. Así, nuestro \( x \) redondea hacia 1, y \( -x \) hacia \( -1 \).
\( \bullet \)   Redondeo alejándose del \( \color{blue}0 \): Aquí se redondea hacia el valor que tiene el mayor valor absoluto. Por ejemplo, nuestro \( x \) redondearía hacia \( \gamma=1+2^{-23} \), y \( -x \) hacia \( -1-2^{-23} \).
\( \bullet \)   Redondeo hacia el valor "par": Cuando tenemos que redondear una cantidad que está entre dos valores representables consecutivos \( \alpha \) y \( \alpha'=\alpha +2^{e-p+1} \) (aquí \( e \) es la parte del exponente de \( \alpha \) escrito en forma normalizada), es siempre cierto que uno solo de esos dos valores tiene su último dígito binario (en la mantisa) igual a 0, y el otro de los valores tendrá su último dígito binario de mantisa igual a 1. En este caso, el redondeo se elige hacia el valor cuyo último dígito es 0. En nuestro ejemplo, tenemos que:

\( \alpha=(1.0000'0000'0000'0000'0000'000')_2 \;\cdot \;2^0 \) (aquí \( e = 0 \))

\( \alpha'=1+2^{-23}=(1.0000'0000'0000'0000'0000'001')_2 \;\cdot \;2^0 \)

mientras que nuestro valor \( x \) está entre ellos dos: \( \alpha<x<\alpha' \).
   El redondeo se hará hacia \( \alpha=1 \), porque el último dígito binario de la mantisa de \( \alpha \) es un 0.
   Igualmente, \( -x \) se redondeará hacia \( -1 \), por la misma razón.

\( \bullet \)   Redondeo hacia "impar": Aquí la situación es parecida al caso anterior, salvo que el redondeo se hace hacia aquel valor que en su último dígito binario tiene un \( 1 \). En nuestro ejemplo, \( x \) redondearía hacia \( \alpha' \), y del mismo modo \( -x \) va hacia \( -\alpha' \).

\( \bullet \)   Redondeo estocástico: Aquí la dirección del redondeo (hacia arriba o hacia abajo) se decide al azar, con algún generador automático de números aleatorios.

   Los redondeos hacia "par" o hacia "impar" distribuyen uniformemente los redondeos, tanto en los semiejes positivo y negativo, como a lo largo de toda la secuencia de enteros.
   El redondeo estocástico asegura una distribución estadística uniforme de los errores por redondeo.
   Es por eso que estos últimos 3 tipos de redondeo son preferibles a los anteriores, que producen sesgos en uno u otro sentido.
   La desventaja de los redondeos estocásticos es que, por un lado, es impredecible el valor concreto que dará en un caso determinado tras redondear, y además calcular números aleatorios supone un esfuerzo de cómputo adicional, que no se justifica en el caso general.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

2. Métodos de redondeo por defecto en C.

Y el ganador es...  :-*

   El estándar IEEE 754 establece que su método de redondeo por defecto es "hacia par", o sea, hacia aquel valor cuyo último dígito binario en la mantisa es un 0.
   Esto se debe a la buena distribución estadística del error que así se consigue.
   El estándar C99 no necesariamente tiene un modo de redondeo preferido, pero provee la macro FLT_ROUNDS, de la librería <float.h>, que da información al programador sobre el método de redondeo presente en su sistema. Los valores posibles de FLT_ROUNDS son estos:

-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 (esto quiere decir, hacia "par" o hacia "impar", como lo definimos arriba).
 2    hacia \( +\infty \).
 3    hacia \( -\infty \).


   Puede haber otros valores negativos, pero en ese caso su significado depende exclusivamente de la implementación local.
   En cuanto a otros posibles valores positivos, el estándar no dice nada, ni tampoco sugiere si están reservados para uso en futuras versiones de C:-X

   En cuanto al método to nearest, el estándar C99 no dice explícitamente si es "hacia par" o "hacia impar".
   Mientras que el estándar IEEE 754, que de entrada elige el método hacia par.

   Pero vayamos un poco más profundo.

\( \bullet \)   El estándar C99 especifica que el compilador debe realizar, si no se indica lo contrario, redondeo to nearest, respetando la especificación de IEEE 754.
\( \bullet \)   También, al inicio de un programa ejecutable, o sea, ya compilado, también está por defecto el modo de redondeo to nearest, respetando la especificación de IEEE 754.
\( \bullet \)   En ambos casos se ajusta al estándar IEEE 754, y entonces to nearest da toda la sensación de que significa hacia par, y nunca hacia impar.
\( \bullet \)   En el apéndice F del documento del estándar C99 dice que, cuando el estándar C se ajusta al estándar IEEE 754, debe asumirse por defecto este último, a menos que se especifique lo contrario.

   Es claro, pues, el espíritu general de querer ajustarse a IEEE 754 en caso de duda. Y por lo tanto podemos asumir que to nearest en C99 significa hacia par.
   Aún así, pueden quedar dudas sobre este punto. Digamos tan sólo que lo más probable es que todo diseñador de compiladores interprete el estándar C99 como coincidente con el IEEE 754, y que considere to nearest como hacia par.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

3. Redondeo para constantes hexadecimales.

   Aquí la situación es más sencilla, porque la traducción de hexadecimal a binario es directa.
   Cada dígito hexadecimal se puede traducir directamente a un paquete de 4 dígitos binarios (bits), y entonces las reglas de redondeo se ven directamente..
   Veamos lo que ocurre, por ejemplo, con los números entre \( \alpha=1 \) y \( \alpha'=1+2^{-23} \). Estos números, en float hexadecimal se escriben:

\( \alpha: \)  0x1.0p0F
\( \alpha': \) 0x1.000002p0F

   La diferencia entre ambos números, la cantidad \( 2^{-23} \), se escribe:

\( \alpha'-\alpha: \) 0x1.0p-23F

   Ahora las constantes están más claras, si es que les perdemos el temor a los hexadecimales, y a la base 2 que nos indica la letra p:-*

   Si escribimos una constante como 0x1.0000008p0F, al redondearla se convertirá en 1 (o sea, en \(  \alpha \)).
   Una constante como 0x1.0000018p0F, se redondeará hacia \( \alpha' \).

   Finalmente, la constante 0x1.000001p0F está a medio camino entre \( \alpha \) y \( \alpha' \), así que su dirección de redondeo depende del modo de redondeo presente en el sistema.
   Asumiendo el redondeo hacia par, la constante redondeará hacia \( \alpha \).

   Aquí, es más o menos sencillo anticipar la dirección de redondeo (si hacia \(  \alpha \) o \( \alpha' \)), porque basta traducir el último dígito hexadecimal a binario de \( \alpha' \) por ejemplo, en este caso: \( (2)_{16} = (0010)_2 \), y verificar cuál es el último dígito binario significativo, a ver si es 0 ó 1, para determinar la paridad.
   En este caso sólo los primeros tres dígitos 001 son significativos para \( \alpha' \) (¿por qué?), y así la paridad de \( \alpha' \) es 1.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

4. Conversión de constantes binarias a decimales y viceversa.

   Cuando tenemos números escritos en base 2 ó 16, con una cantidad finita de cifras en la parte fraccionaria, al convertirlos a base 10, su parte fraccionaria no se vuelve periódica.
   Es decir, tenemos una forma finita, precisa, de escribirlos en base decimal.

   Sin embargo, al revés no tenemos siempre esa suerte. Puede haber números escritos en base 10 que, al convertirlos a base 2 ó 16, su parte fraccionaria adopte un desarrollo periódico. Esto es malo porque nos obliga a truncar o redondear un número que creíamos haber indicado con absoluta precisión.

   Analicemos el primer caso, que es más benévolo.
   Una constante binaria, como \( (0{.}1001'0101'1)_2 \) se traduce a decimal sin problemas, dando el valor \( 0{.}583984'375 \). En hexadecimal sería: \( (0{.}958)_{16} \).

   El inconveniente aquí es que probablemente nuestro sistema sólo acepte una cantidad máxima de dígitos en una mantisa escrita en base decimal.

4-El estándar IEEE 754 establece:

\( \bullet \)   Para el formato Single Precision, la cantidad total de dígitos significativos en la mantisa son los primeros 9, y el resto puede descartarse, o bien rellenarse con los dígitos que al implementador se le ocurran.
\( \bullet \)   Para el formato Double Precision, la cantidad total de dígitos significativos en la mantisa son los primeros 17.

   ¿Con qué diabólicos  >:D >:D >:D dígitos se rellenarán aquellos dígitos que van detrás del 9no significativo en Single, o del 17mo en Double?
   Lo típico y recomendable es rellenar con 0's.
   ¿Pero podemos estar seguros?

   En un pasaje del estándar IEEE 754 (sección 4), establece que al convertir de binario a decimal, debe localizarse el dígito decimal menos significativo, con propósitos de redondeo.
   Lo que entiendo de ahí es que, de los 9 dígitos más significativos, se aplica quizás un redondeo según lo que hay detrás del 9no dígito, y luego del 10mo dígito en adelante uno puede colocar ahí los dígitos que se le antojen. (Aquí "uno puede" quiere decir "el compilador puede").
   Así, al convertir el número \( \gamma=1+2^{-23} \) (en float) de binario a decimal, el número obtenido matemáticamente exacto es:

\( 1{.}000000'1192092'895507'8125 \)

   Pero cuando consideramos sólo los primeros 9 dígitos significativos como válidos, previo redondeo, nos queda tan sólo:

1.000000'12

   Cuando convertimos ese número de nuevo a binario, pasa a ser aproximadamente:

1.0000'0000'0000'0000'0000'0010'0000'001... (con algún error a partir del último dígito).

   Ahora, hagamos el procedimiento de redondear en binario. Obtenemos:

1.0000'0000'0000'0000'0000'001'

   Hemos recuperado mágicamente el número binario del que partimos.
   En realidad esto siempre ocurrirá, por más que hayamos hecho redondeos en el camino...
   Para estar seguros de esto, tendremos que hacer el análisis matemático del error, trabajo que quedará para futuros posts.

   Lo que el estándar IEEE 754 establece, como normativa en las conversiones entre binario y decimal, es esto:

\( \bullet \)   Las conversiones de una base a otra tienen que ser funciones no decrecientes.
\( \bullet \)   Si un número binario, escrito en forma normalizada, y con la máxima precisión de su formato, se convierte a decimal, y luego se convierte otra vez a binario, y se redondea (en cualquiera de los modos de redondeo), el resultado tiene que ser la función identidad (o sea, se recupera el número binario original).

   El hecho de que se exijan 9 dígitos decimales no es casual: 9 dígitos son suficientes para asegurar una conversión de ida y vuelta que cumpla los anteriores requerimientos.
   ¿Se puede tener la misma suerte convirtiendo de decimal a binario, y luego volver a decimal?
   Aquí ya no tendremos esa suerte. Basta verlo con un ejemplo simple, como el número \( x = 1{.}1 \) (en decimal).

   En binario tiene una representación periódica:

1.0001'1001'1001'1001'1001'1001'1001...

   Si truncamos o redondeamos en el dígito binario de la posición 24 (el dígito binario 23vo detrás del punto fraccionario en este caso), obtenemos en cada caso:

1.099999'904632'568359'3... (truncamiento en binario y luego volviendo a decimal)
1.100000'023841'857910'1... (redondeo en binario y luego volviendo a decimal)

   Si nos quedamos con los primeros 9 dígitos significativos:

1.099999'90
1.100000'02

   No hemos recuperdo el número 1.1.
   Ni siquiera se puede arreglar la situación con un redondeo previo.
   Da la sensación de que sólo 6 ó 7 dígitos decimales son enteramente confiables.
   De hecho, para el formato Single Precision sólo podemos asegurar 6 dígitos decimales "confiables" en todos los casos (es decir, 6 dígitos significativos en total, no sólo la parte fraccionaria).

   Con 6 dígitos significativos, el redondeo da en ambos casos el mismo valor: 1.1, que es el que originalmente teníamos.
   Si bien los estándares especifican condiciones para el caso: pasar de binario a decimal y volver a binario,  en cambio no dicen nada sobre el caso contrario: pasar de decimal a binario y volver a decimal.
   Posiblemente esto se deba a que el segundo de estos dos casos se pueda inferir de lo establecido en el primero. Analizaremos esto en otro post...

Conclusión: A fin de estar seguros de los cálculos en base decimal o en base binaria, necesitamos saber cuántos dígitos son necesarios en una y otra base, a fin de que, al convertir de una base a la otra, y luego intentar volver (desconvertir), se obtenga el mismo valor original (previos y adecuados redondeos).

Precaución: supongamos que estamos en la situación de nuestro último ejemplo, en el que tenemos dos números decimales 1.09999990, 1.10000002, que redondeados a 6 dígitos significativos nos devuelven el número eriginal 1.1.
   ¿Quiere decir esto que al comparar ambos números, obtendremos que son iguales? La respuesta es que NO  :o porque las comparaciones se hacen entre las versiones binarias de los números en cuestión, y esos valores binarios eran diferentes, incluso en Single Precision.

   En cuanto a todas estas cuestiones, ¿qué normas adopta el estándar C99?
   Las normas que adopta son las mismas que el IEEE 754, salvo que no deja especificado el método de redondeo (más bien indica con la macro FLT_ROUNDS cuál es el modo de redondeo vigente).
   Así que no hace falta repetir las normas que ya hemos expuesto, pues para el C99 son las mismas.
   No obstante, en C se tienen unas macros con información acerca de los dígitos significativos en binario y decimal, también sobre conversiones entre esas bases, y en todos los casos para sus tres tipos de punto flotante: float, double y long double.
   Detalles, más adelante.  :-*

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Creo que con todo esto hemos tenido un buen panorama de lo que significan los problemas de conversión entre decimal y binario, y los temas de redondeo asociados.

   Hemos mostrado ejemplos ilustrativos, para ver realmente lo que ocurre.
   Pero de a poco iremos buscando la generalización teórica.
   De paso estos ejemplos nos ayudarán a entender convenciones que luego toma el lenguaje C.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

18 Enero, 2013, 10:56 pm
Respuesta #21

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,739
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
21. Números de punto flotante. Valores fuera de rango

   En el post anterior lidiamos con el problema de la pérdida de exactitud tras los últimos dígitos significativos de una constante de punto flotante.
   Ahora vamos a ver qué pasa cuando intentamos escribir una constante fuera del rango de valores de un formato de punto flotante dado.
   Sigamos ejemplificando con el formato Single Precision de IEEE 754, cuya precisión (en binario) es \( p = 24 \), su exponente máximo (en binario) es \( e_{max}=+127 \), y su exponente mínimo (en binario) es \( e_{min}=-126 \).
   Esto quiere decir que el máximo número binario positivo que puede representarse en Single es:

\( \mu=(1{.}1111'1111'1111'1111'1111'111)_2 \cdot 2^{+127} \)

   En lenguaje C, asumiendo como hacemos siempre que float coincide con Single, esa constante se indicaría en hexadecimal así:

1.FFFFFEp127f  ;) ;)

   En decimal, corresponde al número (exacto):

\( 2^{128}-2^{104}=3{.}402823'466385'288598'117041'834845'169254'40  \cdot 10^{38} \)

(que es del orden de los 340 sextillones).

   El dígito binario menos significativo corresponde a la potencia \( 2^{104} \).
   Eso quiere decir que agregando cantidades menores que \( 2^{104} \) obligará a un redondeo.
   Si agregamos un número menor que la mitad de ese número, digamos \( 4{.}9\cdot 2^{103} \), obligará a un redondeo hacia abajo, o sea que \( \mu+4{.}9\cdot 2^{103} \) se redondea a \( \mu \).

   Si buscamos un ejemplo en binario, despleguemos el número \( \mu \) en base 2, para ver claramente, sin la notación exponencial:

1111'1111'1111'1111'1111'1111'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000'0000

   Se ha marcado en rojo el dígito menos significativo que puede representarse en Single Precision.

(Para quienes quieran verlo en forma más compacta, por ejemplo en base 16, aquí va: \( (FFFFFF00000000000000000000000000)_{16} \)).

   ¿Qué ocurre si cambiamos los dígitos a la derecha del dígito binario marcado en rojo?
   Si el dígito en la posición inmediata siguiente, es decir, la posición 25, se deja con 0, y el resto de los dígitos de más a la derecha se cambian arbitrariamente (vamos a suponer que no pondremos ahí infinitos 1's...), entonces es equivalente a haber agregado una cantidad menor que la mitad de \( 2^{104} \).
   Entonces, se produce redondeo, y se hace hacia abajo, obteniendo de vuelta el valor \( \mu \).

   ¿Pero y si agregamos un valor mayor que la mitad de \( 2^{104} \)? ¿Y un valor igual a  la mitad de \( 2^{104} \)?
   Debido al redondeo de tipo to nearest (que va "hacia par"), lo primero que se haría es redondear hacia arriba, obteniendo el valor \( \omega = 2^{128} \), directamente.
   ¡Pero este valor está fuera del rango posible del formato Single Precision:o :o :o
   Cuando esto ocurre, se dice que se ha producido un desbordamiento por arriba, o en inglés: Overflow.

   Cualquier intento de especificar una constante \( x\geq 2^{128}-2^{103} \) producirá overflow en el formato Single Precision.
   Por ejemplo, volviendo a los números más familiares en formato decimal, una constante como 3.41e38f estaría también fuera del rango de float (tomado como igual a Single Precision), y estamos otra vez en overflow.

¿Qué ocurre cuando hay overflow? ??? ???

   Lo que pasa es que se descarta totalmente el valor de la constante que queríamos especificar, y en cambio se almacena en memoria el valor +infinito, tal como se indica en las normas del IEEE 754.
   Recordemos que esto se codifica de la siguiente manera:

  Bit de signo:   0
  Mantisa:        0000'0000'0000'0000'0000'0000'0000
  Exponente:      \( e_{max}+1 \) (en este caso \( +128 \))


(recordemos que el exponente se expresa en formato "sesgado" o "descentrado", con lo cual +128 corresponde al máximo binario en 8 bits: \( 255 = (1111'1111)_2 \), que quiere decir \( 255-127=128 \)).

   Así que se pierde toda exactitud en los valores fuera de rango, ya que se consideran todos "iguales".
   Son los valores infinitos.

   El valor -infinito se produce al intentar especificar un valor negativo fuera de rango.
4-O sea, valores \( x \leq  -(2^{128}-2^{103}) \), producirán también un overflow, pero esta vez corresponde al valor -infinito, que sólo difiere de +infinito en el bit de signo, que ahora es 1.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Ahora veamos qué pasa cuándo se especifican constantes demasiado pequeñas, cayendo fuera del rango del formato (para nosotros, el Single Precision).

   El número \( 2^{-127} \), por ejemplo, no puede representarse en forma normalizada en Single Precision.
   Es uno de los números subnormales.

   Todos los números subnormales se codifican con el exponente \( e_{min}-1=-127 \).
   La mantisa ya no se considera normalizada, y así la precisión del número es igual a la cantidad de dígitos que hay desde el primer 1 (inclusive) de la mantisa hacia la derecha.
   El número \( 2^{-127} \) se representa por:

\( ({.}1000'0000'0000'0000'0000'0000)_2 \cdot 2^{-127} \)  ::) (estoy haciendo trampillas con la notación, pero mejor ni se los explico  >:D ).

   Casos más tristes son, por ejemplo, el número \( 2^{-151} \), que se representa así:

\( ({.}0000'0000'0000'0000'0000'0001)_2 \cdot 2^{-127} \)

   Este número, al no estar normalizado, tiene sólo 1 bit de precisión.
   Para tener toda la precisión real del Single Precision necesitaríamos 23 dígitos significativos más a la derecha del 1. Pero esto no es posible, porque ahora lo que tenemos acotado es el rango de exponentes. ¡No podemos poner nada inferior a -127!
   Por ejemplo, si intentáramos escribir la constante \( 2^{-151} + 2^{-153} \), que es ésta en binario:

\( ({.}0000'0000'0000'0000'0000'0001'{\color{red}\mathbf {01}})_2 \cdot 2^{-127} \)

aunque sólo tenemos tres dígitos significativos (mucho menos que los \( p = 24 \) de máximo que nos otorga el formato Single), se pierde el 1 que está en rojo, o sea, el que corresponde a la potencia \( 2^{-151} \). Queda tan solo:

\( ({.}0000'0000'0000'0000'0000'0001'{\color{red}\mathbf {00}})_2 \cdot 2^{-127} \)

   Otro ejemplo, aún más triste, sería el intento de expresar por ejemplo el número \( 2^{-153} \):

\( ({.}0000'0000'0000'0000'0000'0000'{\color{red}\mathbf {01}})_2 \cdot 2^{-127} \)

   Directamente la parte que está en rojo se pierde, y queda convertida en 0:

\( ({.}0000'0000'0000'0000'0000'0000'{\color{red}\mathbf {00}})_2 \cdot 2^{-127} \)

   Esto corresponde, directamente, a la constante +0 en Single Precision.
   Todos los números subnormales se consideran un desbordamiento por debajo, o en inglés: underflow.
   Sin embargo, el underflow se comporta algo diferente al overflow, ya que el overflow pierde toda información sobre la constante que se intentaba representar, mientras que el underflow conserva algunos dígitos de información aún, de la constante "verdadera".
   Es decir, el underflow provee un mecanismo por el cual uno puede salirse del rango de valores inferiores del formato, pero en forma gradual, hasta llegar a 0.
   En formatos donde no existen los números subnormales, todo underflow se convierte directamente a 0.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Surge aquí una disyuntiva en cuanto a los valores subnormales que son todavía muy cercanos a los valores normales. ¿Hay redondeo? ¿Hacia qué dirección?
   Aquí empieza la zona turbia...
   Lo que interpreto, tras haber analizado los varios documentos, es que primero se efectúa siempre un redondeo, y luego se efectúa la representación definitiva en binario, en este caso con el formato Single Precision.
   Estimo  ::) que el redondeo se efectúa respecto el último dígito de los \( p = 24 \) que se visualizan en el caso de números subnormales, sin importar que el primer dígito significativo no sea el primer bit de la mantisa.
   Si estoy en lo cierto con esto, un número como \( 2^{e_{min}} - \epsilon \), con \( \epsilon \leq 2^{e_{min}-p-1} \) (que está en el rango de los números subnormales), debería redondearse de modo que se convierta en el número \( 2^{e_{min}} \), que es el primer (o más pequeño) número normalizado.

   En estas situaciones el procedimiento a realizar no es tan sencillo. Se supone que, en cualquiera de los dos casos, estamos en una situación de underflow, sin importar que el redondeo haya producido finalmente un número nornalizado.
   Esto se informa al sistema mediante señales??? ??? :-*
   Las "señales" son un tema que veremos mucho después.
   De hecho, el texto del estándar IEEE 754 contempla que sea posible que se generen señales de underflow aún con el valor normalizado \( 2^{e_{min}} \).
   Así, podemos leer entre líneas de este texto, que es posible que un redondeo puede causar que un valor subnormal se convierta en uno normalizado.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   El estándar C99 del lenguaje C no dice nada especial sobre los temas analizados en este post.
   Tan sólo se ajusta al estándar IEEE 754 (siempre bajo el supuesto de que la macro __STDC_IEC_559__ sea 1).
   En tal situación, no hay que agregar ninguna salvedad sobre los valores infinitos y los valores subnormales.
   Ambos estándares establecen que deben producirse resultados consecuentes en las operaciones de punto flotante, y esto incluye lo que hemos discutido sobre constantes escritas a mano, así como redondeos y conversiones entre binario y decimal.
   "Consecuente" quiere decir que se tienen que obtener los mismos resultados ya sea con constantes durante la etapa de compilación, así como en la etapa de ejecución con el programa ya compilado.
   Asimismo, las funciones de la librería estándar tienen que producir resultados consecuentes con las reglas de conversión y redondeo especificados por los estándares respectivos.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Hemos visto que no podemos producir "a propósito" en el lenguaje C, una constante escrita a mano que resulta en un valor NaN.
   Si en la implementación local están activados los valores NaN, entonces se puede obtener un valor NaN silencioso mediante una macro predefinida de nombre NAN.
   Pero no hay modo directo de generar un NaN a mano. (Aunque no es posible hacerlo con una constante de punto flotante, en cambio se puede generar un NaN con una expresión constante de punto flotante, pero es un tema que trataremos en futuros posts).

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Para que se diviertan un poco, les dejo un par de páginas donde se pueden hacer experimentos con números binarios, decimales y hexadecimales, con partes entera y fraccionaria, así como visualizar los bits de un dato en formato Single Precision.

Convertir entre binario, decimal y hexadecimal

Convertir bits de un Single Precision de IEEE 754

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

19 Enero, 2013, 11:22 pm
Respuesta #22

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,739
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
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_DIG

Cantidad \( p \) de dígitos de precisión (\( p \)) en la mantisa de un número de tipo float.

DBL_MANT_DIG

Cantidad \( p \) de dígitos de precisión (\( p \)) en la mantisa de un número de tipo double.

LDBL_MANT_DIG

Cantidad \( 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 \).  :o
   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 Precision8^)
   (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. Constantes

titulado: 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.

 :-* :-*

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

21 Enero, 2013, 10:29 pm
Respuesta #23

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,739
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
23. Números de punto flotante. Compilador GCC

Hasta ahora nos hemos contentado con estudiar el estándar de C, sin preocuparnos por el compilador.
De hecho, para ajustar el compilador al estándar simplemente tuvimos que poner una sencilla opción en la configuración de nuestro entorno de trabajo wxDevC++.
Pero eso puede hacerse mientras el comportamiento del compilador es el adecuado.

En cambio, cuando el compilador no ha podido implementar satisfactoriamente los requisitos del estándar, tenemos que investigar cuáles detalles del estándar están implementados, y con qué características.

¿Tan importante es el bendito estándar?  >:(
Repetiremos una y otra vez que sí: pues nos interesa hacer programas compatibles con todos los sistemas, y esta compatibilidad sólo puede asegurarse siguiendo un estándar.

Implementar correctamente los números de punto flotante en un compilador determinado, no es sencillo.
Es un punto sensible que requiere ser investigado con detenimiento.
Como si esto fuera poco, hay opciones a múltiples variantes en el universo de los tipos de punto flotante, que nuestro compilador GCC se da el lujo de implementar a su antojo.

Primero que nada, la documentación necesaria:

[1] Estado de GCC respecto el estándar C99

[2] Estándares de C y su grado de implementación por GCC

[3] Implementación del lenguaje C que hace GCC

[4] Implementación de GCC de números de punto flotante

[5] Tipos de punto flotante adicionales

[6] Tipos flotante de Semi-Precición

[7] Tipos flotantes decimales

[8] Constantes de punto flotante en hexadecimal

[9] Tipos de punto flotante de punto fijo

[10] Extensiones que el GCC hace por su cuenta al lenguaje C



En [1] aparece una tabla actualizada de cuán bien está implementado C99 en GCC.
Podemos ver que hay algún problema en, y sólo en, la entrada correspondiente a floating point (punto flotante). Debajo se comenta lo siguiente:

Citar
IEC 60559 is IEEE 754 floating point. This works if and only if the hardware is perfectly compliant, but GCC does not define __STDC_IEC_559__ or implement the associated standard pragmas;

Esto quiere decir que, mientras nuestros programas vayan a correr en computadoras que sean compatibles con la normativa de IEEE 754 (que por suerte es lo esperable en la mayoría de los sistemas actuales que nos vamos a encontrar, sobretodo computadoras personales), los tipos de punto flotante van a funcionar tal como en la norma.

Sin embargo, no se define la macro __STDC_IEC_559__ de la que hemos hablado en posts anteriores, y que sirve para informar si el compilador tiene soporte para IEEE 754.
O sea que GCC 4.7 aún no considera que su soporte de la normativa IEEE 754 esté completo o correctamente implementado.

Sin embargo, esto no quiere decir que no podamos hacer programas con números de punto flotante.
Más bien quiere decir que debemos hacer esos programas, pero con sumo cuidado, estudiando detenidamente las especificaciones técnicas, y tratar de restringirnos mientras sea posible a aquellos aspectos del compilador que ya se han logrado hacer compatibles con el estándar C99.

La adecuada utilización de los números en punto flotante requiere, además, un minucioso estudio no sólo de las posibilidades y limitaciones del estándar C o el compilador GCC, sino también de un análisis matemático profundo del manejo de errores de aproximación causados por redondeos numéricos y demás limitaciones prácticas de los números de punto flotante.



En [2] podemos ver cómo se adapta GCC a los estándares.
Al parecer, GCC es totalmente compatible con el estándar C90 del lenguaje C.
Para activar esta sintaxis, debe especificarse la opción -std=C90 en "Herramientas", "Opciones del Compilador", "Compiler command line".

Aún así la sintaxis activada con esa opción no es estricta acorde al estándar.
Para lograr que el compilador respete la normativa C90 en forma más estricta, basta agregar la opción -pedantic, así:

-std=c90   -pedantic

En cuanto al soporte para C99, dice el texto que admite ese estándar, excepto por unas cuantas características que aún no se han podido poner en práctica.
En particular, uno de los temas más delicados es el que nos concierne ahora, de los tipos de punto flotante.

La opción para elegir compilar según la normativa C99 sería -std=c99, o bien -std=c99 -pedantic.
En cualquier caso, aún no está completamente implementado este estándar.

La opción -std=c9x también sirve para indicar este último estándar, mientras aún estaba en desarollo.
Hoy en día es una opción del compilador GCC que ya no debiéramos usar.

Finalmente, está el estándar C11 (del año 2011), para el cual GCC tiene un soporte bastante limitado.
Por esta razón, y debido a que es un estándar demasiado reciente, como para que el mundo entero haya podido migrar satisfactoriamente hacia él, no hablaremos en nuestro curso del estándar C11.
Para aquellos interesados, pueden poner a prueba la capacidad de GCC para interpretar C11 poniendo la opción del compilador -std=c11.



Algunos aspectos son dejados por el estándar a consideración de la implementación local, aunque exige que estén bien documentados.
En [4] podemos ver esos detalles para el tema de punto flotante.

El GCC 4.7 no define otros métodos de evaluación (vía la macro FLT_EVAL_METHOD) que los indicados por C99.

El GCC 4.7 no utiliza otros modos de redondeo que los especificados por el estándar (vía la macro FLT_ROUNDS).

Hay otras especificaciones, pero por ahora no nos conciernen.



En [5] vemos que GCC define dos nuevos tipos de punto flotante, que son: __float80, __float128. A su vez, es posible en GCC especificar constantes de punto flotante de esos tipos, mediante los sufijos wW) y qQ) respectivamente.

Esto es sólo informativo, porque no vamos a explicar ni los más mínimos detalles de esto.



En [6] vemos que GCC define el tipo __fp16, que es punto flotante de semi-precisión. Según el modo en que se lo utilice, puede ser compatible con el binary16 de IEEE 754.

Su propósito es sólo para almacenamiento de datos, y no para cálculos, luego su uso está bastante restringido.

Puede ser interesante experimentar con este tipo de datos para ver ejemplos más sencillos de lo que ocurre internamente con los redondeos y los cambios de base.



En [7] aparece una implementación de GCC concerniente a tipos de punto flotante específicamente diseñados para base decimal. Es decir que no han de pasar internamente por una conversión a base binaria. Los tipos se basan en el estándar ISO/IEC WDTR24732.

Los tipos se llaman _Decimal32, _Decimal64, _Decimal128,
y es posible además especificar constantes de esos tipos, mediante los sufijos dF, dD, dL, respectivamente.

A nosotros no nos interesan estos tipos decimales, por no ser estrictamente estándar C99. Además, no tienen un soporte adecuado dentro del mismo GCC 4.7, así que no es recomendable su uso por ahora.



En [8] se explica que el GCC adopta las constantes de punto flotante hexadecimales del estándar C99.
O sea que podemos escribir sin temor constantes como 0x2.ec4p5F.



En [9] se da soporte en GCC a tipos de punto fijo. Hay una cincuentena de tipos definidos ahí, así como nuevos sufijos para cada caso.
No tiene caso repetir aquí todo eso, y lo dejamos como una curiosidad (que puede verse en el enlace respectivo).
Tampoco explicaremos nada acerca de qué se supone que hacen estos nuevos tipos de datos numéricos.



En [10] vemos, entre otras cosas, el uso de la opción -pedantic para instruir al compilador a que no utilice ninguna de las extensiones de GCC no soportadas por el estándar ISO de C.

Esto obliga a una sintaxis estricta, y nada de jueguitos o experimentos raros.




A pesar de que el soporte de GCC para C99 no es completo, está cerca de serlo,
y en cualquier caso seguiremos con la práctica de ajustarnos al estándar, para asegurarnos la portabilidad.




Organización
Comentarios y Consultas

25 Enero, 2013, 12:06 am
Respuesta #24

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,739
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
24. Números de punto flotante. Programas de Testeo (parte I)

   El programita que vamos a ver aquí no tiene ningún misterio.
   Lo único que hace es mostrar la información de los tipos de punto flotante que, por defecto, tiene que mostrar nuestra implementación local, acorde a las exigencias del estándar C99.
   Les voy a explicar cómo está hecho el programa, y luego si quieren lo pueden analizar y ver si se entiende. Pero no voy a comentarlo línea por línea, como he hecho en temas anteriores.

   Las constantes vienen dadas como las macros explicadas en posts anteriores.
   Lo que haremos será mostrar el nombre de la macro.
   Mediante nuestra macro especial DEB(X)
   vamos a mostrar cómo está definida cada macro de <float.h>.

   A continuación mostramos, escritas "a mano" por el programador (o sea por mí), qué valores tiene permitido tomar una macro determinada, según especifica el estándar C99.
   Como siempre, esa información tenemos que escribirla a mano, porque no está en ninguna parte de nuestra implementación local de C.
   Por último, mostramos el valor real que tiene la macro en nuestra implementación local.

A veces parecerá que se muestra dos veces lo mismo, pero no es así:  :o
   El compilador define la macro en <float.h> con una "expresión" aritmética.
   El resultado final de esa expresión la calculamos nosotros y la mostramos luego.
   ¿Cómo la calculamos? Muy sencillo: mostramos su valor mediante printf(), y allí se hace el cálculo antes de exhibir el resultado en pantalla.

   Si la expresión era demasiado simple, no había nada que calcular, y se verán ambas iguales.
   Para mostrar los datos y los resultados, hemos usado printf(), de la siguiente manera:

\( \bullet \)   Ponemos muchas cadenas de texto concatenadas.

   Como al C no le importa si seguimos escribiendo líneas abajo, entonces concatenamos varias cadenas puestas una debajo de la otra, a fin de mostrar lo más parecido posible a cómo será la salida real en pantalla.
   O sea, el estilo de printf() que venimos usando hasta ahora...

\( \bullet \)   En el medio intercalamos información mediante nuestra vieja conocida macro DEB(X).
   Esto puede hacerse porque DEB(X) genera una cadena entrecomillada, y entonces simplemente se concatena a las demás cadenas presentes en el printf().

Repetimos aquí: Usamos el truquito de la macro DEB(X) para mostrar el contenido de las macros, o sea, para ver "por dentro" al archivo <float.h>, y cómo define las macros ahí.

\( \bullet \)   Finalmente, hay que echar una mirada a los valores numéricos "de verdad" de las macros de <float.h>.
   Para eso tenemos que agregar los valores como parámetros adicionales al printf().
   Además, en la cadena de formato tenemos que usar controles "%" adecuados.

   Cuando queramos ver el valor de una macro cuyo valor es un entero, bastará considerar que esos valores son de tipo int (por defecto el C los toma así).
   No hay riesgos de que los valores caigan en un tipo long int, por ejemplo, ya que las macros de <float.h>, cuando son valores enteros, denotan cantidades pequeñas, que caben en un int.
   En ese caso, los parámetros enteros los indicamos con %i en la cadena de formato de printf().
   Si el lector quiere evitar riesgos, entonces le conviene usar el máximo tipo entero admisible en el sistema: intmax_t, y hacer un cast forzado de cada constante entera invocada, por ejemplo: ((intmax_t) (LDBL_MAX_EXP)), y consecuentemente en la cadena de formato usar %ji.

   El estándar no dice nada al respecto, pero es casi seguro que toda implementación decente de C será de tal modo que todas las constantes enteras definidas en macros de las librerías estándar de C, quepan en el tipo int. Si yo hiciera un compilador de C, lo haría de esa manera, con esos cuidados.

   Ahora hablemos de las macros que se definen como constantes de punto flotante.
   Pueden ser ellas constantes float, double o long double.
   Para exhibirlas, lo que haremos será uniformar su presentación al tipo máximo del sistema: long double.
   ¿Por qué? Si una macro se define como un float, quiere decir que tras unos pocos dígitos binarios, se trunca. Para visualizar este número de un modo más "realista" lo introducimos dentro de un long float, que es algo así como la "lingua franca" ( :P :P :P :P) en la cual podemos visualizar todos los valores de punto flotante de nuestro sistema.

   Si las constantes están correctamente definidas, los valores para las macros de float en <float.h> serán representables en float, y los double en double, sin riesgos de errores.

   Pero no podemos predecir que no habrá errores en nuestro compilador.
   Una leve precaución es, por lo tanto, forzar esos valores a long double para mostrarlos "tal cual" son (en alta precisión).

   Para dar un ejemplo: si tomamos el máximo float representable: FLT_MAX, y lo multiplicamos por (1 + FLT_EPSILON), nos da un valor fuera de rango en float, con un consecuente error de overflow, respecto float.
   Así que este nuevo valor no se puede ver en float.
   Pero en long double sí podemos "verlo", y así podríamos exhibir explícitamente cuál es el primer valor que produce un overflow para el tipo float.

   En otro orden de cosas, aunque los números internamente estén bien calculados, puede que al intentar mostrarlos en pantalla con printf() se produzca algún error.
¿Qué hace printf() al mostrar un valor de punto flotante: trunca, redondea, o qué?
   Lo que hace es redondear al valor más cercano posible en el tipo de punto flotante que intenta representar.
   Pero, aún sabiendo esto, para evitar una posible pérdida de información debida a convenciones del printf() que aún no conocemos, o no tenemos ganas de investigar, simplemente procuramos convertir todo a long double, y nos evitamos un estudio más engorroso sobre el tema.

   (En posts futuros, cuando estudiemos a fondo la función printf(), veremos con detenimiento qué es lo que hace. Aquí nos hacemos los ciegos).

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   A fin de convertir todos los valores punto flotante a "long double", hemos definido y utilizado una macro que hace un cast forzado hacia long double, así:

#define LD(X) ((long double)(X))

   Su modo de uso sería, por ejemplo, así:

LD(FLT_MAX)

   Para indicar parámetros de punto flotante de tipo long double en printf(), tenemos que usar el modificador %Le.
   (Hay muchas opciones, pero sólo explicaré esa por ahora).
   Con %Le dejamos lugar en el printf() para que se "inserte" ahí el siguiente parámetro de tipo long double, y además sea exhibido en base decimal.

   La base decimal no nos deja apreciar con exactitud lo que está ocurriendo a nivel de bits, en los límites de representabilidad de los tipos float, double y long double.
   Para visualizar más claramente lo que está ocurriendo conviene mostrar los valores en base binaria.
   Esto se hace con el modificador %La, que deja hueco en el printf() para insertar un long double, y lo exhibe en base hexadecimal.

   Si bien hexadecimal no es lo mismo que binaria, es fácil traducir de una base a la otra, y así esos números se hacen más legibles.

   En el programa mostramos las dos versiones del mismo valor: decimal y hexadecimal.
   La exhibición en base decimal nos sirve cuando menos para tener una idea de "por dónde andan" los valores referidos. Es para hacer una estimación cuantitativa.
   Y el muestreo en base hexadecimal nos ilustra qué ocurre a nivel de bits.

   No es necesario que las constantes punto flotante de las macros de <float.h> tengan una expresión sencilla y exacta en base binaria/hexadecimal.
   Pero esperar que eso ocurra es lo más común.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Por supuesto, hemos agregado nuestras conocidas macros DEB(X) y DEB(X), que venimos usando desde el principio del curso.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Hay situaciones que dependen del valor que tengan las macros FLT_EVAL_METHOD y FLT_ROUNDS.

   Es por eso que hemos tratado esas situaciones con directivas de compilación condicional.
   En caso de que esas macros tengan un cierto valor, se compila un trozo de programa, y si tienen otro valor, se compila otro trozo distinto.
   Si se fijan bien, estos "trozos" son cadenas de texto dentro de un printf().

   O sea que hemos puesto compilación condicional insertada en medio de las partes de la cadena de formato de un printf()8^) >:D
   Esto servirá de paso para ilustrar lo que hace la compilación condicional.

   No es lo mismo "compilación condicional con #if"  que "hacer un programa que se bifurque según una sentencia condicional if(...)".

   La compilación condicional es una forma abreviada de realizar varias versiones parecidas de un mismo programa.
   Tocando algunos parámetros se consigue un programa ligeramente distinto, sin necesidad de estar guardando en el disco distintas versiones que hacen casi lo mismo.  ;)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   En el programa he agregado unas macros extra, que nada tienen que ver con los puntos flotantes. Detalles en spoiler:
   
Agregando acentos al programa

   A fin de garantizar la correcta visualización de los acentos y las eñes en la línea de comandos, he agregado la sentencia siguiente:

system("CHCP 28591>NUL");

   Esa sentencia hace una llamada al sistema (en este caso Windows) e invoca el comando de sistema CHCP con el parámetro 28591.
   Esto cambia la página de códigos a 28591, lo cual nos muestra correctamente los acentos.
   La parte final: ">NUL" sólo redirecciona los mensajes del comando CHCP al puerto NUL de Windows, con lo cual ocultamos el eco de CHCP, que ya no veremos en pantalla.
   Para que esto funcione, hay que agregar, como siempre, el archivo de biblioteca <stdlib.h>.

   Puede ocurrir que no siempre queramos tener esto en nuestro programa ejecutable.
   Para eso se ha agregado una macro que no hace nada:

#define ACENTOS_LineaComandosOk

   A continuación, si esa macro está definida, habilitamos el archivo <stdlib.h>, y también definimos la macro PAG_CODIGOS_28591_LINEA_COMANDOS, cuyo cometido es realizar la acción system("CHCP 28591>NUL").
   En cambio, si la macro ACENTOS_LineaComandosOk no está definida (o sea, si ustedes borran del programa la línea #define ACENTOS_LineaComandosOk), se declara la macro PAG_CODIGOS_28591_LINEA_COMANDOS como una sentencia vacía (un punto y coma suelto bastará para esto).

[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   El programa va colocado a continuación en spoiler:

Spoiler
TestingFloatMacros.c


/*****************************************************************************/
/* TestingFloatMacros.c
/* ====================
/*
/* Este programa verifica los valores de las macros de float.h
/* presentes en la implementación local,
/* y muestra información relacionada con el estándar C99,
/* a fin de poder realizar comparaciones y/o análisis.
/* Las comparaciones son sólo a fines informativos, no se pueden hacer cálculos
/* con los valores exhibidos del estándar C99.
/*
/* Realizado por: Argentinator
/* Enero 2013
/*
/* Curso de programación en C:
/* http://rinconmatematico.com/foros/index.php?topic=64835.msg260248#msg260248
/*
/*****************************************************************************/

#include <float.h>
#include <math.h>
#include <stdio.h>

/* Macro para forzar a tipo (long double) */
#define LD(X) ((long double)(X))

#define DEB_(X) #X
#define DEB(X) DEB_(X)

#define ACENTOS_LineaComandosOk

#ifdef ACENTOS_LineaComandosOk
  #include <stdlib.h>     /*Se usará sólo la llamada al sistema system(...); */
  /* Configurar acentos correctos en la línea de comandos de Windows         */
  #define PAG_CODIGOS_28591_LINEA_COMANDOS system("CHCP 28591>NUL");
#else
  #define PAG_CODIGOS_28591_LINEA_COMANDOS ;
#endif
 
int main(void) {

  PAG_CODIGOS_28591_LINEA_COMANDOS;
 
  printf(
     "__________________________________________________________________________________\n\n"
     "\t\t Análisis de Tipos de Punto Flotante en C.\n"
     "__________________________________________________________________________________\n"
     "\n\n"
     "Tipos de punto flotante de C99: float, double, long double\n\n"
     "Tipos eficientes de punto flotante de C99 (math.h): float_t, double_t\n"
     "\n\n\n"
   );
   
   printf(
     "__________________________________________________________________________________\n\n"
      "FLT_EVAL_METHOD: Método de evaluación (para punto flotante).\n\n"
      "Macro definida como: " DEB(FLT_EVAL_METHOD) "\n"
      "Valores Estándar:    -1, 0, 1, 2\n"
      "Valor presente:      %i\n"
      "\n",
      FLT_EVAL_METHOD
      );
     
  printf(
    "__________________________________________________________________________________\n\n"
    "El estándar requiere que:\n\n"
    "float_t: contenga al menos la precisión y rango de valores de float\n"
    "double_t: contenga al menos la precisión y rango de valores de float\n"
    "\n"   
    "El valor presente de FLT_EVAL_METHOD implica que:\n\n"
#if FLT_EVAL_METHOD == 0
    "float_t  == float\n"
    "double_t == double\n"
#elif FLT_EVAL_METHOD == 1
    "float_t  == double\n"
    "double_t == double\n"
#elif FLT_EVAL_METHOD == 2
    "float_t  == long double\n"
    "double_t == long double\n"
#else
    "Consulte la documentación de su compilador para averiguar a qué tipos corresponden\n"
    "float_t y double_t en su sistema.\n"
#endif     
  );
 
  printf(
     "__________________________________________________________________________________\n\n"
      "FLT_RADIX: Base b para representar internamente números de punto flotante.\n\n"
      "Macro definida como: " DEB(FLT_RADIX) "\n"
      "Estándar exige:      b >= 2\n"
      "Valor presente:      b == %i\n"
      "\n",
      FLT_RADIX
      );
     
     
  printf(
     "__________________________________________________________________________________\n\n"
      "FLT_ROUNDS: Método de redondeo para punto flotante.\n\n"
      "Macro definida como: " DEB(FLT_ROUNDS) "\n"
      "Valores Estándar:      -1, 0, 1, 2, 3\n"
      "Valor presente:        %i\n"
      "\n",
      FLT_ROUNDS
   );
   
  printf(
     "__________________________________________________________________________________\n\n"
     "El modo de redondeo presente por defecto es:\n\n"
#if FLT_ROUNDS == 0
     "Toward 0:         hacia el valor con menor valor absoluto.\n\n"
#elif FLT_ROUNDS == 1
     "To nearest:       hacia el valor cuyo último bit de mantisa es par.\n\n"
#elif FLT_ROUNDS == 2
     "Toward +infinity: hacia el valor más hacia la derecha.\n\n"
#elif FLT_ROUNDS == 3
     "Toward -infinity: hacia el valor más hacia la izquierda.\n\n"
#else
     "Averiguar en la documentación de su compilador qué modo de redondeo se usa.\n"
#endif
  );
 
   printf(
     "__________________________________________________________________________________\n\n"
     "Cantidad de bits en base b = " DEB(FLT_RADIX) " para float, double y long double:\n\n"
     "Definiciones de macros:\n\n"
     "float:        FLT_MANT_DIG == " DEB(FLT_MANT_DIG) "\n"
     "double:       DBL_MANT_DIG == " DEB(DBL_MANT_DIG) "\n"
     "long double: LDBL_MANT_DIG == " DEB(LDBL_MANT_DIG) "\n"
     "\n"
     "Cantidad de bits en la mantisa en base b:\n\n"
     "float:       %i\n"
     "double:      %i\n"
     "long double: %i\n"
     "\n",
     FLT_MANT_DIG,
     DBL_MANT_DIG,
     LDBL_MANT_DIG 
  );
 
  printf(
     "__________________________________________________________________________________\n\n"
     "Valores POSITIVOS mínimos y máximos para float, double y long double:\n\n"
     "Especificaciones a través de macros:\n\n"
     "float:        FLT_MIN <= x <=  FLT_MAX\n"
     "double:       DBL_MIN <= x <=  DBL_MAX\n"
     "long double: LDBL_MIN <= x <= LDBL_MAX\n"
     "\n"
     "Exigencias del estándar:\n\n"
     "float:        FLT_MIN <= 1.0e-37 _____  +1.0e37 <=  FLT_MAX\n"
     "double:       DBL_MIN <= 1.0e-37 _____  +1.0e37 <=  DBL_MAX\n"
     "long double: LDBL_MIN <= 1.0e-37 _____  +1.0e37 <= LDBL_MAX\n"
     "\n"
     "Definiciones locales de macros:\n\n"
     "float:        FLT_MIN == " DEB(FLT_MIN) "\n"
     "              FLT_MAX == " DEB(FLT_MAX) "\n"
     "double:       DBL_MIN == " DEB(DBL_MIN) "\n"
     "              DBL_MAX == " DEB(DBL_MAX) "\n"
     "long double: LDBL_MIN == " DEB(LDBL_MIN) "\n"
     "             LDBL_MAX == " DEB(LDBL_MAX) "\n"
     "\n"
     "Rangos de valores presentes (mostrados en decimal):\n\n"
     "float:       %Le <= x <= %Le\n"
     "double:      %Le <= x <= %Le\n"
     "long double: %Le <= x <= %Le\n"
     "\n"
     "Rangos de valores presentes (mostrados en hexadecimal):\n\n"
     "float:       %La <= x <= %La\n"
     "double:      %La <= x <= %La\n"
     "long double: %La <= x <= %La\n"
     "\n",
     LD(FLT_MIN), LD(FLT_MAX),
     LD(DBL_MIN), LD(DBL_MAX),
     LD(LDBL_MIN), LD(LDBL_MAX),
     LD(FLT_MIN), LD(FLT_MAX),
     LD(DBL_MIN), LD(DBL_MAX),
     LD(LDBL_MIN), LD(LDBL_MAX)
  );

 
   printf(
     "__________________________________________________________________________________\n\n"
     "Mínimo exponente e en base b = " DEB(FLT_RADIX) " tal que b^(e - 1)es normalizado:\n\n"
     "Definiciones de macros:\n\n"
     "float:        FLT_MIN_EXP == " DEB(FLT_MIN_EXP) "\n"
     "double:       DBL_MIN_EXP == " DEB(DBL_MIN_EXP) "\n"
     "long double: LDBL_MIN_EXP == " DEB(LDBL_MIN_EXP) "\n"
     "\n"
     "Valor resultante de e:\n\n"
     "float:        FLT_MIN_EXP == %i\n"
     "double:       DBL_MIN_EXP == %i\n"
     "long double: LDBL_MIN_EXP == %i\n"
     "\n",
     FLT_MIN_EXP,
     DBL_MIN_EXP,
     LDBL_MIN_EXP
    );
   
   printf(
     "__________________________________________________________________________________\n\n"
     "Máximo exponente e en base b = " DEB(FLT_RADIX) " tal que b^(e - 1) es representable:\n\n"
     "Definiciones de macros:\n\n"
     "float:        FLT_MAX_EXP == " DEB(FLT_MAX_EXP) "\n"
     "double:       DBL_MAX_EXP == " DEB(DBL_MAX_EXP) "\n"
     "long double: LDBL_MAX_EXP == " DEB(LDBL_MAX_EXP) "\n"
     "\n"
     "Valor resultante de e:\n\n"
     "float:        FLT_MAX_EXP == %i\n"
     "double:       DBL_MAX_EXP == %i\n"
     "long double: LDBL_MAx_EXP == %i\n"
     "\n",
     FLT_MAX_EXP,
     DBL_MAX_EXP,
     LDBL_MAX_EXP
    );
   
   
   
   printf(
     "__________________________________________________________________________________\n\n"
     "Mínimo exponente d en base 10 tal que 10^d es normalizado:\n\n"
     "Definiciones de macros:\n\n"
     "float:        FLT_MIN_10_EXP == " DEB(FLT_MIN_10_EXP) "\n"
     "double:       DBL_MIN_10_EXP == " DEB(DBL_MIN_10_EXP) "\n"
     "long double: LDBL_MIN_10_EXP == " DEB(LDBL_MIN_10_EXP) "\n"
     "\n"
     "Valor resultante de e:\n\n"
     "float:        FLT_MIN_10_EXP == %i\n"
     "double:       DBL_MIN_10_EXP == %i\n"
     "long double: LDBL_MIN_10_EXP == %i\n"
     "\n",
     FLT_MIN_10_EXP,
     DBL_MIN_10_EXP,
     LDBL_MIN_10_EXP
    );
   
   printf(
     "__________________________________________________________________________________\n\n"
     "Máximo exponente d en base 10 tal que 10^d es representable:\n\n"
     "Definiciones de macros:\n\n"
     "float:        FLT_MAX_10_EXP == " DEB(FLT_MAX_10_EXP) "\n"
     "double:       DBL_MAX_10_EXP == " DEB(DBL_MAX_10_EXP) "\n"
     "long double: LDBL_MAX_10_EXP == " DEB(LDBL_MAX_10_EXP) "\n"
     "\n"
     "Valor resultante de e:\n\n"
     "float:        FLT_MAX_10_EXP == %i\n"
     "double:       DBL_MAX_10_EXP == %i\n"
     "long double: LDBL_MAX_10_EXP == %i\n"
     "\n",
     FLT_MAX_10_EXP,
     DBL_MAX_10_EXP,
     LDBL_MAX_10_EXP
    );
   
   printf(
     "__________________________________________________________________________________\n\n"
     "Diferencia epsilon entre 1 y el mínimo valor normalizado mayor que 1:\n\n"
     "Definiciones de macros:\n\n"
     "float:        FLT_EPSILON == " DEB(FLT_EPSILON) "\n"
     "double:       DBL_EPSILON == " DEB(DBL_EPSILON) "\n"
     "long double: LDBL_EPSILON == " DEB(LDBL_EPSILON) "\n"
     "\n"
     "Valor resultante de epsilon en hexadecimal y en decimal:\n\n"
     "float:        FLT_EPSILON == %La == %Le\n"
     "double:       DBL_EPSILON == %La == %Le\n"
     "long double: LDBL_EPSILON == %La == %Le\n"
     "\n",
     LD(FLT_EPSILON), LD(FLT_EPSILON),
     LD(DBL_EPSILON), LD(DBL_EPSILON),
     LD(LDBL_EPSILON), LD(LDBL_EPSILON)
    );
   
    printf(
     "__________________________________________________________________________________\n\n"
     "Cantidad q de dígitos DECIMALES exactos tal que\n"
     "al convertir un número x desde: decimal ---> binario ---> decimal\n"
     "se obtiene el x original (al escribirlo en base decimal):\n\n"
     "Definiciones de macros:\n\n"
     "float:        FLT_DIG == " DEB(FLT_DIG) "\n"
     "double:       DBL_DIG == " DEB(DBL_DIG) "\n"
     "long double: LDBL_DIG == " DEB(LDBL_DIG) "\n"
     "\n"
     "Valor resultante de q:\n\n"
     "float:        q == %i\n"
     "double:       q == %i\n"
     "long double:  q == %i\n"
     "\n", FLT_DIG, DBL_DIG, LDBL_DIG
     ); 
 
    printf(
     "__________________________________________________________________________________\n\n"
     "Cantidad n de dígitos DECIMALES exactos tal que\n"
     "al convertir un número x desde: binario ---> decimal ---> binario\n"
     "se obtiene el x original,\n"
     "con el tipo de punto flotante de mayor precisión y rango presente en el sistema:\n\n"
     "Macro:    DECIMAL_DIG == " DEB(DECIMAL_DIG) "\n"
     "Valor resultante de n == %i\n"
     "\n", DECIMAL_DIG
    );
   
   
   getchar();
 
 }


[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC


Organización
Comentarios y Consultas

27 Enero, 2013, 08:47 am
Respuesta #25

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,739
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
25. Números de punto flotante. Programas de Testeo (parte II)

   El archivo de biblioteca <math.h> contiene varias macros y funciones para diversos propósitos. En este post vamos a estudiar sólo algunas macros, que informan sobre valores infinitos, valores NaN, y diagnósticos que intentan distinguir entre valores finitos e infinitos, o normales y subnormales.

   Los valores concretos que pueden tener estas macros no está claramente especificado por el estándar, salvo por algunas pocas especificaciones.
   Esto es, sin embargo, el caso general.
   Bajo el supuesto de que la macro __STDC_IEC_559__ está definida (lo que equivale a decir que se respeta el estándar ISO/IEC/IEEE 60559),  el comportamiento de estas macros es más definido.

HUGE_VALF: Es una macro que indica un valor grande y fuera de rango, en concreto: una constante positiva de tipo float.

HUGE_VAL: Es una macro que indica un valor grande y fuera de rango, en concreto: una constante positiva de tipo double.

HUGE_VALL: Es una macro que indica un valor grande y fuera de rango, en concreto: una constante positiva de tipo long double.

(En efecto, la macro correspondiente a double es HUGE_VAL, y no HUGE_VALD).

   Si bien el estándar no especifica qué valor exactamente tendrán esas macros, su espíritu es representar un valor grande fuera de rango. Se dejan los detalles a la implementación local.
   Lo más probable es que representen un valor infinito positivo, en el caso de que la implementación local admita ese tipo de valores.
   En cualquier caso, es la implementación local la que tiene que arreglárselas para barajar correctamente el manejo y significado de estas macros.

INFINITY: Especifica una constante de tipo float que representa un valor infinito positivo o sin signo, en el caso de que la implementación local acepte valores infinitos.
   En caso de que esto no esté disponible, lo que hace es generar una constante float fuera de rango, que produce un error de overflow durante la etapa de compilación del programa.

NAN: Esta macro está definida si y sólo si en la implementación local se definen valores NaN silenciosos de tipo float. En ese caso se expande a un valor NaN silenciosos concreto de tipo float.

   En caso de que la macro __STDC_IEC_559__ esté definida, existirán valores infinitos y NaN silenciosos en la implementación local, aunque el estándar C99 no obliga a que existan NaN señalizadores.
   En este caso, además, las macros HUGE_VALF, HUGE_VAL, HUGE_VALL, expanden a los valores infinitos positivos respectivos de los tipos float, double y long double.

En particular, podemos asegurar que la constante NAN está definida, y representa un valor NaN silenciosos.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Hay macros y funciones que permiten clasificar valores de los tipos de punto flotante según que sean cero, subnormales, normales, finitos, infinitos, NaN, y quizá otros más, si la implementación local lo permite.

   Cada una de esas clases se identificará con algún número entero. ¿Cuál?
   Eso no es necesario establecerlo con certeza, pero el estándar C99 establece que esos números enteros, usados con fines de clasificación, tienen que ser distintos, y ser establecidos en la definición de las macros siguientes:

FP_INFINITE
FP_NAN
FP_NORMAL
FP_SUBNORMAL
FP_ZERO

   En rigor, esas macros no son más que unos números enteros (concretamente, de tipo int, para ser compatibles con otras especificaciones que se dan luego).
   Pero son valores que tienen un significado que debemos respetar en forma consecuente, para que nuestros programas estén prolijos.
   El significado es, obviamente:

Floating Point Infinity
Floating Point NaN
Floating Point Normal
Floating Point Subnormal
Floating Point Zero

   La implementación local puede, inclusive, definir más macros cuyo nombre empiece con FP_, y que indique otros conjuntos de valores de punto flotante.
   Nosotros, sin embargo, nos ceñimos estrictamente a lo que obliga el estándar C99.
   Las macros que se ocupan de clasificar valores de punto flotante, son las que listamos a continuación, y devuelven como resultado los valores FP_ arriba indicados.

fpclassify(x): Devuelve un valor entero de tipo int. El valor devuelto es una de las constantes FP_ arriba indicadas.
   Por ejemplo, devuelve FP_INFINITY si x es un valor infinito, FP_SUBNORMAL si x es subnormal, etc.

isfinite(x): Devuelve un valor entero de tipo int, que es distinto de 0 si x es un número de punto flotante finito (o sea, cero, normal o subnormal, y no infinito ni NaN), y devuelve 0 en caso contrario.

isinf(x): Devuelve un valor entero de tipo int, que es distinto de 0 si x es un valor infinito (positivo o negativo), y devuelve 0 en caso contrario.

isnane(x): Devuelve un valor entero de tipo int, que es distinto de 0 si x es un valor NaN, y devuelve 0 en caso contrario.

isnormal(x): Devuelve un valor entero de tipo int, que es distinto de 0 si x es un valor normalizado, y devuelve 0 en caso contrario.

   Para que estas macros funcionen adecuadamente, tienen que ser construidas inteligentemente por la implementación local, ya que las macros no son capaces de detectar el tipo de datos de su argumento (en este caso x).

   Como programadores, no es algo que a nosotros tenga que preocuparnos, pero aún así puede ser útil saber en más detalle cómo se supone que han de funcionar "internamente" esas macros.
   La macro toma el valor que se le ha pasado a través del argumento x.
   Intenta determinar el tamaño en bits del dato x pasado como argumento, a fin de determinar de la manera más adecuada posible el tipo de datos de punto flotante al que x pertenece. Luego de hacer alguna conversión de tipos, si fuera necesario, se pasa a clasificar el valor x en concreto.

   Esto es importante porque, por ejemplo, el valor x podría ser considerado normalizado en long double, pero podría ser subnormal en el tipo float. También un valor infinito en float podría ser por ejemplo finito en double.

   Finalmente, nombremos la siguiente macro:

signbit(x): Devuelve un valor de tipo int, que es distinto de 0 si el bit de signo del argumento x representa un signo negativo en el formato de números de punto flotante.
   Aquí no importa si el argumento x es normalizado, subnormal, infinito o NaN. Si hay presente un signo negativo, la macro signbit() lo indica en cualquier caso, incluso para valores NaN, cuyo signo en el estándar C99 no cuenta para nada, en principio. En el caso de que x sea 0.0, y si en el sistema se soportan ceros signados, entonces detecta el signo negativo del susodicho cero signado.
   Si el sistema no admite ceros signados, entonces se considera que los ceros tienen signo positivo, a los fines de la macro signbit() (o sea que devolverá el valor 0 en este caso).

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Lo que haremos a continuación será un programa que testee estos valores especiales, y poner a prueba las macros clasificadoras.
   Como siempre, nos ayudaremos de nuestras macros DEB_ y DEB para "espiar" cómo están definidas las macros de nuestro sistema.
   Si ustedes obtienen los mismos resultados que yo (casi seguro que sí), verán que varias de esas macros se han definido a través de ciertas funciones. Investigando un poco más, esas funciones tienen porciones de código en lenguaje ensamblador, y la cosa ya se va complicando.

   Así que no podemos extraer nada en limpio de la información que nos proporciona DEB.
   No tendremos más remedio que consultar el valor final que nos muestra la función printf().

   A nivel de directivas de compilación, tenemos que hacer algunos diagnósticos previos.
   Como nuestro programa tiene que servir para cualquier implementación ajustada al estándar C99, tenemos que considerar los casos anómalos que dicho estándar establece.

   Por ejemplo, si la implementación local genera un error de overflow durante la etapa de compilación, al intentar obtener el valor de punto flotante de la macro INFINITY, entonces tendremos que adaptar nuestro programa a esa circunstancia.
   No nos complicaremos mucho la vida.  8^)
   Definiremos una macro tipo "banderín", de nombre FLAG_INFINITY_OK, a la que adjudicaremos manualmente el valor 1 si sabemos que la macro INFINITY no nos da ningún error de overflow durante la compilación, y le daremos un valor 0 en caso contrario.

   En cuanto a la macro NAN, en cada lugar que vayamos a nombrarla, tendremos que poner declaraciones de compilación condicional ifdef, preguntando si NaN está definida o no.

   A fin de observar cómo están definidas "por dentro" las macros clasificatorias, las cuales necesitan un argumento x, vamos a definir una macro de nombre x, y cuya definición es de nuevo x mismo.  :o ??? :o ??? :o ??? :-X

   Las macros así definidas tienen la apariencia de ser "recurrentes", pero en realidad no lo son.
El compilador de C detecta estas definiciones de macros cuya definición es igual a su nombre, y las deja tal cual, sin hacer sustituciones ni conversiones de ningún tipo.

   Al invocar las macros clasificatorias con DEB, y con el argumento x, el resultado será que x funcionará como un parámetro "mudo", un símbolo que no representa valores de algún tipo de datos en particular.
   Es, pues, un truco nuevo  ;) ;) que aprendemos con las macros.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Tenemos que saber además cómo la función printf() nos mostrará los valores infinitos y NaN.

   Los valores infinitos los muestra como inf o infinity, y le antepone un signo negativo, si corresponde.
   Si se elige la forma abreviada inf, o la forma completa infinity, depende de la implementación local.
   En cuanto a los valores NaN, printf() los muestra simplemente como nan, o bien como nan(cccccc), donde cccccc representa una secuencia de dígitos y/o letras y/o el caracter subrayado: _.

   Estos detalles dependen de la implementación local.
   (Si el signo de NaN tiene relevancia y ha de aparecer en printf(), también depende de la implementación local).
   Seguramente ustedes obtendrán lo mismo que yo: inf para infinito y nan para NaN.
   Si en la cadena de formato de printf() ponemos %Le, se muestran inf, infinity, nan (en minúsculas).
   Si ponemos en cambio %Le, se muestran INF, INFINITY, NAN (en mayúsculas).

   Igual que en el post anterior, usaremos la macro LD para convertir todos los valores a long double.
¿Hay peligro de que un valor infinito en float, tras ser convertido a long double, pase a ser un valor finito, y obtengamos resultados erróneos por culpa de usar la macro LD?

   La respuesta es que NO, porque el estándar establece que los valores se conservan al pasar de un tipo de punto flotante al tipo mayor de todos, el long double. En particular, esto se aplica a valores infinitos y NaN.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Nuestro programa de testeo será, pues, muy sencillo.
   Lo ponemos en el siguiente spoiler:

Spoiler


/*****************************************************************************/
/* TestingFloatClassify.c
/* ======================
/*
/* Este programa muestra algunas macros de math.h
/* presentes en la implementación local.
/*
/* Realizado por: Argentinator
/* Enero 2013
/*
/* Curso de programación en C:
/* http://rinconmatematico.com/foros/index.php?topic=64835.msg260248#msg260248
/*
/*****************************************************************************/

#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

/* Macros de "depuración" (debug). Muestran cómo una macro X está definida */
#define DEB_(X) #X
#define DEB(X) DEB_(X)

/* La siguiente macro informa la situación de la macro INFINITY */
/* Cambiar a 0 si el compilador da overflow con macro INFINITY */
#define FLAG_INFINITY_OK 1   

/* Macro para forzar a tipo (long double) */
#define LD(X) ((long double)(X))

/* Macro que define un símbolo mudo */
#define x x


int main(void) {

  system("CHCP 28591>NUL"); /* Activar acentos "correctos" en línea de comandos */
 
  printf(
     "__________________________________________________________________________________\n\n"
     "\t\t Análisis de Tipos de Punto Flotante en C.\n"
     "__________________________________________________________________________________\n"
     "\n\n"
     "Tipos de punto flotante de C99: float, double, long double\n\n"
     "Tipos eficientes de punto flotante de C99 (math.h): float_t, double_t\n\n"
     "En este programa se analizan macros especiales de <math.h>.\n"
     "\n\n\n"
   );
   
   printf(
     "__________________________________________________________________________________\n\n"
      "HUGE_VALF: Valor muy grande para float.\n"
      "HUGE_VAL : Valor muy grande para double.\n"
      "HUGE_VALL: Valor muy grande para long double.\n"
      "INFINITY : Valor infinito positivo para float.\n"
      "NAN      : Valor NaN (Not a Number) para float.\n"
      "\n"
      "Macros definidas como: \n\n"
      "HUGE_VALF: " DEB(HUGE_VALF) "\n"
      "HUGE_VAL : " DEB(HUGE_VAL)  "\n"
      "HUGE_VALL: " DEB(HUGE_VALL) "\n"
      "INFINITY : " DEB(INFINITY)  "\n"
#ifdef NAN
      "NAN      : " DEB(NAN)       "\n"
#else
      "NAN      : (no está definida, pues en esta implementación no hay valores NaN implementados).\n"
#endif
      "\n"
      "Expanden a: \n\n"
      "\n"
      "HUGE_VALF: %Le\n"
      "HUGE_VAL : %Le\n"
      "HUGE_VALL: %Le\n"
#if FLAG_INFINITY_OK
      "INFINITY : %Le\n"
#else
      "INFINITY : (overflow en etapa de compilación)\n"
#endif
#ifdef NAN
      "NAN      : %Le\n"
#else
      "NAN      : (no implementado).\n"
#endif
      "\n", 
      LD(HUGE_VALF),
      LD(HUGE_VAL),
      LD(HUGE_VALL)
#if FLAG_INFINITY_OK
      , LD(INFINITY)
#endif
#ifdef NAN
        , LD(NAN)
#endif
      );
     
  printf(
    "__________________________________________________________________________________\n\n"
    "Constantes clasificadores de valores punto flotante.\n\n"
    "Se definen como:\n\n"
    "FP_INFINITE : " DEB(FP_INFINITE)  "\n"
    "FP_NAN      : " DEB(FP_NAN)       "\n"
    "FP_NORMAL   : " DEB(FP_NORMAL)    "\n"
    "FP_SUBNORMAL: " DEB(FP_SUBNORMAL) "\n"
    "FP_ZERO     : " DEB(FP_ZERO)      "\n"
    "\n"
    "Valor que adquieren:\n\n"
    "FP_INFINITE : %i\n"
    "FP_NAN      : %i\n"
    "FP_NORMAL   : %i\n"
    "FP_SUBNORMAL: %i\n"
    "FP_ZERO     : %i\n"
    "\n", FP_INFINITE, FP_NAN, FP_NORMAL, FP_SUBNORMAL, FP_ZERO
  );
 
  printf(
     "__________________________________________________________________________________\n\n"
      "Macros clasificadoras de valores de punto flotante.\n\n"
      "fpclassify:\n"
      "   " DEB(fpclassify(x)) "\n"
      "isfinite:\n"
      "   " DEB(isfinite(x)) "\n"
      "isinf:\n"
      "   " DEB(isinf(x)) "\n"
      "isnan:\n"
      "   " DEB(isnan(x)) "\n"
      "isnormal:\n"
      "   " DEB(isnormal(x)) "\n"
      "signbit:\n"
      "   " DEB(signbit(x)) "\n"
      );
     
     
   
   getchar();
 
 }




[cerrar]



CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización
Comentarios y Consultas

27 Enero, 2013, 11:53 pm
Respuesta #26

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,739
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
26. Números de punto flotante. Programas de Testeo (parte III)

   Quisiéramos ahora poner un poco a prueba las constantes y macros clasificatorias que vimos en el post anterior.
   Por ejemplo, la macro isinf(x) tiene que devolver un valor int distinto de 0 si x es un valor infinito, y en cambio devuelve 0 si x no es un valor infinito.

   Nos interesa "curiosear" a ver por ejemplo cuál es ese valor distinto de 0 que la macro devuelve.
   ¿Es un simple 1, es otro número, varía según qué valor tenga el argumento x?
   Esto depende de la implementación local, así que los valores devueltos obtenidos por isinf(x) no pueden usarse "confiadamente" para cálculos en un programa.
   Sólo pueden usarse para comparaciones del tipo booleano _Bool, o sea, diagnósticos verdadero-falso.

   Lo que haremos será utilizar valores extremos de los diversos tipos de punto flotante, así como las constantes HUGE_VALF, HUGE_VAL, HUGE_VALL, INFINITY, NAN, y ver cómo reaccionan las macros clasificatorias. Dado que el estándar C99 anticipa que el valor devuelto es de tipo int, podemos confiar en el uso del parámetro de formato %i en la llamada a printf().
   Se consideran también valores positivos y negativos.

   En este programa no necesitamos usar DEB, porque no requerimos "espiar por dentro" el contenido de las macros de <math.h> o <float.h>, pues eso ya lo hicimos en programas anteriores.
   Tampoco vamos a usar la macro LD, ya que no vamos a exhibir valores de punto flotante. Esos valores ya los hemos mostrado con el programa anterior.

   En cambio, sí tendremos que especificar nuestra macro FLAG_INFINITY_OK, ya que vamos a invocar el valor de la macro INFINITY, que en alguna implementación local puede dar un error de overflow.

   Para poner a prueba las macros con valores subnormales, tomamos el mínimo valor positivo normalizado de cada tipo, y lo dividimos por 4 (del mismo tipo).
   Esto dará automáticamente un valor subnormal en el tipo en cuestión.
   Si la implementación local no admite valores subnormales, entonces se obtendrá 0.
   De modo similar, tomando el máximo valor finito representable en cada tipo, al multplicarlo por 4 (en el mismo tipo) se obtiene un valor infinito, o bien un error de overflow.
   No vamos a mostrar directamente estos valores subnormales, aunque bien podríamos hacerlo.
   Esto es a gusto del programador.
   Yo no tengo ningún apuro, porque más adelante seguiremos haciendo cálculos con números de punto flotante, así que volveremos una y otra vez sobre los valores extremos, manejo de redondeos, overflow, underflow, errores, etc.

   Debido a que usaremos muchas veces los valores infinitos, y dado que no podemos anticipar el comportamiento de las macros HUGE_VALF, HUGE_VAL, HUGE_VALL en el caso en que INFINITY da overflow (el estándar no es claro en ese detalle), entonces lo más práctico será no correr el programa directamente.

   Por eso el programa cortará la compilación casi al principio de todo, en caso de que le informemos con FLAG_INFINITY_OK que hay algún problema.

   El programa es largo, repetitivo y monótono.  :-[
   Está escrito de un modo totalmente ineficiente, pero claro como el agua.  :-*
   A medida que aprendamos diversas técnicas, podremos reescribir nuestros programas de forma más compacta.  8^)

Antes da dárles el programa, quiero comentar un detalle de la ejecución.
   Al parecer, mi compilador GCC 4.7.1 interpretó los números subnormales de tipos float ó double, como si fueran números normalizados de tipo long double, o algo así.
   O sea, no detectó que se trata de valores subnormales.

   Puede que yo no haya interpretado correctamente las reglas del estándar C99, pero también es posible que el compilador GCC tenga un error en esas macros.
   Recordemos que los números de punto flotante son el punto débil de este compilador.

   Sin embargo hay otra explicación posible, y es la siguiente:
   En el programa TestingFloatMacros.c podemos ver, al ejecutarlo, el valor explícito que toma por defecto la macro FLT_EVAL_METHOD.
   El valor que yo obtuve (y que muy posiblemente será el mismo que obtuvieron ustedes) es 2, cuyo significado es que "todas las operaciones y expresiones se convierten o evalúan primero como si fueran de tipo long double".

   Esto podría explicar lo que está ocurriendo: ese método de evaluación afectaría en particular a las macros fpclassify() e isnormal(), haciendo que el compilador trate valores float ó double subnormales como si fueran valores long double normalizados.

   Como sea, en estos casos uno puede pedir asistencia técnica en un foro especializado, o bien reportar un error al equipo de desarrolladores.
   He enviado una consulta a los realizadores, pero fue antes de darme cuenta de lo que expliqué en el párrafo anterior, así que posiblemente me manden al diablo.
   Abogando a mi favor, les diré que el estándar no es demasiado específico sobre este tema.

El programa está en el spoiler:

Spoiler

TestingFloatIII.c



/*****************************************************************************/
/* TestingFloatIII.c
/* ==================
/*
/* Este programa testea valores de algunas macros de math.h
/* según funcionan en la implementación local,
/* a fin de poder realizar comparaciones y/o análisis.
/*
/* Realizado por: Argentinator
/* Enero 2013
/*
/* Curso de programación en C:
/* http://rinconmatematico.com/foros/index.php?topic=64835.msg260248#msg260248
/*
/*****************************************************************************/

#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

/* La siguiente macro informa la situación de la macro INFINITY */
/* Cambiar a 0 si el compilador da overflow con macro INFINITY */
#define FLAG_INFINITY_OK 1  

int main(void) {

  system("CHCP 28591>NUL"); /* Activar acentos "correctos" en línea de comandos */
 
  printf(
     "__________________________________________________________________________________\n\n"
     "\t\t Análisis de Tipos de Punto Flotante en C.\n"
     "__________________________________________________________________________________\n"
     "\n\n"
     "Tipos de punto flotante de C99: float, double, long double\n\n"
     "Tipos eficientes de punto flotante de C99 (math.h): float_t, double_t\n\n"
     "Análisis de valores devueltos por macros clasificatorias de math.h.\n"
     "\n\n\n"
   );
  
#if (FLAG_INFINITY_OK == 0)
  printf(
    "La macro INFINITY da error de overflow en este implementación.\n"
    "Por lo tanto este programa no mostrará más datos.\n"
  );
#else

  printf(
    "__________________________________________________________________________________\n\n"
    "Constantes clasificadores de valores punto flotante.\n\n"
    "FP_INFINITE : %i\n"
    "FP_NAN      : %i\n"
    "FP_NORMAL   : %i\n"
    "FP_SUBNORMAL: %i\n"
    "FP_ZERO     : %i\n"
    "\n", FP_INFINITE, FP_NAN, FP_NORMAL, FP_SUBNORMAL, FP_ZERO
  );
 

  printf(
     "__________________________________________________________________________________\n\n"
      "Macros clasificadoras de valores de punto flotante.\n\n"
      "fpclassify(x): Da un entero que clasifica al argumento de punto flotante x.\n\n"
      "fpclassify( HUGE_VALF)      : %i\n"
      "fpclassify(-HUGE_VALF)      : %i\n"
      "fpclassify( HUGE_VAL )      : %i\n"
      "fpclassify(-HUGE_VAL )      : %i\n"
      "fpclassify( HUGE_VALL)      : %i\n"
      "fpclassify(-HUGE_VALL)      : %i\n"
      "fpclassify( INFINITY)       : %i\n"
      "fpclassify(-INFINITY)       : %i\n"
#ifdef NAN
      "fpclassify( NAN)            : %i\n"
      "fpclassify(-NAN)            : %i\n"
#else
      "fpclassify( NAN)            : (macro NAN no está definida)\n"
      "fpclassify(-NAN)            : (macro NAN no está definida)\n"
#endif
      "fpclassify(+0.0F)           : %i\n"
      "fpclassify(-0.0F)           : %i\n"
      "fpclassify(+0.0)            : %i\n"
      "fpclassify(-0.0)            : %i\n"
      "fpclassify(+0.0L)           : %i\n"
      "fpclassify(-0.0L)           : %i\n"
      "fpclassify(+1.0F)           : %i\n"
      "fpclassify(-1.0F)           : %i\n"
      "fpclassify(+1.0)            : %i\n"
      "fpclassify(-1.0)            : %i\n"
      "fpclassify(+1.0L)           : %i\n"
      "fpclassify(-1.0L)           : %i\n"
      "fpclassify(+FLT_MIN)        : %i\n"
      "fpclassify(-FLT_MIN)        : %i\n"
      "fpclassify(+DBL_MIN)        : %i\n"
      "fpclassify(-DBL_MIN)        : %i\n"
      "fpclassify(+LDBL_MIN)       : %i\n"
      "fpclassify(-LDBL_MIN)       : %i\n"
      "fpclassify(+FLT_MIN  / 4.F) : %i\n"
      "fpclassify(-FLT_MIN  / 4.F) : %i\n"
      "fpclassify(+DBL_MIN  / 4.)  : %i\n"
      "fpclassify(-DBL_MIN  / 4.)  : %i\n"
      "fpclassify(+LDBL_MIN / 4.L) : %i\n"
      "fpclassify(-LDBL_MIN / 4.L) : %i\n"
      "fpclassify(+FLT_MAX)        : %i\n"
      "fpclassify(-FLT_MAX)        : %i\n"
      "fpclassify(+DBL_MAX)        : %i\n"
      "fpclassify(-DBL_MAX)        : %i\n"
      "fpclassify(+LDBL_MAX)       : %i\n"
      "fpclassify(-LDBL_MAX)       : %i\n"
      "fpclassify(+FLT_MAX  * 4.F) : %i\n"
      "fpclassify(-FLT_MAX  * 4.F) : %i\n"
      "fpclassify(+DBL_MAX  * 4.)  : %i\n"
      "fpclassify(-DBL_MAX  * 4.)  : %i\n"
      "fpclassify(+LDBL_MAX * 4.L) : %i\n"
      "fpclassify(-LDBL_MAX * 4.L) : %i\n"
      "\n\n"
        , fpclassify(HUGE_VALF)
        , fpclassify(-HUGE_VALF)
        , fpclassify(HUGE_VAL)
        , fpclassify(-HUGE_VAL)
        , fpclassify(HUGE_VALL)
        , fpclassify(-HUGE_VALL)
        , fpclassify(INFINITY)
        , fpclassify(-INFINITY)

#ifdef NAN
        , fpclassify(NAN)
        , fpclassify(-NAN)
#endif /* (NAN) */

        , fpclassify(0.0)
        , fpclassify(-0.0)
        , fpclassify(0.0)
        , fpclassify(-0.0)
        , fpclassify(0.0)
        , fpclassify(-0.0)
        , fpclassify(1.0)
        , fpclassify(-1.0)
        , fpclassify(1.0)
        , fpclassify(-1.0)
        , fpclassify(1.0)
        , fpclassify(-1.0)
        , fpclassify(FLT_MIN)
        , fpclassify(-FLT_MIN)
        , fpclassify(DBL_MIN)
        , fpclassify(-DBL_MIN)
        , fpclassify(LDBL_MIN)
        , fpclassify(-LDBL_MIN)
        , fpclassify(FLT_MIN  / 4.F)
        , fpclassify(-FLT_MIN / 4.F)
        , fpclassify(DBL_MIN  / 4.)
        , fpclassify(-DBL_MIN / 4.)
        , fpclassify(LDBL_MIN / 4.L)
        , fpclassify(-LDBL_MIN / 4.L)
        , fpclassify(FLT_MAX)
        , fpclassify(-FLT_MAX)
        , fpclassify(DBL_MAX)
        , fpclassify(-DBL_MAX)
        , fpclassify(LDBL_MAX)
        , fpclassify(-LDBL_MAX)
        , fpclassify(FLT_MAX  * 4.F)
        , fpclassify(-FLT_MAX * 4.F)
        , fpclassify(DBL_MAX  * 4.)
        , fpclassify(-DBL_MAX * 4.)
        , fpclassify(LDBL_MAX * 4.L)
        , fpclassify(-LDBL_MAX * 4.L)
      );

  printf(
     "__________________________________________________________________________________\n\n"
      "Macros clasificadoras de valores de punto flotante.\n\n"
      "isfinite(x): Da un entero distinto de 0 si x es punto flotante finito.\n\n"
      "isfinite( HUGE_VALF)      : %i\n"
      "isfinite(-HUGE_VALF)      : %i\n"
      "isfinite( HUGE_VAL )      : %i\n"
      "isfinite(-HUGE_VAL )      : %i\n"
      "isfinite( HUGE_VALL)      : %i\n"
      "isfinite(-HUGE_VALL)      : %i\n"
      "isfinite( INFINITY)       : %i\n"
      "isfinite(-INFINITY)       : %i\n"
#ifdef NAN
      "isfinite( NAN)            : %i\n"
      "isfinite(-NAN)            : %i\n"
#else
      "isfinite( NAN)            : (macro NAN no está definida)\n"
      "isfinite(-NAN)            : (macro NAN no está definida)\n"
#endif
      "isfinite(+0.0F)           : %i\n"
      "isfinite(-0.0F)           : %i\n"
      "isfinite(+0.0)            : %i\n"
      "isfinite(-0.0)            : %i\n"
      "isfinite(+0.0L)           : %i\n"
      "isfinite(-0.0L)           : %i\n"
      "isfinite(+1.0F)           : %i\n"
      "isfinite(-1.0F)           : %i\n"
      "isfinite(+1.0)            : %i\n"
      "isfinite(-1.0)            : %i\n"
      "isfinite(+1.0L)           : %i\n"
      "isfinite(-1.0L)           : %i\n"
      "isfinite(+FLT_MIN)        : %i\n"
      "isfinite(-FLT_MIN)        : %i\n"
      "isfinite(+DBL_MIN)        : %i\n"
      "isfinite(-DBL_MIN)        : %i\n"
      "isfinite(+LDBL_MIN)       : %i\n"
      "isfinite(-LDBL_MIN)       : %i\n"
      "isfinite(+FLT_MIN  / 4.F) : %i\n"
      "isfinite(-FLT_MIN  / 4.F) : %i\n"
      "isfinite(+DBL_MIN  / 4.)  : %i\n"
      "isfinite(-DBL_MIN  / 4.)  : %i\n"
      "isfinite(+LDBL_MIN / 4.L) : %i\n"
      "isfinite(-LDBL_MIN / 4.L) : %i\n"
      "isfinite(+FLT_MAX)        : %i\n"
      "isfinite(-FLT_MAX)        : %i\n"
      "isfinite(+DBL_MAX)        : %i\n"
      "isfinite(-DBL_MAX)        : %i\n"
      "isfinite(+LDBL_MAX)       : %i\n"
      "isfinite(-LDBL_MAX)       : %i\n"
      "isfinite(+FLT_MAX  * 4.F) : %i\n"
      "isfinite(-FLT_MAX  * 4.F) : %i\n"
      "isfinite(+DBL_MAX  * 4.)  : %i\n"
      "isfinite(-DBL_MAX  * 4.)  : %i\n"
      "isfinite(+LDBL_MAX * 4.L) : %i\n"
      "isfinite(-LDBL_MAX * 4.L) : %i\n"
      "\n\n"
        , isfinite(HUGE_VALF)
        , isfinite(-HUGE_VALF)
        , isfinite(HUGE_VAL)
        , isfinite(-HUGE_VAL)
        , isfinite(HUGE_VALL)
        , isfinite(-HUGE_VALL)
        , isfinite(INFINITY)
        , isfinite(-INFINITY)

#ifdef NAN
        , isfinite(NAN)
        , isfinite(-NAN)
#endif /* (NAN) */

        , isfinite(0.0)
        , isfinite(-0.0)
        , isfinite(0.0)
        , isfinite(-0.0)
        , isfinite(0.0)
        , isfinite(-0.0)
        , isfinite(1.0)
        , isfinite(-1.0)
        , isfinite(1.0)
        , isfinite(-1.0)
        , isfinite(1.0)
        , isfinite(-1.0)
        , isfinite(FLT_MIN)
        , isfinite(-FLT_MIN)
        , isfinite(DBL_MIN)
        , isfinite(-DBL_MIN)
        , isfinite(LDBL_MIN)
        , isfinite(-LDBL_MIN)
        , isfinite(FLT_MIN  / 4.F)
        , isfinite(-FLT_MIN / 4.F)
        , isfinite(DBL_MIN  / 4.)
        , isfinite(-DBL_MIN / 4.)
        , isfinite(LDBL_MIN / 4.L)
        , isfinite(-LDBL_MIN / 4.L)
        , isfinite(FLT_MAX)
        , isfinite(-FLT_MAX)
        , isfinite(DBL_MAX)
        , isfinite(-DBL_MAX)
        , isfinite(LDBL_MAX)
        , isfinite(-LDBL_MAX)
        , isfinite(FLT_MAX  * 4.F)
        , isfinite(-FLT_MAX * 4.F)
        , isfinite(DBL_MAX  * 4.)
        , isfinite(-DBL_MAX * 4.)
        , isfinite(LDBL_MAX * 4.L)
        , isfinite(-LDBL_MAX * 4.L)
      );

  printf(
     "__________________________________________________________________________________\n\n"
      "Macros clasificadoras de valores de punto flotante.\n\n"
      "isinf(x): Da un entero distinto de 0 si x es punto flotante infinito.\n\n"
      "isinf( HUGE_VALF)      : %i\n"
      "isinf(-HUGE_VALF)      : %i\n"
      "isinf( HUGE_VAL )      : %i\n"
      "isinf(-HUGE_VAL )      : %i\n"
      "isinf( HUGE_VALL)      : %i\n"
      "isinf(-HUGE_VALL)      : %i\n"
      "isinf( INFINITY)       : %i\n"
      "isinf(-INFINITY)       : %i\n"
#ifdef NAN
      "isinf( NAN)            : %i\n"
      "isinf(-NAN)            : %i\n"
#else
      "isinf( NAN)            : (macro NAN no está definida)\n"
      "isinf(-NAN)            : (macro NAN no está definida)\n"
#endif
      "isinf(+0.0F)           : %i\n"
      "isinf(-0.0F)           : %i\n"
      "isinf(+0.0)            : %i\n"
      "isinf(-0.0)            : %i\n"
      "isinf(+0.0L)           : %i\n"
      "isinf(-0.0L)           : %i\n"
      "isinf(+1.0F)           : %i\n"
      "isinf(-1.0F)           : %i\n"
      "isinf(+1.0)            : %i\n"
      "isinf(-1.0)            : %i\n"
      "isinf(+1.0L)           : %i\n"
      "isinf(-1.0L)           : %i\n"
      "isinf(+FLT_MIN)        : %i\n"
      "isinf(-FLT_MIN)        : %i\n"
      "isinf(+DBL_MIN)        : %i\n"
      "isinf(-DBL_MIN)        : %i\n"
      "isinf(+LDBL_MIN)       : %i\n"
      "isinf(-LDBL_MIN)       : %i\n"
      "isinf(+FLT_MIN  / 4.F) : %i\n"
      "isinf(-FLT_MIN  / 4.F) : %i\n"
      "isinf(+DBL_MIN  / 4.)  : %i\n"
      "isinf(-DBL_MIN  / 4.)  : %i\n"
      "isinf(+LDBL_MIN / 4.L) : %i\n"
      "isinf(-LDBL_MIN / 4.L) : %i\n"
      "isinf(+FLT_MAX)        : %i\n"
      "isinf(-FLT_MAX)        : %i\n"
      "isinf(+DBL_MAX)        : %i\n"
      "isinf(-DBL_MAX)        : %i\n"
      "isinf(+LDBL_MAX)       : %i\n"
      "isinf(-LDBL_MAX)       : %i\n"
      "isinf(+FLT_MAX  * 4.F) : %i\n"
      "isinf(-FLT_MAX  * 4.F) : %i\n"
      "isinf(+DBL_MAX  * 4.)  : %i\n"
      "isinf(-DBL_MAX  * 4.)  : %i\n"
      "isinf(+LDBL_MAX * 4.L) : %i\n"
      "isinf(-LDBL_MAX * 4.L) : %i\n"
      "\n\n"
        , isinf(HUGE_VALF)
        , isinf(-HUGE_VALF)
        , isinf(HUGE_VAL)
        , isinf(-HUGE_VAL)
        , isinf(HUGE_VALL)
        , isinf(-HUGE_VALL)
        , isinf(INFINITY)
        , isinf(-INFINITY)

#ifdef NAN
        , isinf(NAN)
        , isinf(-NAN)
#endif /* (NAN) */

        , isinf(0.0)
        , isinf(-0.0)
        , isinf(0.0)
        , isinf(-0.0)
        , isinf(0.0)
        , isinf(-0.0)
        , isinf(1.0)
        , isinf(-1.0)
        , isinf(1.0)
        , isinf(-1.0)
        , isinf(1.0)
        , isinf(-1.0)
        , isinf(FLT_MIN)
        , isinf(-FLT_MIN)
        , isinf(DBL_MIN)
        , isinf(-DBL_MIN)
        , isinf(LDBL_MIN)
        , isinf(-LDBL_MIN)
        , isinf(FLT_MIN  / 4.F)
        , isinf(-FLT_MIN / 4.F)
        , isinf(DBL_MIN  / 4.)
        , isinf(-DBL_MIN / 4.)
        , isinf(LDBL_MIN / 4.L)
        , isinf(-LDBL_MIN / 4.L)
        , isinf(FLT_MAX)
        , isinf(-FLT_MAX)
        , isinf(DBL_MAX)
        , isinf(-DBL_MAX)
        , isinf(LDBL_MAX)
        , isinf(-LDBL_MAX)
        , isinf(FLT_MAX  * 4.F)
        , isinf(-FLT_MAX * 4.F)
        , isinf(DBL_MAX  * 4.)
        , isinf(-DBL_MAX * 4.)
        , isinf(LDBL_MAX * 4.L)
        , isinf(-LDBL_MAX * 4.L)
      );

  printf(
     "__________________________________________________________________________________\n\n"
      "Macros clasificadoras de valores de punto flotante.\n\n"
      "isnan(x): Da un entero distinto de 0 si x es punto flotante NaN.\n\n"
      "isnan( HUGE_VALF)      : %i\n"
      "isnan(-HUGE_VALF)      : %i\n"
      "isnan( HUGE_VAL )      : %i\n"
      "isnan(-HUGE_VAL )      : %i\n"
      "isnan( HUGE_VALL)      : %i\n"
      "isnan(-HUGE_VALL)      : %i\n"
      "isnan( INFINITY)       : %i\n"
      "isnan(-INFINITY)       : %i\n"
#ifdef NAN
      "isnan( NAN)            : %i\n"
      "isnan(-NAN)            : %i\n"
#else
      "isnan( NAN)            : (macro NAN no está definida)\n"
      "isnan(-NAN)            : (macro NAN no está definida)\n"
#endif
      "isnan(+0.0F)           : %i\n"
      "isnan(-0.0F)           : %i\n"
      "isnan(+0.0)            : %i\n"
      "isnan(-0.0)            : %i\n"
      "isnan(+0.0L)           : %i\n"
      "isnan(-0.0L)           : %i\n"
      "isnan(+1.0F)           : %i\n"
      "isnan(-1.0F)           : %i\n"
      "isnan(+1.0)            : %i\n"
      "isnan(-1.0)            : %i\n"
      "isnan(+1.0L)           : %i\n"
      "isnan(-1.0L)           : %i\n"
      "isnan(+FLT_MIN)        : %i\n"
      "isnan(-FLT_MIN)        : %i\n"
      "isnan(+DBL_MIN)        : %i\n"
      "isnan(-DBL_MIN)        : %i\n"
      "isnan(+LDBL_MIN)       : %i\n"
      "isnan(-LDBL_MIN)       : %i\n"
      "isnan(+FLT_MIN  / 4.F) : %i\n"
      "isnan(-FLT_MIN  / 4.F) : %i\n"
      "isnan(+DBL_MIN  / 4.)  : %i\n"
      "isnan(-DBL_MIN  / 4.)  : %i\n"
      "isnan(+LDBL_MIN / 4.L) : %i\n"
      "isnan(-LDBL_MIN / 4.L) : %i\n"
      "isnan(+FLT_MAX)        : %i\n"
      "isnan(-FLT_MAX)        : %i\n"
      "isnan(+DBL_MAX)        : %i\n"
      "isnan(-DBL_MAX)        : %i\n"
      "isnan(+LDBL_MAX)       : %i\n"
      "isnan(-LDBL_MAX)       : %i\n"
      "isnan(+FLT_MAX  * 4.F) : %i\n"
      "isnan(-FLT_MAX  * 4.F) : %i\n"
      "isnan(+DBL_MAX  * 4.)  : %i\n"
      "isnan(-DBL_MAX  * 4.)  : %i\n"
      "isnan(+LDBL_MAX * 4.L) : %i\n"
      "isnan(-LDBL_MAX * 4.L) : %i\n"
      "\n\n"
        , isnan(HUGE_VALF)
        , isnan(-HUGE_VALF)
        , isnan(HUGE_VAL)
        , isnan(-HUGE_VAL)
        , isnan(HUGE_VALL)
        , isnan(-HUGE_VALL)
        , isnan(INFINITY)
        , isnan(-INFINITY)

#ifdef NAN
        , isnan(NAN)
        , isnan(-NAN)
#endif /* (NAN) */

        , isnan(0.0)
        , isnan(-0.0)
        , isnan(0.0)
        , isnan(-0.0)
        , isnan(0.0)
        , isnan(-0.0)
        , isnan(1.0)
        , isnan(-1.0)
        , isnan(1.0)
        , isnan(-1.0)
        , isnan(1.0)
        , isnan(-1.0)
        , isnan(FLT_MIN)
        , isnan(-FLT_MIN)
        , isnan(DBL_MIN)
        , isnan(-DBL_MIN)
        , isnan(LDBL_MIN)
        , isnan(-LDBL_MIN)
        , isnan(FLT_MIN  / 4.F)
        , isnan(-FLT_MIN / 4.F)
        , isnan(DBL_MIN  / 4.)
        , isnan(-DBL_MIN / 4.)
        , isnan(LDBL_MIN / 4.L)
        , isnan(-LDBL_MIN / 4.L)
        , isnan(FLT_MAX)
        , isnan(-FLT_MAX)
        , isnan(DBL_MAX)
        , isnan(-DBL_MAX)
        , isnan(LDBL_MAX)
        , isnan(-LDBL_MAX)
        , isnan(FLT_MAX  * 4.F)
        , isnan(-FLT_MAX * 4.F)
        , isnan(DBL_MAX  * 4.)
        , isnan(-DBL_MAX * 4.)
        , isnan(LDBL_MAX * 4.L)
        , isnan(-LDBL_MAX * 4.L)
      );

  printf(
     "__________________________________________________________________________________\n\n"
      "Macros clasificadoras de valores de punto flotante.\n\n"
      "isnormal(x): Da un entero distinto de 0 si x es punto flotante normalizado.\n\n"
      "isnormal( HUGE_VALF)      : %i\n"
      "isnormal(-HUGE_VALF)      : %i\n"
      "isnormal( HUGE_VAL )      : %i\n"
      "isnormal(-HUGE_VAL )      : %i\n"
      "isnormal( HUGE_VALL)      : %i\n"
      "isnormal(-HUGE_VALL)      : %i\n"
      "isnormal( INFINITY)       : %i\n"
      "isnormal(-INFINITY)       : %i\n"
#ifdef NAN
      "isnormal( NAN)            : %i\n"
      "isnormal(-NAN)            : %i\n"
#else
      "isnormal( NAN)            : (macro NAN no está definida)\n"
      "isnormal(-NAN)            : (macro NAN no está definida)\n"
#endif
      "isnormal(+0.0F)           : %i\n"
      "isnormal(-0.0F)           : %i\n"
      "isnormal(+0.0)            : %i\n"
      "isnormal(-0.0)            : %i\n"
      "isnormal(+0.0L)           : %i\n"
      "isnormal(-0.0L)           : %i\n"
      "isnormal(+1.0F)           : %i\n"
      "isnormal(-1.0F)           : %i\n"
      "isnormal(+1.0)            : %i\n"
      "isnormal(-1.0)            : %i\n"
      "isnormal(+1.0L)           : %i\n"
      "isnormal(-1.0L)           : %i\n"
      "isnormal(+FLT_MIN)        : %i\n"
      "isnormal(-FLT_MIN)        : %i\n"
      "isnormal(+DBL_MIN)        : %i\n"
      "isnormal(-DBL_MIN)        : %i\n"
      "isnormal(+LDBL_MIN)       : %i\n"
      "isnormal(-LDBL_MIN)       : %i\n"
      "isnormal(+FLT_MIN  / 4.F) : %i\n"
      "isnormal(-FLT_MIN  / 4.F) : %i\n"
      "isnormal(+DBL_MIN  / 4.)  : %i\n"
      "isnormal(-DBL_MIN  / 4.)  : %i\n"
      "isnormal(+LDBL_MIN / 4.L) : %i\n"
      "isnormal(-LDBL_MIN / 4.L) : %i\n"
      "isnormal(+FLT_MAX)        : %i\n"
      "isnormal(-FLT_MAX)        : %i\n"
      "isnormal(+DBL_MAX)        : %i\n"
      "isnormal(-DBL_MAX)        : %i\n"
      "isnormal(+LDBL_MAX)       : %i\n"
      "isnormal(-LDBL_MAX)       : %i\n"
      "isnormal(+FLT_MAX  * 4.F) : %i\n"
      "isnormal(-FLT_MAX  * 4.F) : %i\n"
      "isnormal(+DBL_MAX  * 4.)  : %i\n"
      "isnormal(-DBL_MAX  * 4.)  : %i\n"
      "isnormal(+LDBL_MAX * 4.L) : %i\n"
      "isnormal(-LDBL_MAX * 4.L) : %i\n"
      "\n\n"
        , isnormal(HUGE_VALF)
        , isnormal(-HUGE_VALF)
        , isnormal(HUGE_VAL)
        , isnormal(-HUGE_VAL)
        , isnormal(HUGE_VALL)
        , isnormal(-HUGE_VALL)
        , isnormal(INFINITY)
        , isnormal(-INFINITY)

#ifdef NAN
        , isnormal(NAN)
        , isnormal(-NAN)
#endif /* (NAN) */

        , isnormal(0.0)
        , isnormal(-0.0)
        , isnormal(0.0)
        , isnormal(-0.0)
        , isnormal(0.0)
        , isnormal(-0.0)
        , isnormal(1.0)
        , isnormal(-1.0)
        , isnormal(1.0)
        , isnormal(-1.0)
        , isnormal(1.0)
        , isnormal(-1.0)
        , isnormal(FLT_MIN)
        , isnormal(-FLT_MIN)
        , isnormal(DBL_MIN)
        , isnormal(-DBL_MIN)
        , isnormal(LDBL_MIN)
        , isnormal(-LDBL_MIN)
        , isnormal(FLT_MIN  / 4.F)
        , isnormal(-FLT_MIN / 4.F)
        , isnormal(DBL_MIN  / 4.)
        , isnormal(-DBL_MIN / 4.)
        , isnormal(LDBL_MIN / 4.L)
        , isnormal(-LDBL_MIN / 4.L)
        , isnormal(FLT_MAX)
        , isnormal(-FLT_MAX)
        , isnormal(DBL_MAX)
        , isnormal(-DBL_MAX)
        , isnormal(LDBL_MAX)
        , isnormal(-LDBL_MAX)
        , isnormal(FLT_MAX  * 4.F)
        , isnormal(-FLT_MAX * 4.F)
        , isnormal(DBL_MAX  * 4.)
        , isnormal(-DBL_MAX * 4.)
        , isnormal(LDBL_MAX * 4.L)
        , isnormal(-LDBL_MAX * 4.L)
      );

  printf(
     "__________________________________________________________________________________\n\n"
      "Macros clasificadoras de valores de punto flotante.\n\n"
      "signbit(x): Da un entero distinto de 0 si es negativo el bit de signo\n"
      "            del punto flotante x.\n\n"
      "signbit( HUGE_VALF)      : %i\n"
      "signbit(-HUGE_VALF)      : %i\n"
      "signbit( HUGE_VAL )      : %i\n"
      "signbit(-HUGE_VAL )      : %i\n"
      "signbit( HUGE_VALL)      : %i\n"
      "signbit(-HUGE_VALL)      : %i\n"
      "signbit( INFINITY)       : %i\n"
      "signbit(-INFINITY)       : %i\n"
#ifdef NAN
      "signbit( NAN)            : %i\n"
      "signbit(-NAN)            : %i\n"
#else
      "signbit( NAN)            : (macro NAN no está definida)\n"
      "signbit(-NAN)            : (macro NAN no está definida)\n"
#endif
      "signbit(+0.0F)           : %i\n"
      "signbit(-0.0F)           : %i\n"
      "signbit(+0.0)            : %i\n"
      "signbit(-0.0)            : %i\n"
      "signbit(+0.0L)           : %i\n"
      "signbit(-0.0L)           : %i\n"
      "signbit(+1.0F)           : %i\n"
      "signbit(-1.0F)           : %i\n"
      "signbit(+1.0)            : %i\n"
      "signbit(-1.0)            : %i\n"
      "signbit(+1.0L)           : %i\n"
      "signbit(-1.0L)           : %i\n"
      "signbit(+FLT_MIN)        : %i\n"
      "signbit(-FLT_MIN)        : %i\n"
      "signbit(+DBL_MIN)        : %i\n"
      "signbit(-DBL_MIN)        : %i\n"
      "signbit(+LDBL_MIN)       : %i\n"
      "signbit(-LDBL_MIN)       : %i\n"
      "signbit(+FLT_MIN  / 4.F) : %i\n"
      "signbit(-FLT_MIN  / 4.F) : %i\n"
      "signbit(+DBL_MIN  / 4.)  : %i\n"
      "signbit(-DBL_MIN  / 4.)  : %i\n"
      "signbit(+LDBL_MIN / 4.L) : %i\n"
      "signbit(-LDBL_MIN / 4.L) : %i\n"
      "signbit(+FLT_MAX)        : %i\n"
      "signbit(-FLT_MAX)        : %i\n"
      "signbit(+DBL_MAX)        : %i\n"
      "signbit(-DBL_MAX)        : %i\n"
      "signbit(+LDBL_MAX)       : %i\n"
      "signbit(-LDBL_MAX)       : %i\n"
      "signbit(+FLT_MAX  * 4.F) : %i\n"
      "signbit(-FLT_MAX  * 4.F) : %i\n"
      "signbit(+DBL_MAX  * 4.)  : %i\n"
      "signbit(-DBL_MAX  * 4.)  : %i\n"
      "signbit(+LDBL_MAX * 4.L) : %i\n"
      "signbit(-LDBL_MAX * 4.L) : %i\n"
      "\n\n"
        , signbit(HUGE_VALF)
        , signbit(-HUGE_VALF)
        , signbit(HUGE_VAL)
        , signbit(-HUGE_VAL)
        , signbit(HUGE_VALL)
        , signbit(-HUGE_VALL)
        , signbit(INFINITY)
        , signbit(-INFINITY)

#ifdef NAN
        , signbit(NAN)
        , signbit(-NAN)
#endif /* (NAN) */

        , signbit(0.0)
        , signbit(-0.0)
        , signbit(0.0)
        , signbit(-0.0)
        , signbit(0.0)
        , signbit(-0.0)
        , signbit(1.0)
        , signbit(-1.0)
        , signbit(1.0)
        , signbit(-1.0)
        , signbit(1.0)
        , signbit(-1.0)
        , signbit(FLT_MIN)
        , signbit(-FLT_MIN)
        , signbit(DBL_MIN)
        , signbit(-DBL_MIN)
        , signbit(LDBL_MIN)
        , signbit(-LDBL_MIN)
        , signbit(FLT_MIN  / 4.F)
        , signbit(-FLT_MIN / 4.F)
        , signbit(DBL_MIN  / 4.)
        , signbit(-DBL_MIN / 4.)
        , signbit(LDBL_MIN / 4.L)
        , signbit(-LDBL_MIN / 4.L)
        , signbit(FLT_MAX)
        , signbit(-FLT_MAX)
        , signbit(DBL_MAX)
        , signbit(-DBL_MAX)
        , signbit(LDBL_MAX)
        , signbit(-LDBL_MAX)
        , signbit(FLT_MAX  * 4.F)
        , signbit(-FLT_MAX * 4.F)
        , signbit(DBL_MAX  * 4.)
        , signbit(-DBL_MAX * 4.)
        , signbit(LDBL_MAX * 4.L)
        , signbit(-LDBL_MAX * 4.L)
      );

#endif  /* (FLAG_INFINITY_OK) */
     
   getchar();
 
 }




[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización
Comentarios y Consultas

28 Enero, 2013, 03:52 am
Respuesta #27

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,739
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
27. Números complejos en C. Preliminares

   El estándar C99 otorga soporte para números complejos en el lenguaje C.
   Aquí haremos un breve repaso de la teoría básica de números complejos, y explicaremos someramente cómo se implementan en el lenguaje C.

   Matemáticamente, los números complejos se representan por pares de números reales \( (a, b) \), que se pueden interpretar como las coordenadas de un punto en el plano cartesiano.
   La 1er componente, \( a \), se llama parte real, y la 2da componente, \( b \), se llama parte imaginaria.
   Los números complejos cuya parte imaginaria es \( 0 \) se llaman reales puros,  mientras que aquellos cuya parte real es 0, se llaman imaginarios puros.

   La unidad imaginaria es el número imaginario puro \( (0, 1) \), que se denota brevemente con \( \color{blue}i \).

   Luego, una notación comunmente utilizada para los números complejos \( (a, b) \) es: \( a+b\color{blue}i \).

   Las reglas algebraicas de los números complejos son las mismas que ya conocemos para los números reales, con la única salvedad de que \( -1 \) tiene ahora una raíz cuadrada:

            \( {\color{blue}i}^2=-1. \)

   Un número complejo \( a+bi \) puede pensarse geométricamente como un vector cuyo punto de origen es el punto \( (0, 0) \) del plano, y cuyo vértice es \( (a, b) \).
   El segmento resultante tiene una longitud, que se llama módulo, y un ángulo respecto el eje de números reales puros positivos, expresado en radianes, que se llama Argumento.

Módulo de \( a+bi \): \( |a+bi| = \sqrt{a^2+b^2} \) (distancia euclidiana entre \( (0,0) \) y \( (a, b) \)).

Argumento de \( a+bi \): \( \arg(a+bi)= \angle(\textsf{eje real positivo}, \overrightarrow{{(0,0)-(a,b)}}) \).

   Denotemos \( r=|a+bi|, \theta=\arg(a+bi) \).
$.En ese caso, tenemos las siguientes relaciones:

            \( a+bi=r\cos \theta + i r\sen\theta. \)

   En la teoría de variable compleja se suele usar la notación exponencial:

            \( a+bi=re^{i\theta} \)

donde \( e^{i\theta} \) podemos pensarlo, para simplificar las cosas, como una abreviatura de:

            \( re^{i\theta}=r\cos\theta +{\color{blue} i}\, r\sen\theta. \)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Ahora pensemos cómo es posible implementar números complejos en el lenguaje C.
   Esto ha de hacerse a través de algún tipo de datos diseñado para números complejos.
   ¿Acaso habrá complejos con partes real e imaginaria enteras, de punto flotante, ambas?
   El estándar C99 define tipos de datos complejos de punto flotante. Y sólo eso.
   Las partes real e imaginaria se representarán con números de punto flotante reales, como los que ya vimos en los posts anteriores.
   ¿Puede que la parte real sea de tipo float, mientras que la parte imaginaria sea double?
   ¿O ambas componentes tienen que ser del mismo tipo?

   En cuanto a los valores infinitos, por cada ángulo \( \theta \) es posible obtener una semirrecta distinta que apunta a un infinito complejo en esa dirección.
   O sea que ya no se puede tener infinitos con un signo positivo o negativo, sino que, o bien se tiene un valor infinito por cada dirección del plano complejo, o bien se establece uno solo para todos los casos, que es lo que se hace en teoría de variable compleja cuando se define el infinito complejo.

   Todos estos detalles los estudiaremos en el post siguiente.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización
Comentarios y Consultas

29 Enero, 2013, 06:58 am
Respuesta #28

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,739
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
28. Números complejos en C. Estándar C99. Conversión de tipos

   En el estándar C99 se considera que los tipos de punto flotante se dividen en dos grupos: los reales y los complejos.

Por cada tipo de punto flotante real hay un correspondiente tipo de punto flotante complejo, y se les designa con la palabra _Complex. Así, los tipos complejos son estos:

float _Complex, double _Complex, long double _Complex.

   Se habla también del tipo de punto flotante real correspondiente a un tipo de punto flotante dado.
   Esto es sólo terminología, pero que ayuda a entendernos con precisión en algunas descripciones.
   Por ejemplo, el tipo real que corresponde a double _Complex es double, y el que corresponde a float es el mismo float (porque ya es de tipo real).

   Internamente, un dato de un tipo complejo se almacena en memoria como dos valores consecutivos del tipo real correspondiente. El primero de ellos es igual a la parte real del número complejo, y el segundo es igual a la parte imaginaria.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

¿Existen efectivamente los tipos complejos en toda implementación local?

   Eso no puede saberse a priori para "toda" implementación.
   El estándar considera casos en los que puede ocurrir que los tipos complejos no estén soportadas.
   En ese caso indica que el compilador no debe usar la palabra _Complex.
   Esta extraña situación puede darse sólo en implementaciones locales de las llamadas freestanding (se refiere a situaciones en que el programa no va a correr en un sistema operativo determinado, y en caso contrario se llaman hosted).

   Nosotros, por largo tiempo, no vamos a preocuparnos por las implementaciones freestanding,  así que, en particular, tendremos disponibles los tipos de punto flotante complejos.

   Aún así, aunque tengamos tipos complejos, no quiere decir que éstos tengan todas las características "deseables" (en algún sentido).
   El estándar C99 establece la definición de la macro __STDC_IEC_559_COMPLEX__, la cual está definida en caso de que la implementación local adhiera al apéndice G del documento del estándar C99.
   En caso contrario, no está definida.

   Cada vez que nos refiramos a las normas establecidas en ese apéndice G, lo anotaremos así: C99(ap.G).

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   ¿Cómo especificamos "a mano" valores complejos?
   Hay varios trucos que, en su día les enseñaré, pero por ahora digamos que, simplemente, "no se puede hacer así como así".
   Si queremos escribir un valor complejo como \( 7{.}34 + 147{.}22\; \color{blue}i \), podemos escribir sin problemas en lenguaje C las partes real e imaginaria \( 7{.}34 \) y \( 147{.}22 \), porque son números reales de punto flotante, que sabemos anotar.
   El problema está en que no hay modo de especificar manualmente la unidad imaginaria \( \color{blue}i \) .
   ¿Hay algún mecanismo para expresar la bendita \( \color{blue}i \) ?  >:(
   Para lograrlo hay que apelar a unas macros que están definidas en la librería <complex.h>. Allí se definen las macros siguientes:

_Complex_I: Representa una constante de tipo float _Complex, cuyo valor es 0.0F + 1.0F i, o sea, es igual a la unidad imaginaria i , aunque siempre en tipo float, tan sólo.

I: Es otra macro, que equivale a _Complex_I, o sea, es una forma más breve de generar la unidad imaginaria i .

   ¿Por qué dos macros para lo mismo?
   El meollo del asunto es que, como en versiones anteriores del lenguaje C no había tipos complejos, los programadores comunmente se los inventaban.
   Así que seguramente habrán definido sus propias versiones de I.
   Luego, si quieren compilar programas antiguos con compiladores nuevos, el resultado puede ser un desastre.
   Para evitar esos conflictos, un programador puede desactivar la macro I mediante #undef, así:

#undef I

   Ahora sí, si queremos expresar un número complejo, como el de nuestro ejemplo, tendríamos que escribir algo como esto:

7.34 + 147.22 * _Complex_I

o bien esto otro, que es equivalente:

7.34 + 147.22 * I

   La palabra _Complex tiene una sintaxis algo extraña, y esto se debe, otra vez, a los posibles problemas de compatibilidad con programas antiguos que pudieran tener definida su propia versión de complex.

   En la librería <complex.h> se define, sin embargo, la macro complex, que sirve como sinónimo de la palabra _Complex.

   Así, podemos escribir float complex y será lo mismo que si escribiéramos float _Complex.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Además, en <complex.h>, el estándar C99 prevee la posibilidad  :o de que se implemente un tipo de punto flotante adicional, que sólo se usaría para representar números imaginarios puros.
   Son los tipos imaginarios, y se indican con la palabra _Imaginary. Los nombres de los tipos son:

float _Imaginary, double _Imaginary, long double _Imaginary

   Asimismo se declara una macro imaginary, que equivale a _Imaginary.

   Y además se declara la macro _Imaginary_I, que es de nuevo la unidad imaginaria \( \color{blue}i \), aunque ahora tiene tipo float _Imaginary.

   La existencia de tipos imaginarios depende de la implementación local.
   Si la macro __STDC_IEC_559_COMPLEX__ está definida, entonces los tipos imaginarios (a través de la palabra _Imaginary) están disponibles, así como las macros imaginary, _Imaginary_I (en <complex.h>).

   En caso de que existan los tipos _Imaginary, la macro I pasa a ser, en realidad, equivalente a _Imaginary_I, que es de tipo float _Imaginary. Si no, equivale a _Complex_I, que es de tipo float _Complex.

   Un dato de tipo _Imaginary se almacena en memoria como el tipo real correspondiente.
   Por ejemplo, un valor de tipo float _Imaginary internamente se almacena como un float.

   ¿Y entonces cómo se hace para distinguir que uno es un número imaginario y el otro un número real?
   Como siempre, el tipo de datos de un valor dado, es una característica adicional que se guarda "por ahí".
   El C puede distinguir si se trata de valores reales o imaginarios, porque para eso es un lenguaje de alto nivel.  :P


   Para saber si los tipos _Imaginary están definidos en la implementación local, debemos consultar las especificaciones del compilador, en nuestro caso el GCC 4.7.1.

   Si queremos saber si las macros imaginary e _Imaginary están definidas, podemos consultar, ya sea verificando si la macro __STDC_IEC_559_COMPLEX__ está definida, o directamente (como me parece más recomandable en este caso), mediante #ifdef:

#ifdef imaginary
   #define DoesExists_imaginary "imaginary definida"
#else
   #define DoesExists_imaginary "imaginary no definida"
#endif


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

¿Qué pasa si para un número complejo la parte real es de un tipo y la parte imaginaria de otro?

   Por ejemplo, 2.0 + 1.59F * I tiene parte real de tipo double y parte imaginaria de tipo float.

   En ese caso, se ha de pasar por un proceso de conversión, tal que la parte con menos precisión se convierte al tipo de datos de la parte con mayor precisión.
   Así, si una parte es float, siempre se convertirá al tipo de la otra parte  ;) , y si una parte es long double, la otra parte se convertirá siempre a long double  ;) ;) ;) .

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Todo lo que hemos discutido ya para los tipos de puntos flotante reales, sirve para tipos complejos y tipos imaginarios.

   Por ejemplo, las reglas de redondeo, el significado de FLT_EVAL_METHOD, entre otras cuestiones.

   Así que, no hace falta que repitamos nada de toda esa teoría.
   Por dar sólo un ejemplo, el número FLT_MIN / 4.0F * I es un número complejo (o imaginario, según sea el tipo de I) que es subnormal en el rango de los valores de float _Complexfloat _Imaginary).

   Sin embargo, debemos mencionar aquí el tema de las conversiones entre tipos flotantes.
   Para tipos reales habíamos podido evitar introducir este tópico hasta ahora, pero como los tipos complejos obligan a convertir unos tipos en otros, si los tipos de las partes real e imaginaria son distintos, es algo que no podemos postergar más.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Reglas de conversión entre tipos flotantes (reales, complejos o imaginarios).

   Las siguientes reglas de conversión aplican a todos los tipos de punto flotante, tanto reales, complejos como imaginarios.

  • Si un float se convierte a double o a long double, su valor permanece sin cambios.

  • Si un double se convierte a long double, su valor permanece sin cambios.

  • Si un valor de tipo de punto flotante tuviese que convertirse a un tipo "menor", entonces:

    \( \bullet \)   Permanece sin cambios cuando es posible representarlo en forma exacta en el tipo "menor" (o sea, no se pierde precisión).

    \( \bullet \)   Si el valor se encuentra dentro del rango de valores, pero no es posible representar toda la precisión del tipo original, entonces se "recorta" de alguna manera (truncando o redondeando, con alguno de los métodos de redondeo que conocemos).
    Sin embargo, el modo en que este "recorte" se hace, depende de la implementación local.

    Lo más probable es que se use redondeo, y con el método especificado en FLT_ROUNDS, aunque esto no puede asegurarse desde las reglas del estándar C99.

  • Cuando un valor de tipo complejo o imaginario sufre una conversión, cada una de sus partes real e imaginaria, se convierten según las reglas especificadas para los tipos reales.

  • Las partes real e imaginaria siempre se convierten (automáticamente) al "mayor" de los tipos reales que ellas tienen.

  • Cuando un tipo real se convierte a un tipo complejo, la parte real permanece inalterada en valor y tipo, mientras que la parte imaginaria será un cero positivo o sin signo (del mismo tipo de la parte real).
  • Si un valor de tipo imaginario se convierte a un tipo complejo, la parte imaginaria permanece inalterada, mientras que la parte real será un será un cero positivo o sin signo (del mismo tipo de la parte imaginaria).
  • Si un valor de un tipo complejo se convierte a un tipo real, se conserva inalterada la parte real, en valor y tipo, y se descarta la parte imaginaria.
  • Si un valor de un tipo complejo se convierte a un tipo imaginario, se conserva inalterada la parte imaginaria, en valor y tipo, y se descarta la parte real.


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Tratamiento de valores infinitos y NaN en el caso complejo.

   La situación puede tornarse algo ambigua si la parte real es finita pero la parte imaginaria es infinita.
   ¿Qué se considera o se hace en casos como ése?

   En principio, lo que interpreto es que el estándar C99 no establece ninguna regla a priori.  ??? ??? ???
   O sea que puede entenderse cualquier cosa en caso de que se mezclen valores finitos con infinitos y/o con NaNs.
   La interpretación definitiva dependería de la implementación local.

Sin embargo, si está definida la macro __STDC_IEC_559_COMPLEX__ entonces podemos tener algunas certezas, tal como se especifica en las siguientes convenciones:

\( \bullet \)   Un dato complejo o imaginario con al menos una de sus partes (real y/o imaginaria) infinita, se considera un valor infinito. Esto es así incluso en el caso en que una de las partes (real o imaginaria) sea un NaN.

\( \bullet \)   Un valor complejo o imaginario se considera finito si ambas partes (real e imaginaria) son valores finitos (ni infinito ni NaN).

\( \bullet \)   Un valor complejo o imaginario se considera un cero si cada una de sus partes real e imaginaria es un cero.

   El C99 no dice nada de los valores NaN:'(
   Lo que yo imagino  ??? :o es que quizá  :-\ se considere un valor NaN a aquel cuyas partes real e imaginaria sean ambos un NaN.
   O quizá esto simplemente se deje sin especificar, a libre criterio de la implementación local.

   Análoga incertidumbre tenemos con la presencia de valores subnormales.

   Sin embargo, no todo está perdido.
   En partes más adelantadas del curso veremos otras herramientas de análisis del lenguaje C, que pueden ayudarnos a diagnosticar detalles incómodos como estos, con mayor precisión, y "reaccionar" de forma adecuada.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

¿Cómo se muestra un valor complejo en C99 con printf()?

   No hay un especificador % específico para eso, así que el método que hay es: apañarse.   8^)

   Para hacerlo de una manera rápida, y sin mucha teoría,  vamos a definir un par de macros que convierten todo a long double, y obtienen las partes real e imaginaria. (Hay otras formas mejores, pero por ahora las evitaré).

#define LD_Re(X) ((long double)(X))
#define LD_Im(X) ((long double)((X)*(- I)))


   Con eso, si ponemos la instrucción:

printf("%Le + %Le i", LD_Re(1.14 + 0.243 * I), LD_Im(1.14 + 0.243 * I) );

   Nos mostrará este resultado:

1.140000e+000 + 2.430000e-001 i

   O sea que la dichosa i la tuvimos que poner "a mano".  >:(
   ¿En qué se basa el truquito anterior, con el que obtuvimos las partes real e imaginaria por separado, para poder exhibirlas con printf()8^) 8^)
   La macro LD_Re lo que hace es convertir el argumento X al tipo de punto flotante real long double. Por las reglas de conversión, esto mantiene la precisión de la parte real, sin perder información, y también descarta la parte imaginaria.
   Con eso obtenemos la parte real, lista para "cocinarla" con printf(), previamente "sazonado" con %Le.
 
   Para la parte imaginaria, usamos álgebra: la parte imaginaria del complejo \( z = a+bi \) se obtiene tomando la parte real del número \( w = -i z \).
   A esto lo convertimos, como siempre, a long double, y listo.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   ¿Puede que la implementación local haya definido la unidad imaginaria I, pero no haya definido los tipos _Imaginary?
   La verdad es que sí, y de hecho GCC 4.7.1 hace más o menos lo que más le gusta en lo referido a los números complejos.
   O sea que les hice tragar toda una teoría sobre el estándar C99, y lo más probable es que se encuentren conque el compilador GCC no se ajuste a las especificaciones del estándar, sino que hace lo que quiere.

   En mi sistema funciona la constante I, pero no funcionan los tipos _Imaginary.
   Además, cada vez que uso la I, me pone una advertencia (Warning) de que las constantes imaginarias han sido definidas como una extensión  ??? ??? ??? de GCC, y que se aceptan por defecto.

   Habrá que investigar si estas extensiones pueden rodearse, evitarse, y si es posible obtener algo que se ajuste mejor al estándar C99.
   Por ahora da toda la sensación de que esto no ha sido debidamente implementado en nuestro compilador GCC 4.7.1.

   En particular, el estado actual de GCC 4.7.1 respecto a C99 en el tema de números complejos informa que:
\( \bullet \)   La librería <complex.h> de GCC 4.7.1 hace todo lo que el estándar C99 exige que "como mínimo" esté presente en una implementación local dada.
\( \bullet \)   Pero: aún no tiene soporte para C99(ap.G) (es decir, no adhiere a las especificaciones del Apéndice G del estándar, y por lo tanto tampoco define la macro __STDC_IEC_559_COMPLEX__, con las consecuencias que ello pueda traer).

:'( :'( :'( :'( :'( :'( :'(

   En particular, podremos constatar que los tipos _Imaginary no están definidos. 


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización
Comentarios y Consultas

18 Febrero, 2013, 10:50 pm
Respuesta #29

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,739
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
29. Números complejos en C. Programa de testeo

   Aquí vamos a realizar un pequeño programa que intentará determinar qué es lo que nuestra implementación local soporta en materia de números complejos.
   Vamos a usar la directiva de compilación condicional #if para verificar si ciertas macros están definidas o no, y esto nos dará la información requerida.

   Esta información sólo tiene sentido si hemos leído con atención las notas teóricas del post anterior, porque si no, no sabremos con precisión qué significado tendrán los resultados del programa.
   Debemos contrastar todos los resultados obtenidos para tener una comprensión cabal de lo que nuestra implementación local hace con los números complejos.

   Enseguida, como información adicional, consultaremos el tamaño de la constante I, comparándola con el tamaño en memoria de un float _Complex. Esto se hace, como hemos visto ya, con el operador sizeof().
   Si tienen el mismo tamaño, quiere decir que I se almacena con tipo float _Complex, pues abarca una parte real (igual a 0) y una parte imaginaria (en este caso igual a 1).
   Si el tamaño fuera distinto, es seguro que, en realidad, el tamaño es la mitad de un float _Complex, puesto que sólo puede ocurrir que I sea un  float _Imaginary, cuyo tamaño es el de 1 float.
   Por eso sólo consideramos dos casos en la sentencia printf() que analiza esta cuestión.
   En esa sentencia hacemos uso del operador condicional ()? :.

   Si no, lo lógico sería comparar directamente contra el tamaño de un float, a ver si coincide.
   Pero vemos que no es necesario.

   Además, astutamente hemos comparado contra el tamaño de un float _Complex. ¿Por qué? Porque el tipo de datos float _Complex siempre existirá en una implementación hosted, mientras que float _Imaginary no (no lo podemos asegurar).

   Una tercer cosa que haremos será "curiosear" en la definición de las macros asociadas a los números complejos, y ver cómo están implementadas.
   Esto lo llevaremos a cabo, como siempre, con nuestras macros DEB_(X) y DEB(X).

   Algunas macros nos darán como "resultado" a ¡ellas mismas!  :o
   No voy a entrar en muchos detalles técnicos, pero esto significa que, en realidad, la macro que estamos tratando de analizar, no está definida.
   Esto tiene que ver con algunas cosas que ya hemos explicado de cómo funciona el preprocesador y las directivas de compilación.

   Por último, mostraremos algunos valores complejos mediante printf().
   Para no escribir siempre lo mismo, lo empaquetaremos en una macro que se encargue de mostrar en pantalla valores de números complejos.

   Para esto, podríamos servirnos de las macros LD_Re y LD_Im, definidas en el post anterior. Pero no las vamos a usar, al fin y al cabo, pues existe el inconveniente de que esas macros sólo sirven para números complejos finitos, y pueden dar resultados erróneos en otros casos.

   Yo encontré un truquito  >:D para mostrar las partes real e imaginario directamente con printf(), pero no se los voy a enseñar porque ese "truquito" no está estandarizado, y ya hemos discutido varias veces por qué hay que evitar caer en esa situación.

   No quedará más remedio que "robar"  :-[ algo de teoría que corresponde a temas del "futuro" de este curso, para remediar esta situación, y poder ver correctamente los valores deseados.
   Usaremos las funciones creall() y cimagl(), que están definidas en la librería <complex.h>, y que sirven para obtener, respectivamente, las partes real e imaginaria de un número complejo.
   El resultado que devuelven es un número real, concretamente de tipo long double.

   Lo que haremos será construir una macro llamada PRINTF_COMPLEX(X), que toma un parámetro X, el cual se asume que es un dato de algún tipo complejo de punto flotante (si no, no hay garantías de que la macro funcione).
   La macro mostrará, mediante el uso de la función printf(), exactamente la expresión con números complejos que pretendemos mostrar en pantalla, y debajo se exhiben los valores tal como el compilador realmente "ve", mostrando las partes real e imaginaria en el formato \( a+bi \). La i se coloca en forma manual.
   La macro se define así:

#define PRINTF_COMPLEX(X) printf(#X "\n\t == %Le + %Le i\n", creall(X), cimagl(X));

   El programa completo es como sigue:

(Abrir para ver programa)

TestingComplex.c



#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h>

#define DEB_(X) #X
#define DEB(X) DEB_(X)

/* Como estamos compilando bajo una implementación "hosted", */
/* existen los tipos '_Complex', y también la macro _Complex_I. */

#define PRINTF_COMPLEX(X) printf(#X "\n\t == %Le + %Le i\n\n", creall(X), cimagl(X));

#ifdef __STDC_IEC_559_COMPLEX__
  #define Info1 "Hay soporte para números complejos según C99(ap.G)\n\n"
  #define Info2 "Hay soporte total para C99(ap.G)\n\n"
#else
  #define Info1 "No hay soporte para números complejos según C99(ap.G)\n\n"
  #define Info2 "Hay soporte parcial para C99(ap.G)\n\n"
#endif

#ifdef complex
  #define Info3 "La macro 'complex' está definida.\n\n"
#else
  #define Info3 "La macro 'complex' NO está definida.\n\n"
#endif

#ifdef imaginary
  #define Info4 "La macro 'imaginary' está definida.\n\n"
#else
  #define Info4 "La macro 'imaginary' NO está definida.\n\n"
#endif

#ifdef _Complex_I
  #define Info5 "La macro '_Complex_I' está definida.\n\n"
#else
  #define Info5 "La macro '_Complex_I' NO está definida.\n\n"
#endif

#ifdef _Imaginary_I
  #define Info6 "La macro '_Imaginary_I' está definida.\n\n"
#else
  #define Info6 "La macro '_Imaginary_I' NO está definida.\n\n"
#endif

#ifdef I
  #define Info7 "La macro 'I' está definida.\n\n"
#else
  #define Info7 "La macro 'I' NO está definida.\n\n"
#endif


int main(void) {
   
    system("CHCP 28591>NUL");
   
    printf("ANÁLISIS DE LAS CAPACIDADES DE NÚMEROS COMPLEJOS DEL COMPILADOR ACTUAL.\n\n"
           Info1 Info2 Info3 Info4 Info5 Info6 Info7);
   
    printf("¿La unidad imaginaria es de tipo 'float _Complex'? %s\n\n", (sizeof(I) == sizeof(float _Complex))? "SI": "NO" );
   
    printf("Definición interna de las macros:\n\n"
           "(Aquellas que muestran su mismo nombre, es que no están definidas).\n\n" );
    printf("complex   ?   " DEB(complex) "\n"
           "_Complex_I?   " DEB(I) "\n"           
           "imaginary?    " DEB(imaginary) "\n"
           "_Imaginary_I? " DEB(_Imaginary_I) "\n"
           "I?            " DEB(I) "\n"
        );
       
    printf("\n\nVisualización de algunos ejemplos de números complejos:\n\n");
   
    PRINTF_COMPLEX(I);
    PRINTF_COMPLEX(1.77L + 9.83L * I);
    PRINTF_COMPLEX(9.72F + 5.78L * I);
    PRINTF_COMPLEX(HUGE_VALL + 1.0 *  I);
    PRINTF_COMPLEX(HUGE_VALL *  I + 1.0);
    PRINTF_COMPLEX(HUGE_VALL * (1.0 +  1.0 * I));

    getchar();

}



[cerrar]

Por lo general soy reacio a mostrarles cuál es la salida que me da a mí cuando corro el programa, porque cabe la posibilidad de que obtengamos resultados distintos, ya que estamos poniendo a prueba los límites mismos de la implementación local.

   Sin embargo, esta vez haré una excepción, y les mostraré lo que el programa me dio a mí al correrlo, a fin de poder comentar algunos puntos:



ANÁLISIS DE LAS CAPACIDADES DE NÚMEROS COMPLEJOS DEL COMPILADOR ACTUAL.

No hay soporte para números complejos según C99(ap.G)

Hay soporte parcial para C99(ap.G)

La macro 'complex' está definida.

La macro 'imaginary' NO está definida.

La macro '_Complex_I' está definida.

La macro '_Imaginary_I' NO está definida.

La macro 'I' está definida.

¿La unidad imaginaria es de tipo 'float _Complex'? SI

Definición interna de las macros:

(Aquellas que muestran su mismo nombre, es que no están definidas).

complex   ?   _Complex
_Complex_I?   (0.0F + 1.0iF)
imaginary?    imaginary
_Imaginary_I? _Imaginary_I
I?            (0.0F + 1.0iF)


Visualización de algunos ejemplos de números complejos:

I
         == 0.000000e+000 + 1.000000e+000 i

1.77L + 9.83L * I
         == 1.770000e+000 + 9.830000e+000 i

9.72F + 5.78L * I
         == 9.720000e+000 + 5.780000e+000 i

HUGE_VALL + 1.0 * I
         == inf + 1.000000e+000 i

HUGE_VALL * I + 1.0
         == nan + inf i

HUGE_VALL * (1.0 + 1.0 * I)
         == inf + inf i




   Como se ve, nuestro compilador no tiene los tipos _Imaginary, y en particular la constante I es de tipo float _Complex.

   Hemos usado como ejemplo valores complejos que tienen en su parte real ó imaginaria, o en ambas, el valor long double HUGE_VALL, el cual sirve para generar infinitos (revisar teoría de valores "reales" de punto flotante).

   Al intentar mostrar estos valores con printf(), ocurren diversas situaciones.
   Por ejemplo, si la parte real es HUGE_VALL, entonces se muestran correctamente la parte real y la imaginaria.
   Pero si intentamos hacer que la parte imaginaria sea un HUGE_VALL, resulta que la parte real se "trastorna" y se nos muestra un NAN.

Estos comportamientos no son claros, ni nos dan certidumbre como programadores.

   Lo que debemos hacer es trabajar cuidadosamente con los infinitos y los NaNs, a fin de evitar errores.
   ¿Qué significa "trabajar cuidadosamente"?
   Quiere decir que, si el estándar o el compilador no nos aseguran un comportamiento determinado, no tenemos derecho a suponer que el programa se comportará de un modo u otro, lo que nos obligará a realizar siempre chequeos intermedios, comprobaciones en los sucesivos pasos de los cálculos, hasta estar seguro de que no han ocurrido situaciones extrañas o indeseables.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Otro detalle, que ya hemos apuntado anteriormente, es que cuando usamos la macro I, el compilador GCC nos pone un Warning, es decir, un mensaje que avisa que el compilador acepta por defecto constantes de tipo imaginario.
   Esto quiere decir que acepta constantes con "sufijo" i, admitiendo constantes de la forma \( a+bi \), sin necesidad de dar el rodeo al que nos obliga el estándar C99: a + b * _Complex_I.

   En particular, observamos en la salida del programa que la macro I está definida directamente como 0.0+1.0i, usando el famoso sufijo "i".
   Debemos recordar que el estándar C99 no admite un sufijo como ése, o al menos, no dice nada al respecto.

   Es por eso que yo, aunque sé que puedo escribir explícitamente constantes complejas con el sufijo i, :-X  ::) me hago el tonto y lo evito a propósito, porque no es estándar. 

   Sin embargo, no podemos ignorar  :o :o esta característica del compilador GCC 4.7, ya que si por accidente escribimos una constante numérica, con una i a su derecha, el compilador no "detectará" que se trata de un error, y la considerará una constante compleja válida, con lo cual el programa compilará.  :o :o :o :o
   No obstante, no es posible confundirse con una posible variable llamada "i", porque el sufijo "i" debe acompañar a una constante numérica para que el compilador la reconozca como tal.  :-\

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Con esto damos por terminada esta primera aproximación a los números de punto flotante, tanto reales como complejos, y podremos por fin pasar a otros temas.  :-\

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización
Comentarios y Consultas