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

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

05 Enero, 2013, 04:36 pm
Respuesta #10

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,394
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
10. Pare de sufrir: Alternativas a la consola CMD

   Hemos dicho ya lo fea e incómodo que es el shell de comandos CMD de DOS/Windows.
   Sin embargo, nos gustaría seguir utilizando una consola así de sencilla, porque es mucho más fácil hacer programas en C que ejecuten directamente en la consola.

   En este post vamos a estudiar la alternativa que ofrecen los emuladores de consolas.
   Son programas que hacen el mismo trabajo que la consola de Windows: ejecutan comandos del sistema, muestran resultados por la entrada/salida estándar de los programas en C, etc.
   La diferencia con la consola usual estriba en que tienen muchas más opciones: se puede configurar el fondo de la ventana con mayor variedad de colores, se puede elegir el tipo de letra entre los que aparecen normalmente en las ventanas de Windows (las TrueType y OpenType), y hasta es posible agregar una imagen como fondo de pantalla.
   Una de las cosas que más nos interesará es justamente esto del fondo de pantalla...

   Vamos a instalar el emulador ConEmu, que significa: Console Emulator. He aquí la guía adecuada, paso a paso, para que nuestros programas C funcionen correctamente desde Dev-Cpp.

  • Vayamos a la página de descarga de ConEmu:
    Página de descarga de ConEmu

  • Nos aparece el enlace de descarga siguiente:

    ConEmuSetup.130827.exe

  • Hacemos clic allí, y descargamos el correspondiente ejecutable.

  • Buscamos el archivo recién descargado en nuestra carpeta de descargas, y lo ejecutamos (haciendo doble clic en él).

  • Se ejecuta entonces el asistente de la instalación del programa.
       Hacer clic en siguiente (o next) para ir aceptando cada una de las opciones.
       (En una de las primeras ventanas nos aparece un acuerdo de licencia. Hacer clic abajo en la cajita blanca o checkbox, para aceptar. Luego proseguir con siguiente o next.)
       Finalmente clic en Install, y cuando termine, en Finish.
  • El programa está instalado. Ahora lo ejecutamos por primera vez (buscarlo en el menú de programas), haciendo clic o doble clic en su ícono.

  • Aparece ahora una ventana de configuración rápida (ConEmu Fast configuration).
       Hacemos clic para que quede marcada la primer casilla, que dice Single instance mode. Esto quiere decir que todos los programas que necesiten consola, se ejecutarán en una sola copia de esta.
       (Evitemos múltiples consolas, que puede ser muy liado).

  • Se abre ahora la consola propiamente dicha, mostrándose en forma muy similar a la consola corriente de DOS/Windows. Hacemos clic en la flechita que está justo al de uno de los íconos superiores a la derecha, el que está en color verde y tiene un signo +.

  • Enseguida aparece una ventana para Settigs (selecciones de opciones del programa). Luego, clic en Main.

  • Allí aprovechamos a configurar cómo lucrirá la consola.
       Por ejemplo, en Font yo seleccoiné Arial Unicode MS con tamaño 16.
       En Antialising seleccioné la opción Clear Type.
       Click 2 veces en Monospace, para desactivar la opción que hace que las letras se amontonen.

  • En Main/Size & Pos ponemos otros valores para Width y Height.

  • Vayamos a Features/Colors. Allí vamos abajo, donde dice Schemes. Abrimos el desplegable y seleccionamos el esquema de colores que más nos guste.
       Yo les recomiendo elegir: <Solarized light>.

  • Ahora vamos Features/Integration, y allí en Force ConEmu as default terminal.
       Activamos esa opción haciendo clic en la cajita blanca. Debajo figura una lista de programas que se supone que son capaces de lanzar una ventana de comandos.
       En principio sólo figura el explorer.exe.
       Agregamos, separado por el signo barra vertical | al programa devcpp.exe, así:

    explorer.exe|devcpp.exe

  • Finalmente, click en Save Settings....  Esto cierra la ventana de configuración.

  • Ahora ejecutamos wxDev-C++ y abrimos nuestro programa HolaMundo.c.
       Aplicamos Compilar y ejecutar sobre él, y veremos cómo se ejecuta en una nueva subventana de ConEmu.
    Tras terminar el programa, presionamos ENTER, y se cierra dicha subventana.

   Normalmente, en nuestros programas poníamos un "freno", como el comando getchar(), antes del final, para poder visualizar los resultados del programa antes de que se cierre la ventana de comandos.
   Sin embargo, mientras estemos usando ConEmu, esto no será necesario, ya que la consola misma se encarga de poner un mensaje avisando de espera, que nos invita a presionar las teclas ESC o ENTER para terminar, y así cerrar la subventana correspondiente.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Ahora vamos a insertar una imagen de fondo en la consola ConEmu. :)
   Es posible insertar archivos de imágenes de cualquier formato, pero estamos particularmente interesados en los mapas de bits: BMP.
   Así que vamos a crear una imange BMP sencilla, y la vamos a cargar en la consola ConEmu como fondo de pantalla.
   Hagamos lo siguiente:

  • Abrir el programa de dibujo que tengamos más a mano, como por ejemplo el Paint.

  • En el menú Imagen/Atributos..., seleccionar Ancho=500, Alto=500, teniendo cuidado de seleccionar Píxeles.
       En la opción Colores, seleccionar Colores (y no Blanco y Negro).
       Esto nos dará, obviamente, un recuadro de imagen de 512x512 píxeles para poner nuestro dibujo.
       Esta medida que les sugiero (fuertemente), es para que no ocupe toda la ventana de ConEmu, sino sólo una pequeña porción manejable, distinguible del resto.

  • Pintemos el fondo del dibujo con un color suave (un gris pálido, o el que prefieran).

  • Hagamos un óvalo o círculo en el centro, relleno con color blanco.

  • En el centro del círculo pongamos una letra C bien grande, en un color oscuro (a mí me gusta el azul oscuro).
       Esto es para hacer honor al lenguaje C, que estamos estudiando.

  • Guardémoslo con el nombre outputc.bmp.
       Procuremos guardarlo en la misma carpeta en que tenemos nuestros documentos, o bien donde tenemos nuestros proyectos C, para que podamos encontrarlo fácilmente.

  • Ahora vamos de nuevo a la ventana de comandos de ConEmu, al menú Setup Tasks... (que aparece tras abrir la flechita al lado del iconito verde arriba a la derecha de todo).
       Allí vamos a la opción Main. En el panel a la derecha marcamos la casilla que dice Background image (bmp, jpg, png).
       Abajo a la derecha hacemos clic en los puntos suspensivos, y buscamos nuestro dibujo recién hecho, outputc.bmp.
       Importante: Este dibujo no debemos cambiarlo de lugar, porque si no, ConEmu no lo encontrará.

  • Debajo a la derecha, en donde dice Placement, abrimos el desplegable y elegimos la opción UpRight.
       Esto nos posicionará la imagen en el extremo superior derecho de la consola ConEmu.
  • Finalmente hacemos clic de nuevo en Save settings, abajo a la derecha.


   Veremos que nuestro dibujo aparece en la consola, y justo donde acabamos de decir: abajo a la derecha.
   Si hacemos modificaciones a nuestro dibujo veremos que, tras guardarlas en Paint, automáticamente se actualizan los cambios en la imagen mostrada en consola.

   En la consola común y corriente de Windows no podíamos darnos el lujo de poner imágenes de fondo como acá.
   Ahora en ConEmu sí podemos hacerlo.
   Pero lo más importante es que usaremos esa imagen en algunos de nuestros programas...
   Por eso he insistido con ella. ;)



CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

08 Enero, 2013, 01:07 pm
Respuesta #11

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,394
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
11. Configuración de opciones del Compilador. Compatibilidad con C99.

(Actualizado: 20/Diciembre/2013)

   Hay muchísimas opciones que configuran el comportamiento del compilador.
   Aquí vamos a configurar el IDE para que las cosas nos funcionen lo mejor posible, de acuerdo al ya famoso estándar C99 que hemos elegido.

   La opción -std=c99 es la que adopta las especificaciones del estándar C99, lo mejor que le es posible al compilador GCC en su actual versión (que en diciembre de 2013 es la 4.8.2).
   Esto en teoría tendría que configurar nuestra distribución MinGW para que funcione con el estándar C99.

   Para esto, en nuestro IDE wxDevC++ vamos al menú Herramientos, clic en Opciones del Compilador.
   Allí observamos en la pestaña Compilador, en la sección Paquete de Compilador a configurar, que nuestro paquete seleccionado es MinGW.
   Si no lo fuera, entonces abrimos el desplegable y buscamos MinGW. Lo seleccionamos.

   Ahora sigamos los pasos explicados en la siguiente imagen, y comentados más abajo.


Paso 1. Una vez que eso está listo, hacemos clic en el botón que se encuentra a la derecha, que sirve para reinicializar todas las opciones del compilador a su estado original, tal como cuando hicimos la instalación del IDE.

Cada vez que hayamos jugado demasiado con las opciones del compilador, podemos restaurarlo al estado original mediante ese botón.

Paso 2. Nos va a saltar inmediatamente un cartel pidiendo confirmación. Hacemos clic en Yes o .

Paso 3. Nos fijamos en la misma pestaña, en la sección Compiler Command Line, en la primer casilla, que dice Añadir los siguientes comandos cuando llama al compilador. Allí escribimos nuestra opción preferida:
   
-std=c99

Paso 4. Listo. Ahora terminamos haciendo clic en Aceptar.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   He podido comprobar que con esta opción funcionan bien los tipos enteros y sus rangos, tal como se espera con el C99.
   De cualquier manera, recordemos que MinGW y su compilador GCC, si bien han hecho un buen esfuerzo por adaptarse al C99, están lejos de lograr una compatibilidad completa.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Aunque la opción -std=c99 esté seleccionada, puede que el compilador GCC todavía haya elegido mantener algunas características propias que no son estrictamente correctas en el estándar C99.
   Los desarrolladores de GCC consideran que esas reglas estrictas son pedantes, :P  y por tal motivo, si uno las quiere, tiene que agregar al lado de la opción -std=c99 la opción

-pedantic-errors

   Yo voy a tener configurado mi compilador de esa manera, y para estar todos iguales en este curso, les recomiendo que hagan lo mismo.
   Pero no es esto estrictamente necesario.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Hay una característica especial del estándar C99, la de los caracteres universales, que pueden usarse en identificadores. Este tema será explicado mucho más adelante.
   No obstante, para tener mayor compatibilidad con C99, podemos habilitar una opción del compilador GCC que habilita estos identificadores:

-fextended-identifiers

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

08 Enero, 2013, 07:26 pm
Respuesta #12

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,394
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
12. Uso básico de la función printf()

Aún no voy a explicar la función printf(), sino que sólo voy a contar lo mínimo necesario para nuestros propósitos inmediatos.

   1ro. vamos a explicar cómo poner prolijos títulos explicativos a nuestros programas.
   2do. vamos a indicar algunas opciones especiales destinadas a visualizar valores de números enteros.

1. Uso casero de printf(). Concatenación natural de strings.

   Nos interesa tan sólo presentar algunos datos en forma sencilla y rudimentaria en la pantalla.
   Para ello utilizaremos, como hasta ahora, la función printf().
   Como printf() está definida en la librería <stdio.h>, es necesario que la incluyamos en nuestro programa escribiendo:

#include <stdio.h>

   La función printf() escribe una string (cadena de caracteres) en la pantalla (mejor dicho, en la ventana de comandos).
   Como ya hemos visto, una string se indica encerrando sus caracteres entre comillas: "Hola mundo".
   Sin embargo, es posible pegar varias strings, o sea, concatenarlas, simplemente poniéndolas una al lado de la otra: Por ejemplo, el efecto de escribir:

"<Todo junto> se escribe separado,"     " mientras que <separado> se escribe todo junto."   ".....\n\n"

es el mismo resultado que si escribiéramos todo en una sola cadena entrecomillada, así:

"<Todo junto> se escribe separado, mientras que <separado> se escribe todo junto......\n\n"

Un ejemplo más molesto:

"Ho" "la m" "und" "o."

es equivalente a "Hola mundo."

   Por otra parte, debemos utilizar el caracter '\n' cada vez que queramos indicarle a printf() que debe realizar un salto de línea.
   Es decir, si escribimos algo como esto:

"Esto está arriba. "
"Esto también."

El compilador va a entender esto: "Esto está arriba.Esto también".

   Así que, si queremos que realmente haya un salto de línea, bien visible para el usuario, tenemos que colocar un '\n', así:

"Esto está arriba.\n"
"Esto ya no."


   Bueno, el compilador de todos modos lo va a juntar en una sola línea: "Esto está arriba.\nEsto ya no."
   Pero lo importante es que ahora, si ponemos eso como parámetro de la función printf(), cuando corramos el programa vamos a obtener el resultado deseado:

Esto está arriba.
Esto ya no.


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

2. Escribir prolijamente varias líneas de texto con una sola sentencia printf().

   Supongamos que queremos escribir un minitutorial que explique la escala musical en la pantalla.
   Queremos que nuestro programa produzca el siguiente texto, que ponemos en spoiler:

Minitutorial de la escala musical en el pentagrama


             Tutorial básico de música
             ======== ====== == ======
            
Una octava musical occidental está compuesta por 8 notas:

Do, Re, Mi, Fa, Sol, La, Si, Do.

Las notas van subiendo de tonalidad, desde la más grave a la más aguda.
El último Do se encuentra a una distancia tonal de 12 semitonos del primer Do.
La posición de las notas en el pentagrama, en clave de Sol, es como sigue:

------------------------------------------------------------------------------

------------------------------------------------------------------------------
                                                             O  (DO)
---------------------------------------------------O-- (SI) ------------------
                                            O (LA)
----------------------------------O-- (SOL) ----------------------------------
                           O (FA)
------------------O-- (MI) ---------------------------------------------------
           O (RE)  
--O-- (DO)

Hemos dibujado una 'Redonda' (O), que equivale a 4 tiempos de 'Negra',
en cada posición del pentagrama.
Como se puede apreciar, las notas Do y Re caen fuera del pentagrama.
En particular, es necesario escribir un pequeño trazo horizontal, extendiendo
en un renglón hacia abajo el pentagrama, para anotar una nota Do ahí.



[cerrar]

   El primer inconveniente que vamos a encontrar es que las letras con acentos á é í ó ú, con diéresiss ü y las eñes ñ, no se van a ver correctamente, debido a que en la ventana de comandos Windows usa una codificación de caracteres distinta que en el Explorer.

   Una solución parcial a esto sería, por ejemplo, escribir la letra seguida de un apóstrofe: a' e' i' o' u'.
   Sería un entendimiento entre nosotros de que eso "viene a significar" letras acentuadas.
   Para la ñ podemos convenir en reemplazarla por ejemplo con n~, o bien n#.[/color]
   Eso no es lo ideal. Sin embargo, para obtener los caracteres correctamente acentuados, tendríamos que estudiar cuestiones más avanzadas de la codificación de caracteres, así como el uso de caracteres Unicode en C. Esto no es simple, y obviamente lo voy a postergar bastante.
   Una mejor solución sería cambiar la página de códigos como explicamos en la sección 10.

   Otra dificultad aparente es que el minitutorial de música parece respetar ciertas reglas visuales de alineación.
   Para que todo quede bien alineado, tendríamos que poder editar de forma cómoda el texto mientras hacemos el programa en C. Y esto dentro de la función printf().
   Bueno, acá aprovechamos que al lenguaje C no le importa si nosotros decidimos continuar nuestra sentencia en las líneas de más abajo.
   Eso sí, debemos cuidar el entrecomillado, porque C lee una string de corrido en un renglón, y no la continúa en el renglón de abajo. (En realidad esto puede depender del compilador, no sé).
   Por último, para "bajar a la línea siguiente", recordar que SIEMPRE se debe colocar un caracter de salto de línea '\n', porque printf() no puede adivinar que queremos "bajar" al renglón de abajo.  ::)
   Queda así (abrir Spoiler):

Programita Minitutorial de Música
MiniTutorialMusica.c


#include <stdio.h>

int main(void) {
  printf(
    "             Tutorial ba'sico de mu'sica\n"  
    "             ======== ======= == =======\n"
    "\n"
    "Una octava musical occidental esta' compuesta por 8 notas:\n"
    "\n"
    "Do, Re, Mi, Fa, Sol, La, Si, Do.\n"
    "\n"
    "Las notas van subiendo de tonalidad, desde la ma's grave a la ma's aguda.\n"
    "El u'ltimo Do se encuentra a una distancia tonal de 12 semitonos del primer Do.\n"
    "La posicio'n de las notas en el pentagrama, en clave de Sol, es como sigue:\n"
    "\n"
    "------------------------------------------------------------------------------\n"
    "\n"
    "------------------------------------------------------------------------------\n"
    "                                                             O  (DO)\n"
    "---------------------------------------------------O-- (SI) ------------------\n"
    "                                            O (LA)\n"
    "----------------------------------O-- (SOL) ----------------------------------\n"
    "                           O (FA)\n"
    "------------------O-- (MI) ---------------------------------------------------\n"
    "           O (RE)\n"
    "--O-- (DO)\n"
    "\n"
    "Hemos dibujado una 'Redonda' (O), que equivale a 4 tiempos de 'Negra',\n"
    "en cada posicio'n del pentagrama.\n"
    "Como se puede apreciar, las notas Do y Re caen fuera del pentagrama.\n"
    "En particular, es necesario escribir un pequen~o trazo horizontal, extendiendo\n"
    "en un renglo'n hacia abajo el pentagrama, para anotar una nota Do ahi'.\n"
   ); /* Paréntesis que cierra función printf( ... ) */
  
}



[cerrar]

   La gracia de este ejemplo está en que es posible escribir grandes cantidades de texto mediante un sola sentencia printf().
   La edición usada en el programa es natural y cómoda, y refleja fielmente lo que va a salir después en pantalla.

¿Ya logré vendérselos?  >:D

   Obsérverse que aquellas líneas que van completamente en blanco, se han indicado con una string que simplemente dice: "\n", o sea, un salto de línea, sin texto adicional.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

09 Enero, 2013, 09:13 am
Respuesta #13

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,394
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
13. Testeando tipos de datos enteros de C (parte I)

Visualización de datos numéricos con printf()

   Para visualizar números enteros con printf() hay varias posibilidades.
   Como siempre, escribiremos una string con una frase informativa, y en el punto exacto donde deseamos que se visualice el dato numérico, colocamos uno de los modificadores siguientes:

%i   adecuado para denotar un (signed) int.
%u   adecuado para denotar un unsigned int.
%ji  adecuado para denotar un intmax_t.
%ju  adecuado para denotar un uintmax_t.

Por ejemplo, si queremos mostrar el resultado de un cálculo sencillo, haríamos así:

printf("%i", 50+11*77-19*4 );

   Como el entero típico en C es un int, hemos usado %i.
   Si trabajamos a conciencia con un dato de tipo unsigned int, podemos usar %u.
   Por ejemplo, podemos convertir rápidamente un número hexadecimal a decimal, así:

printf("%u", 0x5DF4);

   Eso funciona porque el lenguaje C almacena en memoria a los números con un mismo formato, sin importar que nosotros se lo hayamos indicado en base decimal, octal o hexadecimal.
   Así que a C le da lo mismo si ponemos 0x5DF4 o sus equivalentes en otras bases.
   Luego, la opción %u le instruye a printf() que ese número lo muestre en formato decimal.
   Además, le indica que considere el dato como unsigned int.
   Así que si ponemos ahí un número negativo, puede dar resultados sin sentido.

   Ahora bien. Los indicadores de formato %i y %u se mueven en el rango de int y unsigned int.
   Nosotros estamos interesados en poder abarcar números tan grandes como el rango máximo de valores aceptado por el estándar C99.
   A estos les corresponden los tipos de datos intmax_t y uintmax_t.
   Para que printf() muestre correctamente este tipo de números hay que intercalar una j detrás del signo %. Ejemplos:

  printf("%ji",   9000000000000 );
  printf("%ju", 18000000000000u );

   Notemos el uso del sufijo u en el número 18 trillones. Esto es necesario, porque el compilador intenta encajar en un entero de tipo signed a una constante sin sufijos, y el 18 trillones es demasiado grande para encajar. En cambio, si le avisamos con el sufijo u que es unsigned, todo funcionará bien.
   Un último ejemplo: Imaginemos que queremos ver cuál es el valor numérico asociado al caracter '@', hacemos:

printf("%i", '@');

   Mágicamente aparecerá el número 64, código ASCII de '@'.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Macros especiales (sencillas) para mostrar información interna del compilador.

   Ahora quisiéramos mostrar el valor de una de las constantes declaradas en <limits.h>, por ejemplo INT_MAX.
   Pero no nos conformemos con algo tan simple como:

printf("%i", INT_MAX);

   Procuremos lograr algo más informativo. Inclusive, podríamos intentar ver cómo está definida la macro INT_MAX internamente en el archivo <limits.h.

   Recordemos nuestras macros funcionales DEB_(X) y DEB(X), que habíamos definido unos posts atrás.
   La macro DEB_(X) toma el parámetro X, y si X es una macro primero intenta "desenrollar" toda la definición de la misma. Al resultado lo pone entre comillas, para hacer de eso una string.
   Pero DEB_(X) no nos sirve de mucho si X es una macro tipo función.
   La macro DEB(X) primero expande la definición completa del parámetro X (asumiendo que X es una macro), y recién después se le agregan las comillas.

   En cualquier caso, obtenemos una string, que podemos poner "justo al lado" de otras strings, a fin de concatenarlas.
   Por ejemplo, si escribimos:

"Esto es una " DEB(frase entrecomillada) " aunque no lo parezca."

el compilador lo convertirá en esto:

"Esto es una " "frase entrecomillada" " aunque no lo parezca."

   A su vez, por concatenación, eso equivale a esta sola string:

"Esto es una frase entrecomillada aunque no lo parezca."

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   ¿Qué ocurriría si escribimos lo siguiente?

            "....................Tipo: " "signed int"    "\n"
            "..................Limite: " "superior"  "\n"
            "............Nombre macro: " "INT_MAX"      "\n"
            "...............Expande a: " DEB(INT_MAX) "\n"
            "...Lenguaje C interpreta: " "%ji" "\n"
            "\n"


   Si en nuestro sistema INT_MAX está definida en hexadecimal como 0x7FFF, entonces DEB(INT_MAX) es la string "0x7FFF".
   Luego de esto, todas las strings se concatenan, y dan esto:

"....................Tipo: signed int\n..................Limite: superior\n............Nombre macro: INT_MAX\n...............Expande a: 0x7FFF\n...Lenguaje C interpreta: %ji\n\n"

¿Para qué hacer esto, por Dios?  :o :o :o :o

   Bueno, la intención es poner eso en un printf(...).
   Ahora, el modificador %ji nos permitiría colocar ahí un valor int.
   Este valor será, claro está, la constante INT_MAX que estamos estudiando.
   Así, nuestra sentencia completa printf(), con todas las strings de arriba, y con el valor INT_MAX pasado como parámetro, tendrán este aspecto:

       printf(
            "....................Tipo: " "signed int"    "\n"
            "..................Limite: " "superior"  "\n"
            "............Nombre macro: " "INT_MAX"      "\n"
            "...............Expande a: " DEB(INT_MAX) "\n"
            "...Lenguaje C interpreta: " "%ji" "\n"
            "\n", INT_MAX
         );


   Si corremos un programa con esa sentencia, el resultado en pantalla será esto:

....................Tipo: signed int
..................Limite: superior
............Nombre macro: INT_MAX
...............Expande a: 0x7FFF
...Lenguaje C interpreta: 32767


   Primero se ha mostrado el tipo de entero que se pretende analizar.
   Luego se indica cuál es el límite que se analiza, si el inferior o el superior.
   (Si el rango de valores de un tipo es \( r\leq x \leq s \), \( r \) es el límite inferior, y \( s \) el límite superior).
   Por fin viene el nombre de la macro pertinente, en la que está declarado el valor del límite (inferior o superior) asociado al tipo entero analizado.
   Debajo viene la manera en que esa macro ha sido definida en las librerías, ya sea en <limits.h> o <stdint.h>.
   Por último, aparece por fin el valor numérico concreto de la macro estudiada.

   En el ejemplo, el 32767 apareció porque fue insertado en la posición donde se halla %ji en la cadena de formato para el printf().

Aclaración: Seguramente no aparecerá 0x7FFF al correr el programa, ni tampoco 32767. Lo que aparezca ahí depende enteramente de nuestro compilador y nuestro sistema.

   Al parecer, hemos obtenido información concreta e interesante de la macro INT_MAX, y además la hemos mostrado en pantalla de un modo bastante comprensible para el usuario.
   A propósito se ha separado el printf() en 6 líneas, a fin de hacer coincidir más o menos a ojo con las 6 líneas "de verdad" que esperamos obtener al ejecutar el programa.
   Sin embargo, si queremos repetir este proceso para todas las constantes de <limits.h>, o incluso también para las de <stdint.h>, resultaría muy engorroso intentar volver a escribir todos esos comandos printf(), con esas cadenas confusamente escritas.
   Lo que necesitamos urgentemente es una abreviatura clara, y correctamente escrita.
   Esto en C se puede llevar a cabo, claro está, con las famosas macros.
   Trataremos de hacer una macro prolija que nos resuelva el problema.

   Supongamos que nuestra macro se llamará ANALISIS_MACRO, y que tendrá varios parámetros: TIPO, LIMITE, M, FORMATO, los cuales representan esta información:

TIPO:    Es el tipo de entero a analizar. Se provee "a mano".
LIMITE:  Se refiere a si se presentarán datos del límite superior o inferior del tipo entero en cuestión.
M:       Es el nombre de la macro que corresponde al caso de estudio.
FORMATO: Indica si printf() debe asegurar un formato para tipo signed o unsigned.


   Ahora, en vez de signed int, superior, INT_MAX, y %ji, tenemos algo genérico, denotado con los parámetros TIPO, LIMITE, M, FORMATO, así:

El 1er renglón:

           "....................Tipo: " "signed int"    "\n"

tendríamos que escribirlo así:

           "....................Tipo: " TIPO    "\n"

El 2do renglón:

           "..................Limite: " "superior"  "\n"

pasa a ser ahora:

           "..................Limite: " LIMITE  "\n"

El 3er renglón:

           "............Nombre macro: " "INT_MAX"      "\n"

tendríamos que escribirlo así:

           "............Nombre macro: " #M   "\n"

Recordemos que el operador # realiza un "entrecomillado" del parámetro de una macro, convirtiéndolo en una string.

El 4to renglón seria esto:

           "...............Expande a: " DEB(M) "\n"

La 5ta línea es la que tiene el formato %ji.
   Ese formato es adecuado para mostrar enteros signed.
   Pero si queremos enteros unsigned necesitamos %ju.

   A fin de poder elegir en cada caso el formato que más nos convenga, lo hacemos a través del parámetro FORMATO, así:

           "...Lenguaje C interpreta: "  FORMATO    "\n"

   Finalmente, como las strings ocupan varias líneas, tenemos que usar el signo \ para indicarle a la macro ANALISIS_MACRO que la definición es larga, y continúa en la línea siguiente.
   El resultado final será esto:

#define ANALISIS_MACRO(TIPO, LIMITE, M, FORMATO)       \
            "....................Tipo: " TIPO     "\n" \
            "..................Limite: " LIMITE   "\n" \
            "............Nombre macro: " #M       "\n" \
            "...............Expande a: " DEB(M)   "\n" \
            "...Lenguaje C interpreta: " FORMATO  "\n" \
            "\n"

   Ahora, la invocación de la función printf() puede hacerse en forma más concisa y abreviada, usando la macro, así:

printf( ANALISIS_MACRO("signed int", "superior", INT_MAX,  "%ji" ), INT_MAX);

   Uff, eso es más conciso, y basta compilar el código para verificar que realmente hace el trabajo.
   Pero antes vamos a hacer un cambio más.

   Ciertamente, aunque sabemos qué son %ji y %ju, no se ven muy prolijos. De hecho, no resultan muy informativos.

   Si alguien mira nuestro programa de un golpe de vista, no podrá entender el significado de esos jeroglíficos, puestos en ese lugar.
   Se necesita algo más autoexplicativo.

En general, hacer programas autoexplicativos se considera parte de una buena documentación del programa en sí mismo.

   Así que antes definiremos un par de constantes con un nombre significativo:

#define FORMAT_unsigned "%ju"
#define FORMAT_signed   "%ji"

Ahora, la sentencia la haremos así:

printf( ANALISIS_MACRO("signed int", "superior", INT_MAX,  FORMAT_signed ), INT_MAX);

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Quisiera decirles que ya está todo listo para nuestro programa.
   Pero hay un terrible problema.  :-X

printf() va a mostrar erróneamente la mayoría de los números.  >:(  :banghead: :banghead:

   Esto se debe a que los modificadores % de la cadena de formato de printf() son muy quisquillosos, y sólo aseguran buenos resultados si uno pone el tipo de dato justo que espera ahí.
   Por ejemplo, %ji tiene que ser un intmax_t, y no otra cosa, porque no se sabe qué resultado puede dar.

   Para remediar esto hay dos caminos posibles:

(a) Explicar en profundidad los detalles de las cadena de formato de printf().
(b) Convertir todos los números a los mayores tipos enteros admisibles en el sistema, o sea, a intmax_t y uintmax_t.

   La opción (a) nos llevaría mucho tiempo, como para explicarlo en este justo momento.
   La opción (b) requiere introducir un poco de teoría que también corresponde a temas futuros. Pero es más simple.

   Para convertir unos tipos de datos en otros, a la fuerza, según la voluntad del programador, en C se realizan operaciones casting (moldear).
   Sin entrar en muchos detalles, digamos que vamos a utilizar dos castings, uno para el caso de tipos unsigned, y otro para tipos que son signed o que no se sabe a priori si son signed o no (como le ocurre al tipo char).

   Por ejemplo, para SHRT_MAX, que corresponde a signed short int, pondríamos:

((intmax_t) (SHRT_MAX))

   Para que esto no se vuelva demasiado tedioso, pondremos abreviaturas de nuevo, definiendo macros adecuadas, así:

#define SCAST(X) ((intmax_t) (X))
#define UCAST(X) ((uintmax_t) (X))

   Obviamente, la primera macro es para convertir enteros signed, y la 2da para enteros unsigned.

   Ahora, por fin, tenemos el modo definitivo en que vamos a obtener y/o generar información acerca del límite superior del tipo signed int:

printf( ANALISIS_MACRO("signed int", "superior", INT_MAX,  FORMAT_signed ), SCAST(INT_MAX) );

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Creo que la mejor explicación que puedo dar es el programa mismo.
   Basta copiarlo a vuestro IDE y correrlo ahí.
   El programa mismo explicará lo que va haciendo.
   Pueden copiar y pegar desde el siguiente spoiler, o bien descargarlo del archivo adjunto a este post.

Disculpa anticipada:

(1) El archivo es muuuuy monótono, y se hace largo en su monotonía.  :'( :'(
(2) Habrá quienes piensen que el programa TestingIntegers1.c está hecho por un cavernícola. :banghead:  :laugh:
      ¿Podría ser más breve, conciso, hecho con mejores herramientas, y con más inteligencia? La respuesta es sí a todas esas preguntas. Pero este programa tiene la virtud de que se entiende, y sólo utiliza los pocos conocimientos que tenemos hasta ahora.  8^)

Programa para testear rangos de tipos enteros en C


   
/* TestingIntegers1.c   */
/* ==================   */
/*                      */
/* Autor: Argentinator  */
/* 09/Enero/2013        */
/*                      */
/* Curso de C online    */
/*                      */
/* http://rinconmatematico.com/foros/index.php?topic=64835.msg260248#msg260248 */
/* --------------------------------------------------------------------------- */

#include <stdio.h>
#include <limits.h>
#include <stdint.h>

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

#define ANALISIS_MACRO(TIPO, LIMITE, M, FORMATO)       \
            "....................Tipo: " TIPO     "\n" \
            "..................Limite: " LIMITE   "\n" \
            "............Nombre macro: " #M       "\n" \
            "...............Expande a: " DEB(M)   "\n" \
            "...Lenguaje C interpreta: " FORMATO  "\n" \
            "\n"
  

#define FORMAT_unsigned "%ju"
#define FORMAT_signed   "%ji"

#define SCAST(X) ((intmax_t) (X))
#define UCAST(X) ((uintmax_t) (X))

int main (void) {
   printf(  "\n"
            "______________________________________________________________\n"
            "\n"
            "Ana'lisis de tipos de datos enteros (unsigned y signed).\n"
            "\n______________________________________________________________\n"
            "\n"
            "1ra. Parte: Tipos ba'sicos unsigned.\n"
            "===========\n"
            "\n"
        );  /* Cerramos paréntesis de printf(...) */
      
  
   printf( ANALISIS_MACRO("unsigned char",          "superior", UCHAR_MAX,  FORMAT_unsigned), UCAST(UCHAR_MAX) );
   printf( ANALISIS_MACRO("unsigned short int",     "superior", USHRT_MAX,  FORMAT_unsigned), UCAST(USHRT_MAX) );
   printf( ANALISIS_MACRO("unsigned int",           "superior", UINT_MAX,   FORMAT_unsigned), UCAST(UINT_MAX) );
   printf( ANALISIS_MACRO("unsigned long int",      "superior", ULONG_MAX,  FORMAT_unsigned), UCAST(ULONG_MAX) );
   printf( ANALISIS_MACRO("unsigned long long int", "superior", ULLONG_MAX, FORMAT_unsigned), UCAST(ULLONG_MAX) );
  
   getchar();
    
   printf(  "\n"
            "2da. Parte: Tipo char.\n"
            "===========\n"
            "\n"
        );  

   printf( ANALISIS_MACRO("char", "inferior", CHAR_MIN,  FORMAT_signed), SCAST(CHAR_MIN) );
   printf( ANALISIS_MACRO("char", "superior", CHAR_MAX,  FORMAT_signed), SCAST(CHAR_MAX) );
  
   getchar();

   printf(  "\n"
            "3ra. Parte: Tipos ba'sicos signed.\n"
            "===========\n"
            "\n"
        );  

   printf( ANALISIS_MACRO("signed char",          "inferior", SCHAR_MIN, FORMAT_signed), SCAST(SCHAR_MIN) );
   printf( ANALISIS_MACRO("signed char",          "superior", SCHAR_MAX, FORMAT_signed), SCAST(SCHAR_MAX) );
   printf( ANALISIS_MACRO("signed short int",     "inferior", SHRT_MIN,  FORMAT_signed), SCAST(SHRT_MIN) );
   printf( ANALISIS_MACRO("signed short int",     "superior", SHRT_MAX,  FORMAT_signed), SCAST(SHRT_MAX) );
   printf( ANALISIS_MACRO("signed int",           "inferior", INT_MIN,   FORMAT_signed), SCAST(INT_MIN) );
   printf( ANALISIS_MACRO("signed int",           "superior", INT_MAX,   FORMAT_signed), SCAST(INT_MAX) );
   printf( ANALISIS_MACRO("signed long int",      "inferior", LONG_MIN,  FORMAT_signed), SCAST(LONG_MIN) );
   printf( ANALISIS_MACRO("signed long int",      "superior", LONG_MAX,  FORMAT_signed), SCAST(LONG_MAX) );
   printf( ANALISIS_MACRO("signed long long int", "inferior", LLONG_MIN, FORMAT_signed), SCAST(LLONG_MIN) );
   printf( ANALISIS_MACRO("signed long long int", "superior", LLONG_MAX, FORMAT_signed), SCAST(LLONG_MAX) );

   getchar();
  
   printf(  "\n"
            "4ta. Parte: Tipos de longitud fija (unsigned).\n"
            "===========\n"
            "\n"
        );  

   printf( ANALISIS_MACRO("uint8_t",  "superior", UINT8_MAX,  FORMAT_unsigned), UCAST(UINT8_MAX) );
   printf( ANALISIS_MACRO("uint16_t", "superior", UINT16_MAX, FORMAT_unsigned), UCAST(UINT16_MAX) );
   printf( ANALISIS_MACRO("uint32_t", "superior", UINT32_MAX, FORMAT_unsigned), UCAST(UINT32_MAX) );
   printf( ANALISIS_MACRO("uint64_t", "superior", UINT64_MAX, FORMAT_unsigned), UCAST(UINT64_MAX) );
  
   getchar();
  
   printf(  "\n"
            "5ta. Parte: Tipos de longitud fija (signed).\n"
            "===========\n"
            "\n"
        );  

   printf( ANALISIS_MACRO("int8_t",  "inferior", INT8_MIN,  FORMAT_signed), SCAST(INT8_MIN) );
   printf( ANALISIS_MACRO("int8_t",  "superior", INT8_MAX,  FORMAT_signed), SCAST(INT8_MAX) );
   printf( ANALISIS_MACRO("int16_t", "inferior", INT16_MIN, FORMAT_signed), SCAST(INT16_MIN) );
   printf( ANALISIS_MACRO("int16_t", "superior", INT16_MAX, FORMAT_signed), SCAST(INT16_MAX) );
   printf( ANALISIS_MACRO("int32_t", "inferior", INT32_MIN, FORMAT_signed), SCAST(INT32_MIN) );
   printf( ANALISIS_MACRO("int32_t", "superior", INT32_MAX, FORMAT_signed), SCAST(INT32_MAX) );
   printf( ANALISIS_MACRO("int64_t", "inferior", INT64_MIN, FORMAT_signed), SCAST(INT64_MIN) );
   printf( ANALISIS_MACRO("int64_t", "superior", INT64_MAX, FORMAT_signed), SCAST(INT64_MAX) );
  
   getchar();
  
   printf(  "\n"
            "6ta. Parte: Tipos de longitud mi'nima prefijada (unsigned).\n"
            "===========\n"
            "\n"
        );  

   printf( ANALISIS_MACRO("uint_least8_t",  "superior", UINT_LEAST8_MAX,  FORMAT_unsigned), UCAST(UINT_LEAST8_MAX) );
   printf( ANALISIS_MACRO("uint_least16_t", "superior", UINT_LEAST16_MAX, FORMAT_unsigned), UCAST(UINT_LEAST16_MAX) );
   printf( ANALISIS_MACRO("uint_least32_t", "superior", UINT_LEAST32_MAX, FORMAT_unsigned), UCAST(UINT_LEAST32_MAX) );
   printf( ANALISIS_MACRO("uint_least64_t", "superior", UINT_LEAST64_MAX, FORMAT_unsigned), UCAST(UINT_LEAST64_MAX) );
  
   getchar();
  
   printf(  "\n"
            "7ma. Parte: Tipos de longitud mi'nima prefijada (signed).\n"
            "===========\n"
            "\n"
        );  

   printf( ANALISIS_MACRO("int_least8_t",  "inferior", INT_LEAST8_MIN,  FORMAT_signed), SCAST(INT_LEAST8_MIN) );
   printf( ANALISIS_MACRO("int_least8_t",  "superior", INT_LEAST8_MAX,  FORMAT_signed), SCAST(INT_LEAST8_MAX) );
   printf( ANALISIS_MACRO("int_least16_t", "inferior", INT_LEAST16_MIN, FORMAT_signed), SCAST(INT_LEAST16_MIN) );
   printf( ANALISIS_MACRO("int_least16_t", "superior", INT_LEAST16_MAX, FORMAT_signed), SCAST(INT_LEAST16_MAX) );
   printf( ANALISIS_MACRO("int_least32_t", "inferior", INT_LEAST32_MIN, FORMAT_signed), SCAST(INT_LEAST32_MIN) );
   printf( ANALISIS_MACRO("int_least32_t", "superior", INT_LEAST32_MAX, FORMAT_signed), SCAST(INT_LEAST32_MAX) );
   printf( ANALISIS_MACRO("int_least64_t", "inferior", INT_LEAST64_MIN, FORMAT_signed), SCAST(INT_LEAST64_MIN) );
   printf( ANALISIS_MACRO("int_least64_t", "superior", INT_LEAST64_MAX, FORMAT_signed), SCAST(INT_LEAST64_MAX) );
  
   getchar();
  
   printf(  "\n"
            "8va. Parte: Tipos ra'pidos de longitud mi'nima prefijada (unsigned).\n"
            "===========\n"
            "\n"
        );  

   printf( ANALISIS_MACRO("uint_fast8_t",  "superior", UINT_FAST8_MAX,  FORMAT_unsigned), UCAST(UINT_FAST8_MAX) );
   printf( ANALISIS_MACRO("uint_fast16_t", "superior", UINT_FAST16_MAX, FORMAT_unsigned), UCAST(UINT_FAST16_MAX) );
   printf( ANALISIS_MACRO("uint_fast32_t", "superior", UINT_FAST32_MAX, FORMAT_unsigned), UCAST(UINT_FAST32_MAX) );
   printf( ANALISIS_MACRO("uint_fast64_t", "superior", UINT_FAST64_MAX, FORMAT_unsigned), UCAST(UINT_FAST64_MAX) );
  
   getchar();
  
   printf(  "\n"
            "9na. Parte: Tipos ra'pidos de longitud mi'nima prefijada (signed).\n"
            "===========\n"
            "\n"
        );  

   printf( ANALISIS_MACRO("int_fast8_t",  "inferior", INT_FAST8_MIN,  FORMAT_signed), SCAST(INT_FAST8_MIN) );
   printf( ANALISIS_MACRO("int_fast8_t",  "superior", INT_FAST8_MAX,  FORMAT_signed), SCAST(INT_FAST8_MAX) );
   printf( ANALISIS_MACRO("int_fast16_t", "inferior", INT_FAST16_MIN, FORMAT_signed), SCAST(INT_FAST16_MIN) );
   printf( ANALISIS_MACRO("int_fast16_t", "superior", INT_FAST16_MAX, FORMAT_signed), SCAST(INT_FAST16_MAX) );
   printf( ANALISIS_MACRO("int_fast32_t", "inferior", INT_FAST32_MIN, FORMAT_signed), SCAST(INT_FAST32_MIN) );
   printf( ANALISIS_MACRO("int_fast32_t", "superior", INT_FAST32_MAX, FORMAT_signed), SCAST(INT_FAST32_MAX) );
   printf( ANALISIS_MACRO("int_fast64_t", "inferior", INT_FAST64_MIN, FORMAT_signed), SCAST(INT_FAST64_MIN) );
   printf( ANALISIS_MACRO("int_fast64_t", "superior", INT_FAST64_MAX, FORMAT_signed), SCAST(INT_FAST64_MAX) );
  
   getchar();
  
   printf(  "\n"
            "10ma. Parte: Tipos ma'ximos y asociados a punteros.\n"
            "============\n"
            "\n"
        );  

   printf( ANALISIS_MACRO("uintmax_t", "superior", UINTMAX_MAX, FORMAT_unsigned), UCAST(UINTMAX_MAX) );
   printf( ANALISIS_MACRO("intmax_t",  "inferior", INTMAX_MIN,  FORMAT_signed),   SCAST(INTMAX_MIN) );
   printf( ANALISIS_MACRO("intmax_t",  "superior", INTMAX_MAX,  FORMAT_signed),   SCAST(INTMAX_MAX) );
   printf( ANALISIS_MACRO("uintptr_t", "superior", UINTPTR_MAX, FORMAT_unsigned), UCAST(UINTPTR_MAX) );
   printf( ANALISIS_MACRO("intptr_t",  "inferior", INTPTR_MIN,  FORMAT_signed),   SCAST(INTPTR_MIN) );
   printf( ANALISIS_MACRO("intptr_t",  "superior", INTPTR_MAX,  FORMAT_signed),   SCAST(INTPTR_MAX) );
  
   getchar();
  
   printf(  "\n"
            "11va. Parte: Tipos especiales.\n"
            "============\n"
            "\n"
        );  

   printf( ANALISIS_MACRO("size_t", "superior",       SIZE_MAX,       FORMAT_unsigned), UCAST(SIZE_MAX) );
   printf( ANALISIS_MACRO("ptrdiff_t", "inferior",    PTRDIFF_MIN,    FORMAT_signed),   SCAST(PTRDIFF_MIN) );
   printf( ANALISIS_MACRO("ptrdiff_t", "superior",    PTRDIFF_MAX,    FORMAT_signed),   SCAST(PTRDIFF_MAX) );
   printf( ANALISIS_MACRO("wchar_t", "inferior",      WCHAR_MIN,      FORMAT_signed),   SCAST(WCHAR_MIN) );
   printf( ANALISIS_MACRO("wchar_t", "superior",      WCHAR_MAX,      FORMAT_signed),   SCAST(WCHAR_MAX) );
   printf( ANALISIS_MACRO("wint_t", "inferior",       WINT_MIN,       FORMAT_signed),   SCAST(WINT_MIN) );
   printf( ANALISIS_MACRO("wint_t", "superior",       WINT_MAX,       FORMAT_signed),   SCAST(WINT_MAX) );
   printf( ANALISIS_MACRO("sig_atomic_t", "inferior", SIG_ATOMIC_MIN, FORMAT_signed),   SCAST(SIG_ATOMIC_MIN) );
   printf( ANALISIS_MACRO("sig_atomic_t", "superior", SIG_ATOMIC_MAX, FORMAT_signed),   SCAST(SIG_ATOMIC_MAX) );
  
}


[cerrar]

 ;D


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

10 Enero, 2013, 09:30 pm
Respuesta #14

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,394
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
14. Testeando tipos de datos enteros de C (parte II)

   Ahora vamos a hacer un programa que muestre información sobre los rangos de valores que toma cada tipo entero. Queremos que haga esto:

(i)   Por cada tipo de datos entero, mostrar el  rango de valores que toma en nuestra implementación local, o sea, el rango de valores admitido por el compilador en nuestro sistema.
(ii)  Luego mostrar el rango de valores que el estándar exige que siempre esté.
(iii) Finalmente mostrar una línea comparando ambos rangos, a fin de visualizar si nuestro compilador es compatible con el estándar.

Como nos quedará algo extenso, invertiremos más de un post en esto.

   Esta vez voy a preferir otro método de exposición. Voy a partir del resultado que se desea obtener, y a partir de ahí se irá armando el programa.
   De nuevo se usarán macros, esta vez un poco más complejas.
   Lo ideal es usar funciones, pero es algo que todavía no hemos visto en la teoría.
   En cambio, las macros permiten pensar trabajando al modo de una función, aunque de un modo algo tosco, y asumiendo el riesgo de obtener errores difíciles de descifrar:-\
   En particular, esto nos obliga a ser muy cuidadosos  :o :o :o :o :o con el uso de macros, y documentar en detalle cómo las definimos, con qué propósito, y dejar indicado con claridad cómo debe usarse para que funcione bien.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

1. Ejemplo de lo que se quiere lograr. Ejemplo unsigned int. Algunas pautas generales de programación.

En Spoiler:

Abrir para leer

Imaginemos que queremos exhibir la siguiente información del tipo entero unsigned int:

Rango de valores del tipo [unsigned int]:
___Compilador local: 0 <=  x  <= 4294967295.
______Esta'ndar C99: 0 <=  x  <= 65535.
_____Comparar ambos:
                 0 <=  x  <= 65535 <= 4294967295.

\( \bullet \)   En la 1er línea indicamos que vamos a analizar el tipo de datos unsigned int.
\( \bullet \)   En la 2da línea informamos cuál es el rango de valores que unsigned int admite en nuestra implementación local.
\( \bullet \)   La 3era línea informa acerca del rango de valores que, cuando menos, debe estar soportado en todo compilador, para el tipo unsigned int, acorde a las exigencias del estándar C99.
\( \bullet \)   La 4ta línea compara los rangos de ambos tipos: el primer número, 65535, corresponde al exigido por C99, mientras que el segundo número, 4294967295, corresponde al compilador local.
   Con el par de caracteres <= estamos expresando la frase menor o igual.

   Tengamos en cuenta que todo esto es información presentada en pantalla.
   Daría lo mismo si ponemos la frase explícita "menor o igual" en vez de los símbolos "<=".
   Lo importante es que la entienda el usuario que corre el programa.
   Se observa también que la información mostrada es muy escueta.
   La razón es que todavía estamos en la etapa en que nos conformamos con programas caseros para salir del paso.
   Un exceso de información o explicaciones puede resultar engorroso de leer en línea de comandos.  ::)

   Así que aquí tenemos ya una primera lección: hay que sopesar los pros y los contras a la hora de mostrar mucha o poca información, o que los estilos de presentación de los datos sean o no escuetos. ::)

   En general, conviene mantener las cosas breves, porque al usuario le molesta un exceso de palabrerío. >:( A lo sumo, se le puede dar la opción de obtener más información detallada, si así lo desea. :-*
   Incluso esta información puede estar fuera del programa, como en archivos PDF, páginas web, o darte el teléfono del tipo que te puede sacar de dudas.  ;)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

En la 4ta línea de información además, se comparan los rangos:

                  0 <=  x  <= 65535 <= 4294967295.

   Y parece estar todo bien, ya que se ve claramente que el rango exigido por C99 está comprendido dentro del rango de valores admitidos por nuestro compilador.
   ¿Pero quién se dio cuenta de que estos rangos están correctos? ¿Nosotros o el programa?
   El programa no hizo ninguna verificación sobre esto, sino que nosotros "miramos ahí los númeritos"  :P y nos dimos cuenta de que todo funciona como se esperaba. :laugh:
   Por supuesto que podríamos ahorrarnos tener que mirar eso con nuestros propios ojos, y hacer que el programe tome los números y los compare. Pero esto requiere que estudiemos operadores booleanos y sentencias condicionales, que son temas que aún no abordaremos.  :-X
   De paso, quiero insistir en que se pueden hacer muchas cosas con un lenguaje, aún conociendo pocas herramientas de él.  Un programador diestro es aquel que está familiarizado con todas las posibilidades que le permite llevar a cabo una serie de herramientas del lenguaje de programación que usa.  8^)
   Estamos, pues, tirando de la cuerda, a ver cuánto podemos hacer sin más teoría. Saluden a Satán:  >:D

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Imaginemos una sentencia en C que nos muestre la información del rango de valores para el tipo unsigned int.
   Sería, simplemente, un printf() con strings concatenadas. Algo así:

  "Rango de valores del tipo [unsigned int]:         \n"
  "___Compilador local: 0 <=  x  <= 4294967295.      \n"
  "______Esta'ndar C99: 0 <=  x  <= 65535.           \n"
  "_____Comparar ambos:                              \n"
  "                 0 <=  x  <= 65535 <= 4294967295. \n"
  "\n"
);

   Como de costumbre, a fin de poder usar printf(), de la librería <stdio.h>, en nuestro programa, es necesario escribir esta línea al principio:

#include <stdio.h>

   Notemos que, por cada línea de texto, hemos usado una string, terminada con un salto de línea '\n', y seguimos en la siguiente línea, casi como si quisiéramos imitar el comportamiento real del programa.
   Es la misma técnica que usamos en el post anterior. Usar el printf() así, es más claro, porque representa con mucha mayor fidelidad lo que luego va a verse en pantalla al correr el programa.
   Además, pusimos todos los '\n' en la misma columna, para que se vean más nítidamente. Sin embargo eso puede que no sea siempre una buena idea.
   Por ahora es más claro nuestro printf() así presentado, pero como pronto se nos va arruinar, no podremos conservar a los saltos de línea '\n' bonitamente encolumnados.

[cerrar]

2. Macro para visualizar información de rango de valores de unsigned int. 1era. Versión. Casting Forzado.

En Spoiler:

Desarrollo de la macro PRINTF_INFO_UINT_MAX1

   Hay información que ya tenemos en las librerías acerca del rango de valores de unsigned int.
   La constante UINT_MAX de <limits.h> nos informa del máximo valor admitido para unsigned int en la implementación local.
   A fin de poder utilizar esa constante y otras parecidas, tendremos que poner al principio de nuestro programa la línea:

#include <limits.h>

Hagamos el intento de mostrar los datos mediante una macro:

#define PRINTF_INFO_UINT_MAX1 printf("Rango local admitido para unsigned int: 0 <= x <= %ju\n", UINT_MAX)

Si en nuestro programa escribimos la sentencia:

PRINTF_INFO_UINT_MAX1;

será equivalente a haber escrito el printf() indicado en la macro, y el resultado de la ejecución mostrará en pantalla:

Rango local admitido para unsigned int: 0 <= x <= 4294967295

   Hasta aquí no hay grandes misterios.
   Observemos además el uso del modificador "%ju" en printf(), utilizado para exhibir enteros unsigned, de tamaño arbitrario.
   Esto lo hacemos para abarcar, de una vez por todas, todos los casos posibles, ya que uintmax_t soporta hasta el más grande de todos los valores numéricos de todos los tipos enteros unsigned de la implementación local.
   Además, cuando se hace una conversión de tipos, automática o forzada, de una constante cuyo tipo es cualquiera de los enteros unsigned "hacia" el tipo uintmax_t, la conversión es siempre factible, y además correcta, en el sentido de que se conserva bien el valor numérico, tal como debiera ser.
   A pesar de todo esto, no quiere decir que necesariamente todo funcione bien cuando queramos exhibir con printf() constantes enteras cualesquiera (en este caso sólo enteros no signados).

   Para que printf() reciba y muestre enteros uintmax_t, requiere la especificación de conversión "%ju" en el punto exacto de la string (que se llama cadena de formato).

   El tipo de enteros que se espera con "%ju" es un uintmax_t.
   Vamos a suponer que nos restringimos por ahora a tipos unsigned solamente. ¿Puede haber algún error al intentar mostrar valores de tipos enteros de menor longitud?
   En ese caso hay que preguntarse si printf() interpretará y mostrará correctamente los valores numéricos.
   He releído con cuidado el documento sobre el estándar, y no parece claro al respecto. Lo único que puede sacarse en limpio es que el comportamiento de printf() queda indefinido, es decir, no especificado por el estándar. Así pues, si printf() funciona respetando los valores numéricos, como esperamos que haga, depende del compilador que nos toque en suerte.
   (En realidad hay razones más profundas, relativas a la lista de argumentos de printf(), cosa que analizaremos algún día).

   A fin de evitar riesgos en este sentido, y poder transportar nuestro programa a cualquier sistema, tendremos que tomar la precaución de hacer un casting forzado, tal como en el post anterior, mediante: ((uintmax_t)(UINT_MAX)).
   No obstante, el tipo entero uintmax_t no está disponible directamente, sino que se define en la biblioteca <stdint.h>. Así que al principio del programa debemos agregar la línea:

#include <stdint.h>

   Nuestra macro quedaría ahora así (en rojo marcamos los cambios):

#define PRINTF_INFO_UINT_MAX1 printf("Rango local admitido para unsigned int: 0 <= x <= %ju\n", ((uintmax_t)(UINT_MAX)) )

   Vemos también que es demasiado largo todo eso, para ponerlo en un solo renglón.
   Así que vamos a quebrar la declaración #define en varias líneas, mediante el símbolo \, el cual sirve, recordemos, para indicar que la definición de la macro continúa en la línea siguiente:

#define PRINTF_INFO_UINT_MAX1 \
   printf(  \
      "Rango local admitido para unsigned int: 0 <= x <= %ju\n",  \
      ((uintmax_t)(UINT_MAX))  \
   ) /* Cierre de printf( ... ) */

   Notemos que hemos puesto un paréntesis solitario para que se vea claramente dónde "termina" lo que corresponde al printf(). Por las dudas, hemos puesto ahí también un comentario indicando que ahí termina printf(), para que esto sea más visible dentro del programa.
[cerrar]

3. Macro para visualizar información del rango de valores de tipos enteros unsigned. 1era. versión.

En Spoiler:

Desarrollo de macro PRINTF_INFO_U1 (que tiene 2 parámetros)

   Como queremos mostrar esta información para todos los tipos enteros unsigned, tenemos que poner dos parámetros ahí: uno que indique el nombre del tipo, y otro que indique su máximo admitido por la implementación local. Nuestra macro quedaría ahora así:

#define PRINTF_INFO_U1(TIPO, MAX) \
   printf(  \
      "Rango local admitido para " TIPO ": 0 <= x <= %ju\n",  \
      ((uintmax_t)(MAX))  \
   ) /* Cierre de print( ... ) */

   El nombre de la macro ha cambiado a PRINTF_INFO_U1, algo más genérico, porque ahora nos dará información sobre el tipo de entero que le indiquemos.
   La U al final del nombre es por "UNSIGNED", ya que pretendemos usar esta macro sólo para tipos de enteros unsigned, por ahora.
   Tiene dos parámetros, TIPO y MAX.
   El parámetro TIPO representa un tipo entero, que nosotros le indicaremos.
   Fijémonos que, para que ese parámetre funcione correctamente, tiene que ser una string correctamente entrecomillada, ya que se encuentra entre dos strings, las cuales el compilador intentará concatenar.

   Aquí hay dos soluciones posibles. O bien forzamos el entrecomillado dentro de la macro misma, con el operador #, o bien documentamos la macro (con comentarios) y ponemos un aviso de que el parámetro TIPO requiere que le pasemos una string adecuadamente entrecomillada.
   A mí me parece más consecuente la 2da de estas posibilidades. Para entender por qué, analicemos el aspecto de estas dos posibles formas de usar la macro:

PRINTF_INFO_U1( unsigned int, UINT_MAX);
PRINTF_INFO_U1("unsigned int", UINT_MAX);


   En la 1ra forma, se pasa como argumento unsigned int, que no se sabe lo que es.
   A simple vista parece que estuviéramos pasando como parámetro ¡un tipo de datos!
   Pero en realidad la intención es que ese parámetro funcione como una string.
   En cambio, la 2da forma me parece más clara a la vista. No hay dudas de que eso es una string, y que si la macro va a mostrar ese dato, lo que mostrará será una string.
   No se confunde con ninguna otra cosa, y así sabemos que podemos usar la macro sin peligro de que su resultado final se convierta en algo extraño, impredecible.

   Hemos elegido la claridad 8^) por encima del formalismo absoluto  (que implicaría definir las cosas a prueba de errores de todo tipo, que no es fácil de lograr con las macros).

   En cuanto al parámetro MAX, la macro espera que allí aparezca una cierta constante, o una macro equivalente a una constante. De hecho, para que funcione correctamente, tiene que ser algún entero de los unsigned.
   Ahora, para volver a obtener la información relativa al tipo unsigned int, tendríamos que usar esa macro así:

PRINTF_INFO_U1("unsigned int", UINT_MAX);

[cerrar]

4. Macro exclusiva para el tipo unsigned int. 2da. versión.

En Spoiler:

Desarrollo de macro PRINTF_INFO_UINT_MAX2

   Agreguemos la información del rango de valores exigido por el estándar.
   Si bien a la constante UINT_MAX pudimos acceder, porque está en las librerías de C, resulta que el valor \( 2^{16}-1 \) (o sea 65535), que es el menor de los valores que el estándar exige para UINT_MAX, no es algo que pueda accederse a través de constantes en una librería de C::)  Esa información está solamente escrita en documentos, archivada en los cajones de las oficinas de ISO, en sobres lacrados y rubricados por las autoridades ¿competentes? del caso.  :-*
   ¿Cómo mostrar esa información en nuestro programa?
   La única solución es escribirla a mano.  :P

   Primero modifiquemos la macro PRINTF_INFO_UINT_MAX1:

#define PRINTF_INFO_UINT_MAX2 \
   printf(  \
      "Rango local admitido para unsigned int: 0 <= x <= %ju\n"  \
      "Rango exigido por el esta'ndar C99:     0 <= x <= 65535\n",  \
      ((uintmax_t)(UINT_MAX))  \
   ) /* Cierre de printf( ... ) */

   Hemos agregado una 2da línea de información, acerca el rango del estándar.
   Y como se ve, pusimos "a mano" el valor 65535, como un pieza de información dentro de una string.
   El printf() ni se enteró de que ahí había un valor numérico que nos interesaba.

[cerrar]


5. Macro exclusiva para el tipo unsigned int. 3ra. versión.

En Spoiler:


Desarrollo de macro PRINTF_INFO_UINT_MAX3

   Si quisiéramos generalizar esto para aplicarlo a todos los tipos enteros, tendríamos que dejar que la macro acepte como parámetro el valor 65535.
   O sea que no puede figurar ahí en forma fija, puesta a mano, sino que mejor dejamos el "hueco" para poder poner un número cualquiera.
   Vamos a poner en ese lugar un especificador de conversión "%ju", lo cual reclamará que agreguemos un nuevo entero de tipo uintmax_t a la lista de argumentos de la función printf().
   Así que volvemos a redefinir la macro, así:

#define PRINTF_INFO_UINT_MAX3 \
   printf(  \
      "Rango local admitido para unsigned int: 0 <= x <= %ju\n"  \
      "Rango exigido por el estandar C99:     0 <= x <= %ju\n",  \
      ((uintmax_t)(UINT_MAX)), ((uintmax_t)(65535))   \
   ) /* Cierre de printf( ... ) */

   ¿Por qué no pusimos el número 65535 directamente, sino que usamos el cast forzado (uintmax_t)(65535)?
   Bueno, les dejo como ejercicio ver lo que pasa si no se usa el cast.
   Probando diferentes opciones, resulta que el printf() no me funcionó correctamente en este caso.
   Y el compilador no ha cometido pecado en esto, porque el estándar no obliga a nada, como ya hemos visto con este "asunto" del especificador "%ju".
   Sólo con el cast forzado tendremos la seguridad de que las cosas encajarán bien.

[cerrar]


6. Macro exclusiva para el tipo unsigned int. 4ta. versión. Macro para hacer casting forzado.

En Spoiler:

Macro PRINTF_INFO_UINT_MAX4. Macro de cast forzado U_CAST

   Ya vemos que en una misma sentencia repetimos dos veces el cast forzado.
   Dado que es una operación que se nos está haciendo muy común, quizás convenga abreviarla, por ejemplo mediante una macro (igual que en el post anterior).
   La ventaja de usar una macro, es que nos aseguramos de hacer la definición bien una única vez, y el resto de las veces sólo invocamos el nombre de la macro, y tendremos una declaración uniforme, sin errores que de otro modo surgen involuntariamente en el tipeo de un programa.

#define U_CAST(X)  ((uintmax_t)(X))

Volvemos a redefinir nuestra macro, ahora usando U_CAST, así:

#define PRINTF_INFO_UINT_MAX4 \
   printf(  \
      "Rango local admitido para unsigned int: 0 <= x <= %ju\n"  \
      "Rango exigido por el esta'ndar C99:     0 <= x <= %ju\n",  \
      U_CAST(UINT_MAX), U_CAST(65535)   \
   ) /* Cierre de printf( ... ) */


El modo de usarla es muy sencillo:

PRINTF_INFO_UINT_MAX4;

Eso por sí solo hará el trabajo.

[cerrar]

7. Macro genérica para tipos enteros. 4ta. versión.

En Spoiler:

Macro PRINTF_INFO_U4 (que tiene 3 parámetros)

   Pero no es PRINTF_INFO_UINT_MAX4 la macro la que nos interesa, sino PRINTF_INFO_U1, que tenía parámetros.
   Necesitaremos agregarle como tercer parámetro el valor 65535, que en forma genérica será STD_MAX (que quiere decir standard maximum.

#define PRINTF_INFO_U4(TIPO, MAX, STD_MAX) \
   printf(  \
      "Rango local admitido para " TIPO ": 0 <= x <= %ju\n"  \
      "Rango exigido por el esta'ndar C99: 0 <= x <= %ju\n",  \
      U_CAST(MAX), U_CAST(STD_MAX)  \
   ) /* Cierre de print( ... ) */


   El modo de usarla con nuestro ejemplo, sería así:

PRINTF_INFO_U4("unsigned int", UINT_MAX, 65535);

   Aquí podemos preguntarnos si está bien poner el número 65535 así no más, o si conviene ponerle un sufijo u que indique unsigned, para que el cast forzado U_CAST(STD_MAX) se haga correctamente.
   Digamos por ahora tan solo que el cast forzado con (uintmax_t) arregla cualquier inconveniente que podamos plantear, siempre que los números sean positivos.
   (El tema de los castings lo estudiaremos más adelante.)

   Pero en caso de que no confiemos mucho en eso todavía, o simplemente nos hayamos olvidado de las reglas del juego, podemos poner los sufijos que queramos al 65535, y asegurarnos que es del tipo correcto. Por ejemplo: 65535ULL nos asegura que será un unsigned long long int.

[cerrar]


8. Macro exclusiva para unsigned int, 5ta. versión. Macro genérica para tipos enteros. 5ta. versión.

En Spoiler:

Macros PRINTF_INFO_UINT_MAX5 y PRINTF_INFO_U5

   Ahora sólo nos falta agregar una línea más, en la cual comparamos los valores MAX y STD_MAX de la macro. Pero esa comparación es meramente visual: la hace el usuario mientras mira la pantalla, si se le da la gana.  :laugh:
   Para el caso exclusivo de unsigned int, definimos la macro:

#define PRINTF_INFO_UINT_MAX5 \
   printf(  \
      "Rango local admitido para unsigned int: 0 <= x <= %ju\n"  \
      "Rango exigido por el esta'ndar C99:     0 <= x <= %ju\n"  \
      "Comparacio'n de ambos rangos: 0 <= x <= %ju <= %ju\n",    \
      U_CAST(UINT_MAX), U_CAST(65535), U_CAST(65535), U_CAST(UINT_MAX)   \
   ) /* Cierre de printf( ... ) */

   La 3ra línea tiene dos especificadores de formato "%ju", por lo tanto el printf() requiere dos argumentos más.
   Pero esos argumentos son datos que ya tenemos disponibles.
   El primero de ellos es el 65535, y el segundo es UINT_MAX.
   Van en ese orden ahora, porque el valor 65535 proveído por el estándar C99 tiene que ser menor o igual que UINT_MAX (que contiene el valor definido por la implementación local).
   Llevemos esto a la macro genérica:

#define PRINTF_INFO_U5(TIPO, MAX, STD_MAX) \
   printf(  \
      "Rango local admitido para " TIPO ": 0 <= x <= %ju\n"  \
      "Rango exigido por el esta'ndar C99: 0 <= x <= %ju\n"  \
      "Comparacio'n de ambos rangos: 0 <= x <= %ju <= %ju\n",    \
      U_CAST(MAX), U_CAST(STD_MAX), U_CAST(STD_MAX), U_CAST(MAX) \
   ) /* Cierre de print( ... ) */
   

   Aquí la 3ra línea es la misma, mientras que la 4ta línea sólo pone los parámetros MAX y STD_MAX en los lugares que antes teníamos constantes concretas.
   El modo de uso es siempre el mismo:

PRINTF_INFO_U5("unsigned int", UINT_MAX, 65535);

[cerrar]

9. Macros para "casi" potencias de 2, y para potencias de 2 "exactas". Sobre el uso de constantes para obtener programas más sólidos.

En Spoiler:

Constantes para "casi" potencias de 2, y para potencias de 2 "exactas"

   Una cosa que podemos advertir acá, es que 65535 no es el único número de ese tipo que vamos a tener, sino que vamos a tener que repetir una y otra vez constantes que son "casi" potencias de 2.
   Para evitar errores involuntarios en la escritura de esas constantes, o incluso la ambigüedad de estarlas escribiendo a veces de un modo u otro distintos, podemos procurarlas mediante identificadores con un nombre específico. Por ejemplo:

#define CASI_2_a_la_16  65535

   ¿Qué ventaja ofrece definir una constante así?
   Si ponemos por error 655355, o 75535, el compilador no nos avisará de nada, porque no sabe que es un error, ya que no sabe lo que estamos tratando de hacer.
   Pero si definimos un identificador, como CASI_2_a_la_16, bastará usarlo para estar seguros de que siempre tendremos el valor correcto de 65535.
   El lector mordaz  >:D dirá que uno también puede equivocarse escribiendo mal el identificador...
   Pero acá es donde yo gano la discusión, porque esta vez el compilador sí avisa que hay un error, y esto nos obliga a escribir el identificador CASI_2_a_la_16 correctamente.  :P :P :P :P
   De la misma manera que en el post anterior, preferimos el método de usar números hexadecimales para especificar constantes de ese tipo. Declaramos las que vamos a usar:

#define CASI_2_a_la_7  0x7fULL
#define CASI_2_a_la_8  0xffULL
#define CASI_2_a_la_15 0x7fffULL
#define CASI_2_a_la_16 0xffffULL
#define CASI_2_a_la_31 0x7fffffffULL
#define CASI_2_a_la_32 0xffffffffULL
#define CASI_2_a_la_63 0x7fffffffffffffffULL
#define CASI_2_a_la_64 0xffffffffffffffffULL

   Los valores numéricos de esas constantes corresponden a:

\( 2^{7}-1, 2^{8}-1, 2^{15}-1, 2^{16}-1, 2^{31}-1, 2^{32}-1, 2^{63}-1, 2^{64}-1. \)

   Por eso llevan el calificativo de "CASI" por delante, ya que son potencias de 2 menos 1, es decir, "casi" potencias de 2.
   También anticipamos que vamos a usar algunas potencias exactas de 2, las siguientes:

#define EXACTO_2_a_la_7  (CASI_2_a_la_7  + 1ULL)
#define EXACTO_2_a_la_15 (CASI_2_a_la_15 + 1ULL)
#define EXACTO_2_a_la_31 (CASI_2_a_la_31 + 1ULL)
#define EXACTO_2_a_la_63 (CASI_2_a_la_63 + 1ULL)

   Corresponden a las potencias exactas de 2 siguientes:

\( 2^{7}, 2^{15}, 2^{31}, 2^{63}. \)

   Se obtuvieron sumando un 1 a las "casi" potencias. Parece un método extraño de obtenerlas, y ustedes pueden elegir otro método de definirlas.
   Pero aquí se requiere mucho cuidado, ya que si deseamos que todas esas constantes sean unsigned, y dado que algunos valores pueden volverse muy grandes, podemos tener problemas con las conversiones automáticas de los tipos enteros.
   Sea cual sea el método elegido para definir esas constantes, ha de ser uno que nos asegure el valor correcto, y en formato unsigned long long int.

[cerrar]

10. Macro genérica para tipos enteros. 6ta. versión.

   Llegamos por fin al final del post. Para entender de qué se habla abajo, no habrá más remedio que ir leyendo los spoilers de las secciones de arriba.
   Vamos a introducir un cambio en las frases del texto que se visualiza en pantalla. Vamos a dejar algo bien escueto, como pretendíamos al principio del post, así:

#define PRINTF_INFO_U6(TIPO, MAX, STD_MAX) \
   printf(  \
      "Rango del tipo [" TIPO "]:\n" \
      "___Compilador local: 0 <= x <= %ju\n"  \
      "______Esta'ndar C99: 0 <= x <= %ju\n"  \
      "_____Comparar ambos: 0 <= x <= %ju <= %ju\n" \
      "\n", U_CAST(MAX), U_CAST(STD_MAX), U_CAST(STD_MAX), U_CAST(MAX) \
   ) /* Cierre de print( ... ) */
 

   Su modo de uso es el de siempre, salvo que ahora utilizamos las constantes "CASI...":

PRINTF_INFO_U6("unsigned int", UINT_MAX, CASI_2_a_la_8);

   El resultado que mostrará el programa será esto:

Rango del tipo [unsigned int]:
___Compilador local: 0 <= x <= 4294967295
______Esta'ndar C99: 0 <= x <= 65535
_____Comparar ambos: 0 <= x <= 65535 <= 4294967295


 ;) ;) ;)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

(Continuamos en el siguiente post...)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

10 Enero, 2013, 10:37 pm
Respuesta #15

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,394
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
15. Testeando tipos de datos enteros de C (parte III)

Continuamos con la tarea del post anterior.

   La macro que terminamos de construir allí sólo sirve para estudiar los tipos unsigned.
Tenemos que hacer macros que nos permitan obtener resultados correctos con tipos de enteros signed.

11. Consideraciones generales. Macro exclusiva para signed int, 6ta. versión. Macro S_CAST de casting forzado a intmax_t.

En Spoiler:

Macro PRINTF_INFO_SINT6. Macro de casting forzado S_CAST

   Los tipos signed requerirán seguramente una macro distinta, ya que ellos tienen ahora dos extremos que considerar: un mínimo y un máximo.
   Basados en la experiencia de lo que hemos hecho hasta ahora, hagamos una macro exclusivamente para el tipo signed int.
   Primero que nada, el especificador de formato tendrá que ser "%ji", es decir que configuramos printf() para que acepte valores de tipo intmax_t, que es la versión signed de uintmax_t.
   En particular, necesitaremos una macro que haga el casting forzado, esta vez hacia el tipo intmax_t:

#define S_CAST(X)  ((intmax_t)(X))

   Se necesitan dos parámetros adicionales para la macro, uno que indique el mínimo del rango de valores de la implementación local, y otro que indique el mayor de los mínimos que admite el estándar C99. Nuestra macro sería así:

#define PRINTF_INFO_SINT6 \
   printf(  \
      "Rango del tipo [signed int]: \n" \
      "___Compilador local: %ji <= x <= %ji\n"  \
      "______Esta'ndar C99: %ji <= x <= %ji\n"  \
      "_____Comparar ambos: %ji <= %ji <= x <= %ji <= %ji\n",    \
      S_CAST(INT_MIN), S_CAST(INT_MAX), \
      -S_CAST(CASI_2_a_la_15), S_CAST(CASI_2_a_la_15),\
       S_CAST(INT_MIN), -S_CAST(CASI_2_a_la_15), S_CAST(CASI_2_a_la_15), S_CAST(INT_MAX)  \
   ) /* Cierre de printf( ... ) */


   Observemos que hay 8 especificadores "%ji" en el printf(), y debe haber por lo tanto 8 argumentos de tipo intmax_t para printf().
   Los hemos escrito separados en tres renglones, para mayor claridad.
   El 1er renglón de argumentos muestra con claridad cuáles son los dos argumentos de los primeros dos especificadores "%ji" de la línea que dice "Compilador local".
   El 2do renglón hace lo propio con "Estándar C99",  y finalmente el 3er renglón muestra los restantes 4 argumentos, en el orden correcto en que deben aparecer en la línea "Comparar ambos".

[cerrar]

12. Macro exclusiva para intmax_t, versiones 6ta. y 7ma. Problemas con valores negativos muy grandes.

En Spoiler:


Macros PRINTF_INFO_INTMAX_T6 y PRINTF_INFO_INTMAX_T6

   El enfoque de la macro esconde problemas potenciales con la conversión de tipos enteros, ya que recordemos que tenemos que manejar números grandes, tanto positivos como negativos.
   Para ver a lo que me refiero, intentemos hacer una macro que haga lo mismo, pero para el caso del tipo intmax_t:

#define PRINTF_INFO_INTMAX_T6 \
   printf(  \
      "Rango local admitido para intmax_t: %ji <= x <= %ji\n"  \
      "Rango exigido por el esta'ndar C99: %ji <= x <= %ji\n"  \
      "Comparacio'n de ambos rangos:  %ji <= %ji <= x <= %ji <= %ji\n",    \
      S_CAST(INTMAX_MIN), S_CAST(INTMAX_MAX), \
      -S_CAST(EXACTO_2_a_la_63), S_CAST(CASI_2_a_la_63),\
       S_CAST(INTMAX_MIN), -S_CAST(EXACTO_2_a_la_63), S_CAST(CASI_2_a_la_63), S_CAST(INTMAX_MAX)  \
   ) /* Cierre de printf( ... ) */


   El programa corre, pero el compilador me avisó de un error aritmético.

   El problema aquí es que la constante EXACTO_2_a_la_63 tiene el valor \( 2^{63} \), que es mayor que el máximo valor aceptado por intmax_t en mi implementación local. Ese valor necesita ser declarado como unsigned long long, y de hecho la macro EXACTO_2_a_la_63 lo hace así.
   Pero luego le hemos cambiado el signo anteponiendo un "-" a la izquierda, tratando de convertirlo a un número negativo.
   Las operaciones de conversión de tipos se pueden estar volviendo locas por dentro, sin que lo sepamos.
   Por ejemplo, el estándar no nos asegura que un número negativo tan grande esté aceptado como signed long long int. (Se está contemplando la posibilidad de una representación interna con complemento a 1, que quitaría del rango un valor negativo de \( -2^{63} \)).

   A mí me da un error de tipo Overflow.
   En general sencillamente hay riesgo de que el programa no sea portable, es decir, que en otras plataformas no pueda compilar.

   Para evitar estos riesgos, tendremos que tratar de rodear el problema.
   Primero, notemos que las constantes que provee el compilador, por muy negativas que sean, es seguro que pueden representarse correctamente como del tipo intmax_t, pues por mera definición es el tipo que soporta a todos los tipos enteros signed, junto con sus rangos de valores.
   Sólo un compilador mal hecho nos pondría en una situación errónea aquí.

   Por otro lado, la constante CASI_2_a_la_63, por ejemplo, la hemos proveído nosotros, y es unsigned long long int. Si pudiéramos dejarla trabajar como unsigned, no tendríamos los inconvenientes de que hablamos.
   Además, observemos que los límites del estándar para los "mínimos" de cada tipo entero, son siempre valores negativos. O sea que el signo - bien podríamos ponerlo a mano como un caracter más. Nadie nos obliga a obtenerlo mediante una expresión aritmética.  :-*

   En cambio, los extremos del lado derecho, tanto locales como del estándar, son números positivos, que tenemos la seguridad de que son representables en toda implementación.
   Por ejemplo, el valor estándar positivo más alto al que nos referiremos será \( 2^{63}-1 \), que es nuestra constante CASI_2_a_la_63.
   Así que no tendremos problemas con los extremos del lado derecho.

   La única precaución que tenemos que tomar es la del mayor de los valores mínimos exigido por el estándar, del cual tendremos que poner su signo negativo a mano, e indicar sólo su valor absoluto, y como unsigned.
   En particular esto nos obliga a cambiar la especificación de formato, en los puntos en donde usaremos ese valor, a "%ju".
   En consecuencia el cast para ese valor será U_CAST.

   A continuación mostramos la macro resultante:

#define PRINTF_INFO_INTMAX_T7 \
   printf(  \
      "\t\tRango del tipo [intmax_t]: \n" \
      "\n" \
      "___Compilador local: %ji <= x <= %ji\n"  \
      "______Esta'ndar C99: -%ju <= x <= %ji\n"  \
      "_____Comparar ambos: %ji <= -%ju <= x <= %ji <= %ji\n",    \
      S_CAST(INTMAX_MIN), S_CAST(INTMAX_MAX), \
      U_CAST(EXACTO_2_a_la_63), S_CAST(CASI_2_a_la_63),\
       S_CAST(INTMAX_MIN), U_CAST(EXACTO_2_a_la_63), S_CAST(CASI_2_a_la_63), S_CAST(INTMAX_MAX)  \
   ) /* Cierre de printf( ... ) */


   El código de esta macro es portable, compila pues sin errores, y ejecuta bien.

   En el primer renglón agregué dos tabuladores "\t\t" para intentar centrar un poco el título, además agregué una línea más en blanco con un "\n" entre el 1er y 2do renglón, por razones estéticas.

[cerrar]


13. Macro genérica para tipos signed, 7ma. versión.

En Spoiler:

Macro PRINTF_INFO_S7

   Ahora tenemos que generalizar a una macro que muestre datos del tipo entero (signed) que queramos.
   Necesitamos especificar una macro que acepte como parámetros una string que va a contener el nombre del tipo de entero signed que nos interesa, luego cuatro constantes numéricas que contengan la información de: el mínimo y el máximo del rango de valores del tipo indicado en la implementación local, el valor absoluto  :o :o :o :o :o del mayor de los mínimos exigido por el estándar C99, y finalmente el menor de los máximos del estándar.
   Especial cuidado ha de tenerse con el valor inferior del estándar, ya que de él deberemos especificar siempre su valor absoluto, por el modo en que programamos la macro. El resultado es esto:

#define PRINTF_INFO_S7(TIPO, MIN, MAX, ABS_STDMIN, STDMAX) \
   printf(  \
      "\t\tRango del tipo [" TIPO "]: \n" \
      "\n" \
      "___Compilador local: %ji <= x <= %ji\n"  \
      "______Esta'ndar C99: -%ju <= x <= %ji\n"  \
      "_____Comparar ambos: %ji <= -%ju <= x <= %ji <= %ji\n",    \
      S_CAST(MIN), S_CAST(MAX), \
      U_CAST(ABS_STDMIN), S_CAST(STDMAX),\
      S_CAST(MIN), U_CAST(ABS_STDMIN), S_CAST(STDMAX), S_CAST(MAX)  \
   ) /* Cierre de printf( ... ) */

   La S de PRINTF_INFO_S7 es de signed, y el 7 es porque estoy respetando el número de versión que traíamos de las macros anteriores (a macros "paralelas" les he puesto el mismo número de versión).
   Hagamos un experimento usándola con esta sentencia:

PRINTF_INFO_S7("intmax_t", INTMAX_MIN, INTMAX_MAX, EXACTO_2_a_la_63, CASI_2_a_la_63);

El resultado mostrado en mi pantalla es correcto:


                Rango del tipo [intmax_t]:

___Compilador local: -9223372036854775808 <= x <= 9223372036854775807
______Esta'ndar C99: -9223372036854775808 <= x <= 9223372036854775807
_____Comparar ambos: -9223372036854775808 <= -9223372036854775808 <= x <= 9223372036854775807 <= 9223372036854775807




   Sin embargo no caben todos esos números en la pantalla. Se puede arreglar la presentación, pero no lo voy a hacer, ya es demasiado engorro de programación hasta aquí.
[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

14. El caso de los tipos enteros "mixtos".

   Tenemos finalmente el caso de tipos de datos ambiguos como char, que el estándar no avisa si son signed o unsigned.

Detalles en el Spoiler:

Macro PRINTF_INFO_M7

   Usando nuestra experiencia con los casos unsigned y signed, podemos crear otra macro que trate este caso. Tendremos que poner especial cuidado de dónde y por qué poner especificadores "%ju" y "%ji".
   La mostramos aquí, sin más explicaciones, pues se entenderá:

#define PRINTF_INFO_M7(TIPO, MIN, MAX, STD_UMAX, ABS_STD_SMIN, STD_SMAX) \
   printf(  \
      "\t\tRango del tipo [" TIPO "]: \n" \
      "\n" \
      "___________________Compilador local: %ji <= x <= %ji\n"  \
      "______Esta'ndar C99 (caso unsigned): 0 <= x <= %ju\n"  \
      "_____________________Comparar ambos: \n" \
      "\n" \
      "\t %ji <= 0 <= x <= %ju <= %ji \n"    \
      "\n" \
      "______Esta'ndar C99 (caso   signed): -%ju <= x <= %ji\n"  \
      "_____________________Comparar ambos: \n" \
      "\n" \
      "\t %ji <= -%ju <= x <= %ji <= %ji\n",    \
      S_CAST(MIN), S_CAST(MAX), \
      U_CAST(STD_UMAX), \
      S_CAST(MIN), U_CAST(STD_UMAX), S_CAST(MAX),  \
      U_CAST(ABS_STD_SMIN), S_CAST(STD_SMAX),\
      S_CAST(MIN), U_CAST(ABS_STD_SMIN), S_CAST(STD_SMAX), S_CAST(MAX)  \
   ) /* Cierre de printf( ... ) */


   La M es por mixto (ambigüedad entre unsigned y signed).
   Los parámetros TIPO, MIN, MAX, son los de siempre.
   El parámetro STD_UMAX indica el valor superior del rango del estándar, en el caso de que el tipo indicado en TIPO sea unsigned.
   El parámetro ABS_STD_SMIN, se refiere al valor absoluto del valor inferior del estándar para el caso signed, mientras que STD_SMAX es el valor superior del estándar para el caso signed.
   El modo de usarla, con un ejemplo, sería así:

PRINTF_INFO_M7("wint_t", WINT_MIN, WINT_MAX, CASI_2_a_la_16, CASI_2_a_la_15, CASI_2_a_la_15);

El resultado mostrado es éste:


                Rango del tipo [wint_t]:

___________________Compilador local: 0 <= x <= 65535
______Esta'ndar C99 (caso unsigned): 0 <= x <= 65535
_____________________Comparar ambos:

         0 <= 0 <= x <= 65535 <= 65535

______Esta'ndar C99 (caso   signed): -32767 <= x <= 32767
_____________________Comparar ambos:

         0 <= -32767 <= x <= 32767 <= 65535




Nótese que se han hecho arreglos estéticos con ayuda del salto de línea '\n' y el tabulador '\t'.

[cerrar]


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC


15. Testeando todas las macros con un programa real.

   A continuación ponemos todas estas macros juntas en un programa, con sus respectivas sentencias de comprobación.
   Es sólo un programa que testea las macros, no uno que testea los rangos de los tipos enteros.
   Si bien hemos declarado allí la macro problemática PRINTF_INFO_INTMAX_T6, en lugar de "invocarla" hemos puesto un aviso con printf() que dice:
 
"No se muestra: PRINTF_INFO_INTMAX_T6\n\n"

   En particular, como esa macro no se "invoca", para el compilador es como si nunca la hubiéramos declarado, o sea que considera que no existe en nuestro programa.
   Esto muestra a las claras cuánto es que las macros difieren de las funciones.
   Por esa razón, no he obtenido mensajes de ningún tipo del compilador.
   ¡Una macro es sólo una regla de reemplazo de trozos de texto!

Para ver el programa, abrir Spoiler:

Programa TestingIntegerMacros.c

TestingIntegerMacros.c


#include <limits.h>
#include <stdint.h>
#include <stdio.h>

#define CASI_2_a_la_7  0x7fULL
#define CASI_2_a_la_8  0xffULL
#define CASI_2_a_la_15 0x7fffULL
#define CASI_2_a_la_16 0xffffULL
#define CASI_2_a_la_31 0x7fffffffULL
#define CASI_2_a_la_32 0xffffffffULL
#define CASI_2_a_la_63 0x7fffffffffffffffULL
#define CASI_2_a_la_64 0xffffffffffffffffULL

#define EXACTO_2_a_la_7  (CASI_2_a_la_7  + 1ULL)
#define EXACTO_2_a_la_15 (CASI_2_a_la_15 + 1ULL)
#define EXACTO_2_a_la_31 (CASI_2_a_la_31 + 1ULL)
#define EXACTO_2_a_la_63 (CASI_2_a_la_63 + 1ULL)

#define S_CAST(X)  ((intmax_t)(X))
#define U_CAST(X)  ((uintmax_t)(X))

#define PRINTF_INFO_UINT_MAX1 \
   printf(  \
      "Rango local admitido para unsigned int: 0 <= x <= %ju\n",  \
      ((uintmax_t)(UINT_MAX))  \
   ) /* Cierre de printf( ... ) */

#define PRINTF_INFO_UINT_MAX2 \
   printf(  \
      "Rango local admitido para unsigned int: 0 <= x <= %ju\n"  \
      "Rango exigido por el esta'ndar C99:     0 <= x <= 65535\n",  \
      ( (uintmax_t)(UINT_MAX) )  \
   ) /* Cierre de printf( ... ) */
   
#define PRINTF_INFO_UINT_MAX3 \
   printf(  \
      "Rango local admitido para unsigned int: 0 <= x <= %ju\n"  \
      "Rango exigido por el esta'ndar C99:     0 <= x <= %ju\n",  \
      ( (uintmax_t)(UINT_MAX) ), 65535ull   \
   ) /* Cierre de printf( ... ) */

#define PRINTF_INFO_UINT_MAX4 \
   printf(  \
      "Rango local admitido para unsigned int: 0 <= x <= %ju\n"  \
      "Rango exigido por el esta'ndar C99:     0 <= x <= %ju\n",  \
      U_CAST(UINT_MAX), U_CAST(65535)   \
   ) /* Cierre de printf( ... ) */

#define PRINTF_INFO_UINT_MAX5 \
   printf(  \
      "Rango local admitido para unsigned int: 0 <= x <= %ju\n"  \
      "Rango exigido por el esta'ndar C99:     0 <= x <= %ju\n"  \
      "Comparacio'n de ambos rangos: 0 <= x <= %ju <= %ju\n",    \
      U_CAST(UINT_MAX), U_CAST(65535), U_CAST(65535), U_CAST(UINT_MAX)   \
   ) /* Cierre de printf( ... ) */

#define PRINTF_INFO_SINT6 \
   printf(  \
      "Rango del tipo [signed int]: \n" \
      "___Compilador local: %ji <= x <= %ji\n"  \
      "______Esta'ndar C99: %ji <= x <= %ji\n"  \
      "_____Comparar ambos: %ji <= %ji <= x <= %ji <= %ji\n",    \
      S_CAST(INT_MIN), S_CAST(INT_MAX), \
      -S_CAST(CASI_2_a_la_15), S_CAST(CASI_2_a_la_15),\
       S_CAST(INT_MIN), -S_CAST(CASI_2_a_la_15), S_CAST(CASI_2_a_la_15), S_CAST(INT_MAX)  \
   ) /* Cierre de printf( ... ) */

#define PRINTF_INFO_INTMAX_T6 \
   printf(  \
      "Rango del tipo [intmax_t]: \n" \
      "___Compilador local: %ji <= x <= %ji\n"  \
      "______Esta'ndar C99: %ji <= x <= %ji\n"  \
      "_____Comparar ambos: %ji <= %ji <= x <= %ji <= %ji\n",    \
      S_CAST(INTMAX_MIN), S_CAST(INTMAX_MAX), \
      -S_CAST(EXACTO_2_a_la_63), S_CAST(CASI_2_a_la_63),\
       S_CAST(INTMAX_MIN), -S_CAST(EXACTO_2_a_la_63), S_CAST(CASI_2_a_la_63), S_CAST(INTMAX_MAX)  \
   ) /* Cierre de printf( ... ) */

#define PRINTF_INFO_INTMAX_T7 \
   printf(  \
      "\t\tRango del tipo [intmax_t]: \n" \
      "\n" \
      "___Compilador local: %ji <= x <= %ji\n"  \
      "______Esta'ndar C99: -%ju <= x <= %ji\n"  \
      "_____Comparar ambos: %ji <= -%ju <= x <= %ji <= %ji\n",    \
      S_CAST(INTMAX_MIN), S_CAST(INTMAX_MAX), \
      U_CAST(EXACTO_2_a_la_63), S_CAST(CASI_2_a_la_63),\
       S_CAST(INTMAX_MIN), U_CAST(EXACTO_2_a_la_63), S_CAST(CASI_2_a_la_63), S_CAST(INTMAX_MAX)  \
   ) /* Cierre de printf( ... ) */


#define PRINTF_INFO_U1(TIPO, MAX) \
   printf(  \
      "Rango local admitido para " TIPO ": 0 <= x <= %ju\n",  \
      ((uintmax_t)(MAX))  \
   ) /* Cierre de print( ... ) */
   
#define PRINTF_INFO_U4(TIPO, MAX, STD_MAX) \
   printf(  \
      "Rango local admitido para " TIPO ": 0 <= x <= %ju\n"  \
      "Rango exigido por el esta'ndar C99: 0 <= x <= %ju\n",  \
      U_CAST(MAX), U_CAST(STD_MAX)  \
   ) /* Cierre de print( ... ) */
   
#define PRINTF_INFO_U5(TIPO, MAX, STD_MAX) \
   printf(  \
      "Rango local admitido para " TIPO ": 0 <= x <= %ju\n"  \
      "Rango exigido por el esta'ndar C99: 0 <= x <= %ju\n"  \
      "Comparacio'n de ambos rangos: 0 <= x <= %ju <= %ju\n",    \
      U_CAST(MAX), U_CAST(STD_MAX), U_CAST(STD_MAX), U_CAST(MAX) \
   ) /* Cierre de print( ... ) */
   
#define PRINTF_INFO_U6(TIPO, MAX, STD_MAX) \
   printf(  \
      "Rango del tipo [" TIPO "]:\n" \
      "___Compilador local: 0 <= x <= %ju\n"  \
      "______Esta'ndar C99: 0 <= x <= %ju\n"  \
      "_____Comparar ambos: 0 <= x <= %ju <= %ju\n" \
      "\n", U_CAST(MAX), U_CAST(STD_MAX), U_CAST(STD_MAX), U_CAST(MAX) \
   ) /* Cierre de print( ... ) */
   
#define PRINTF_INFO_S7(TIPO, MIN, MAX, ABS_STDMIN, STDMAX) \
   printf(  \
      "\t\tRango del tipo [" TIPO "]: \n" \
      "\n" \
      "___Compilador local: %ji <= x <= %ji\n"  \
      "______Esta'ndar C99: -%ju <= x <= %ji\n"  \
      "_____Comparar ambos: %ji <= -%ju <= x <= %ji <= %ji\n",    \
      S_CAST(MIN), S_CAST(MAX), \
      U_CAST(ABS_STDMIN), S_CAST(STDMAX),\
      S_CAST(MIN), U_CAST(ABS_STDMIN), S_CAST(STDMAX), S_CAST(MAX)  \
   ) /* Cierre de printf( ... ) */

#define PRINTF_INFO_M7(TIPO, MIN, MAX, STD_UMAX, ABS_STD_SMIN, STD_SMAX) \
   printf(  \
      "\t\tRango del tipo [" TIPO "]: \n" \
      "\n" \
      "___________________Compilador local: %ji <= x <= %ji\n"  \
      "______Esta'ndar C99 (caso unsigned): 0 <= x <= %ju\n"  \
      "_____________________Comparar ambos: \n" \
      "\n" \
      "\t %ji <= 0 <= x <= %ju <= %ji \n"    \
      "\n" \
      "______Esta'ndar C99 (caso   signed): -%ju <= x <= %ji\n"  \
      "_____________________Comparar ambos: \n" \
      "\n" \
      "\t %ji <= -%ju <= x <= %ji <= %ji\n",    \
      S_CAST(MIN), S_CAST(MAX), \
      U_CAST(STD_UMAX), \
      S_CAST(MIN), U_CAST(STD_UMAX), S_CAST(MAX),  \
      U_CAST(ABS_STD_SMIN), S_CAST(STD_SMAX),\
      S_CAST(MIN), U_CAST(ABS_STD_SMIN), S_CAST(STD_SMAX), S_CAST(MAX)  \
   ) /* Cierre de printf( ... ) */


int main(void) {

    printf("Probando: PRINTF_INFO_UINT_MAX1\n\n");
    PRINTF_INFO_UINT_MAX1;
    getchar();
    printf("Probando: PRINTF_INFO_UINT_MAX2\n\n");
    PRINTF_INFO_UINT_MAX2;
    getchar();
    printf("Probando: PRINTF_INFO_UINT_MAX3\n\n");
    PRINTF_INFO_UINT_MAX3;
    getchar();
    printf("Probando: PRINTF_INFO_UINT_MAX4\n\n");
    PRINTF_INFO_UINT_MAX4;
    getchar();
    printf("Probando: PRINTF_INFO_UINT_MAX5\n\n");
    PRINTF_INFO_UINT_MAX5;
    getchar();
    printf("Probando: PRINTF_INFO_SINT6\n\n");
    PRINTF_INFO_SINT6;
    getchar();
    printf("No se muestra: PRINTF_INFO_INTMAX_T6\n\n");
    getchar();
    printf("Probando: PRINTF_INFO_INTMAX_T7\n\n");
    PRINTF_INFO_INTMAX_T7;
    getchar();
   
    printf("\n_________________________________________\n\n");
   
    printf("Probando: PRINTF_INFO_U1\n\n");
    PRINTF_INFO_U1("unsigned int", UINT_MAX);
    getchar();
    printf("Probando: PRINTF_INFO_U4\n\n");
    PRINTF_INFO_U4("unsigned int", UINT_MAX, CASI_2_a_la_16);
    getchar();
    printf("Probando: PRINTF_INFO_U5\n\n", CASI_2_a_la_16);
    PRINTF_INFO_U5("unsigned int", UINT_MAX, CASI_2_a_la_16);
    getchar();
    printf("Probando: PRINTF_INFO_U6\n\n");
    PRINTF_INFO_U6("unsigned int", UINT_MAX, CASI_2_a_la_16);
    getchar();
    printf("Probando: PRINTF_INFO_S7\n\n");
    PRINTF_INFO_S7("signed int", INT_MIN, INT_MAX, CASI_2_a_la_15, CASI_2_a_la_15);
    getchar();
    printf("Probando: PRINTF_INFO_M7\n\n");
    PRINTF_INFO_M7("wint_t", WINT_MIN, WINT_MAX, CASI_2_a_la_16, CASI_2_a_la_15, CASI_2_a_la_15);
    getchar();
   
}


[cerrar]


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

10 Enero, 2013, 11:31 pm
Respuesta #16

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,394
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
16. Testeando tipos de datos enteros de C (parte IV)

   En este post continuaremos y culminaremos el programa de testeo de rangos de tipos enteros que veníamos estudiando.
   Vamos a escribir un programa llamado TetingIntegers2.c.
   Lo único que tenemos que hacer es poner las versiones definitivas de los 3 tipos de macros que hemos ya desarrollado: una para tipos unsigned, una para tipos signed, y una para tipos "mixtos" (aquellos que el estándar no dice a priori si han de ser unsigned o signed).

   Además, a fin de aprovechar estas macros plenamente, las invocaremos una vez por cada uno de los tipos enteros que el estándar C99 exige que existan en una implementación.
   Nuestro programa también mostrará información adicional acerca de cómo deben comportarse los rangos de valores de tipos relacionados.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Sin embargo...

   Nos está faltando algo importante, a la vez que aburridísimo: la documentación.  :o :o :o :o

   Se debe especificar dentro del mismo archivo del programa cómo funcionan las macros.

   Dentro del programa TetingIntegers2.c, pondremos las versiones definitivas de las macros anteriores, y allí las documentaremos.
   La documentación de las macros se ha estructurado de la siguiente manera:

(1ro) Se colocó un comentario que dice: "He aquí las susodichas macros".  :P
(2do) Se colocaron las macros directamente.
(3ro) Debajo se inició un largo comentario que documentará las macros.
(4to) Se vuelve a nombrar las macros con su encabezamiento (nombre y parámetros sólo).
(5to) Se explica para qué fueron diseñadas las macros, y qué es lo que hacen.
(6to) Se explica el modo general de uso y primeras recomendaciones.
(7mo) Se dan ejemplos típicos de uso de cada una.
(8vo) Se muestra una salida típica de la ejecución real de esas macros.
(9no) Se explica en mayor detalle el significado de cada uno de los parámetros.
(10mo) Se indica con exactitud cómo deben usarse los parámetros para que no haya errores.
(11vo) Se explican los pormenores del diseño y las razones de por qué algunos detalles van de una forma y no de otra.
(12vo) Se informa sobre el buen funcionamiento en general de las macros, y los casos excepcionales que pueden producirse.

   De hecho, en el programa mismo se explica un caso excepcional factible, aunque improbable, en que la macro PRINTF_INFO_M puede fallar. Léanlo de ahí.  :-*

   Las macros son las mismas que las de TestingIntegerMacros.c, salvo que se les ha quitado el númerito de la versión, se han retocado los nombres de los parámetros para que coincidan adecuadamente, y se han hecho pequeños retoques estéticos en la salida por pantalla (saltos de línea y tabuladores).

   El programa que sigue analiza todos los tipos enteros estándar (excepto _Bool), y separa la presención en varias partes, con información adicional sobre las reglas que deben cumplir los rangos de valores.
   De los tipos que hemos llamado mixtos, basta conque esté correcto uno de los dos casos unsigned o signed.

   Además, el programa contiene comentarios sobre todas las macros y constantes allí definidas, y sobre su modo de uso. Esto, como parte de la documentación del programa conque tanto insistimos últimamente...

Van a ver que más de la mitad del programa es pura documentación.
Una buena documentación tiene muchas virtudes:

  • Ayuda a quien lee el programa a entender qué hemos hecho, para qué, y cómo.
  • Si alguien quiere usar partes de nuestro programa (macros, funciones, rutinas), tendrá claro cuáles son las especificaciones de las mismas, las limitaciones y las posibilidades.
  • Previenen de errores lógicos.
  • Nos ayudan a recordar qué diablos fue lo que hicimos al confeccionar el programa, cómo y parar qué, y también a recordar cuáles fueron los problemas que tuvimos, los errores que deben evitarse, y demás cuestiones técnicas.
  • Ayudan a ahorrar tiempo, que de otra forma uno malgastaría tratando de descifrar lo que dice el programa, o directamente tirándolo a la papelera para crear una versión nueva, y quizá no tan buena como la original.

Incluso, escribir lo que se supone que debe hacer una macro, una función, o una parte del programa, es mejor que el programa mismo, porque si hemos cometido un error al escribir el programa, esto ahora puede corregirse, ya que tenemos una especificación técnica clara de qué es lo que se supone que tiene que hacer.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

El programa se puede ver en el spoiler, y también está como archivo adjunto a este post, dentro de un archivo comprimido .ZIP.

Programa TestingIntegers2.c

TestingIntegers2.c


/* [[+D]] */

/* --------------------------------------------------------------------------- */
/* TestingIntegers2.c                                                          */
/* ==================                                                          */
/*                                                                             */
/* Autor: Argentinator                                                         */
/* 09/Enero/2013                                                               */
/*                                                                             */
/* Curso de C online                                                           */
/*                                                                             */
/* http://rinconmatematico.com/foros/index.php?topic=64835.msg260248#msg260248 */
/* --------------------------------------------------------------------------- */
/* [[-D]] */

#include <stdio.h>
#include <limits.h>
#include <stdint.h>

/* [[+D]] */
/* Potencias de 2 a las que se les restó 1 unidad. */
/* Tienen tipo unsigned long long int              */

#define CASI_2_a_la_7  0x7fULL
#define CASI_2_a_la_8  0xffULL
#define CASI_2_a_la_15 0x7fffULL
#define CASI_2_a_la_16 0xffffULL
#define CASI_2_a_la_31 0x7fffffffULL
#define CASI_2_a_la_32 0xffffffffULL
#define CASI_2_a_la_63 0x7fffffffffffffffULL
#define CASI_2_a_la_64 0xffffffffffffffffULL

/* Potencias de 2 exactas.  Tienen tipo unsigned long long int */

#define EXACTO_2_a_la_7  (CASI_2_a_la_7  + 1ULL)
#define EXACTO_2_a_la_15 (CASI_2_a_la_15 + 1ULL)
#define EXACTO_2_a_la_31 (CASI_2_a_la_31 + 1ULL)
#define EXACTO_2_a_la_63 (CASI_2_a_la_63 + 1ULL)

/* Macros que hacen cast forzado de su parámetro X a tipos enteros máximos */
/* Se recomienda que se respete la "signatura" del tipo, así: */
/* S_CAST para tipos signed que promocionan a intmax_t */
/* U_CAST para tipos unsigned que promocionan a uintmax_t */

#define S_CAST(X) ((intmax_t) (X))
#define U_CAST(X) ((uintmax_t) (X))

/* Macros para análisis de rangos de valores de tipos enteros (ver abajo) */

#define PRINTF_INFO_U(TIPO, MAX, STD_UMAX) \
   printf(  \
      "\t\tRango del tipo [" TIPO "]:\n" \
      "\n" \
      "___Compilador local: 0 <= x <= %ju\n"  \
      "______Esta'ndar C99: 0 <= x <= %ju\n"  \
      "_____Comparar ambos: 0 <= x <= %ju <= %ju\n" \
      "\n", U_CAST(MAX), U_CAST(STD_UMAX), U_CAST(STD_UMAX), U_CAST(MAX) \
   ) /* Cierre de print( ... ) */
   
#define PRINTF_INFO_S(TIPO, MIN, MAX, ABS_STD_SMIN, STD_SMAX) \
   printf(  \
      "\t\tRango del tipo [" TIPO "]: \n" \
      "\n" \
      "___Compilador local: %ji <= x <= %ji\n"  \
      "______Esta'ndar C99: -%ju <= x <= %ji\n"  \
      "_____Comparar ambos: %ji <= -%ju <= x <= %ji <= %ji\n"    \
      "\n", \
      S_CAST(MIN), S_CAST(MAX), \
      U_CAST(ABS_STD_SMIN), S_CAST(STD_SMAX),\
      S_CAST(MIN), U_CAST(ABS_STD_SMIN), S_CAST(STD_SMAX), S_CAST(MAX)  \
   ) /* Cierre de printf( ... ) */

#define PRINTF_INFO_M(TIPO, MIN, MAX, STD_UMAX, ABS_STD_SMIN, STD_SMAX) \
   printf(  \
      "\t\tRango del tipo [" TIPO "]: \n" \
      "\n" \
      "___________________Compilador local: %ji <= x <= %ji\n"  \
      "______Esta'ndar C99 (caso unsigned): 0 <= x <= %ju\n"  \
      "_____________________Comparar ambos: \n" \
      "\n" \
      "\t %ji <= 0 <= x <= %ju <= %ji \n"    \
      "\n" \
      "______Esta'ndar C99 (caso   signed): -%ju <= x <= %ji\n"  \
      "_____________________Comparar ambos: \n" \
      "\n" \
      "\t %ji <= -%ju <= x <= %ji <= %ji\n"   \
      "\n\n", \
      S_CAST(MIN), S_CAST(MAX), \
      U_CAST(STD_UMAX), \
      S_CAST(MIN), U_CAST(STD_UMAX), S_CAST(MAX),  \
      U_CAST(ABS_STD_SMIN), S_CAST(STD_SMAX),\
      S_CAST(MIN), U_CAST(ABS_STD_SMIN), S_CAST(STD_SMAX), S_CAST(MAX)  \
   ) /* Cierre de printf( ... ) */

/*

   Las macros
   
   PRINTF_INFO_U(TIPO, MAX, STD_UMAX)
   PRINTF_INFO_S(TIPO, MIN, MAX, ABS_STD_SMIN, STD_SMAX)
   PRINTF_INFO_M(TIPO, MIN, MAX, STD_UMAX, ABS_STD_SMIN, STD_SMAX)
   
    Reemplazan a la función printf(...) con el objetivo de mostrar
    información con un formato y un contenido muy específico,
    acerca de las constantes enteras del lenguaje C.
    Cualquier otro uso puede dar resultados sin sentido,
    errores de compilación y/o errores de ejecución.
   
    Estas macros muestran información del tipo de datos que se informa en
    el parámetro TIPO, indicando los valores mínimos y máximos admitidos por
    la implementación local, y los extremos exigidos por el estándar C99.
    También muestran una comparación entre ambos rangos de valores.
   
    La macro PRINTF_INFO_U está diseñada para el caso de los tipos unsigned.
    La macro PRINTF_INFO_S está diseñada para el caso de los tipos signed.
    La macro PRINTF_INFO_M está diseñada para el caso de los tipos "mixtos",
    es decir, aquellos casos que el estándar C99 no especifica a priori si son
    unsigned o signed (ejemplo: char).
   
    Se recomienda respetar el uso indicado para cada uno.
    En caso contrario, los resultados obtenidos pueden ser incorrectos.
   
    Ejemplo de uso:
    ===============
   
    Supóngase que localmente el rango de signed char es -32768 <= x <= 32767.
    El estándar exige el rango -127 <= x <= 127, cuando menos.
    La macro se usaría así:

    PRINTF_INFO_U("unsigned int", UINT_MAX, CASI_2_a_la_16);
    PRINTF_INFO_S("signed int", INT_MIN, INT_MAX, CASI_2_a_la_15, CASI_2_a_la_15);
    PRINTF_INFO_M("wint_t", WINT_MIN, WINT_MAX, CASI_2_a_la_16, CASI_2_a_la_15, CASI_2_a_la_15);
   
    La ejecución muestra en pantalla:
    =================================
       
    PRINTF_INFO_U:

                Rango del tipo [unsigned int]:
               
    ___Compilador local: 0 <= x <= 4294967295
    ______Esta'ndar C99: 0 <= x <= 65535
    _____Comparar ambos: 0 <= x <= 65535 <= 4294967295


    PRINTF_INFO_S:

                Rango del tipo [signed int]:

    ___Compilador local: -2147483648 <= x <= 2147483647
    ______Esta'ndar C99: -32767 <= x <= 32767
    _____Comparar ambos: -2147483648 <= -32767 <= x <= 32767 <= 2147483647

    PRINTF_INFO_M:

                Rango del tipo [wint_t]:

    ___________________Compilador local: 0 <= x <= 65535
    ______Esta'ndar C99 (caso unsigned): 0 <= x <= 65535
    _____________________Comparar ambos:

             0 <= 0 <= x <= 65535 <= 65535

    ______Esta'ndar C99 (caso   signed): -32767 <= x <= 32767
    _____________________Comparar ambos:

             0 <= -32767 <= x <= 32767 <= 65535

   
    Significado de los parámetros:
    ==============================
   
    TIPO: Representa el nombre del tipo entero que se analizará.
    MIN: Representa el mínimo valor del tipo TIPO en la implementación local.
    MAX: Representa el máximo valor del tipo TIPO en la implementación local.
    STD_UMAX: Representa el "menor de los valores máximos" aceptados por el
              estándar C99, para el caso de enteros unsigned.

    En todas las implementaciones, los valores x del estándar han de
    cumplir la siguiente relación para tipos enteros, cuando son unsigned:
                   
                      0 <= x <= STD_UMAX <= MAX
   
    ABS_STD_SMIN: Representa el VALOR ABSOLUTO del
                  "mayor de los valores mínimos" aceptados por el estándar C99,
                  para el caso de enteros signed.
    STD_SMAX: Representa el "menor de los valores máximos" aceptados por el
              estándar C99, para el caso de enteros signed.

    En todas las implementaciones, los valores x del estándar han de cumplir
    la siguiente relación para tipos enteros, cuando son signed:
                   
                    MIN <= - ABS_STD_SMIN <= x <= STD_SMAX <= MAX


    Especificaciones de los parámetros:
    ===================================

    TIPO: Este parámetro funcionará correctamente si se coloca allí una string.
          Ejemplo: "signed short int".

    MIN: Requiere una constante entera signed para funcionar como se espera.
         
    MAX: Requiere una constante entera POSITIVA para funcionar como se espera.
         Ha de ser unsigned para PRINTF_INFO_U,
         ha de ser sigbed para PRINTF_INFO_S y PRINTF_INFO_M.

    STD_UMAX, ABS_STD_SMIN, STD_SMAX: Requieren una constante entera de tipo
                                      unsigned long long int,
                                      para funcionar como se espera.
                           
    Nota: STD_SNAX es luego forzado a convertirse a signed long long int,
          por lo tanto el valor numérico de STD_MAX tiene que ser POSITIVO
          y no mayor que 0x7FFFFFFFFFFFFFFFull (\( 2^{63} - 1 \))         
                                     
    Si no se respetan los usos mencionados, no hay garantía de que las macros
    funcionen correctamente.
   
    Información sobre el diseño:
    ============================
   
    Las macros indicadas ejecutan una sentencia printf, con una
    cadena de formato en donde se insertarán números enteros.
    A fin de lograr macros generales, y que funcionen en forma uniforme
    manteniendo un estilo sencillo de programación,
    se procedió a utilizar especificadores de formato %ju y %ji.
    Estos especificadores indican que se esperan datos de
    tipos uintmax_t (caso unsigned) e intmaxt_t (caso signed), respectivamente,
    que son los enteros con máximo rango de valores de la implementación local.
   
    Los casos predeciblemente unsigned serán tratados con %ju,
    y los casos predeciblemente signed serán tratados con %ji.
   
    Pudieron ocurrir ambigüedades si es que aparecen enteros negativos de
    valor absoluto muy grande, razón por la cual el extremo inferior estándar
    de los tipos signados se trata aparte, poniendo un signo negativo (-)
    directamente "a mano" en la cadena de formato del printf,
    y además exigiendo que el valor pasado como parámetro sea el VALOR ABSOLUTO
    con tipo unsigned de dicho extremo inferior.   
    En ese caso, un especificador %ju fue necesario.
   
    A fin de asegurar que los argumentos del printf sean correctamente
    interpretados en la cadena de formato, se realizó un cast forzado "hacia"
    los tipos enteros máximos uintmax_t e intmax_t de la implementación local.
    Esto se lleva a cabo con ayuda de las macros U_CAST y S_CAST.
   
    En los parámetros STD_UMAX, ABS_STD_SMIN, STD_SMAX, se recomienda utilizar
    las constantes definidas más arriba, de nombres:
       
        CASI_2_a_la_N, con N = 7, 8, 15, 16, 31, 32, 63, 64.
        EXACTO_2_a_la_N, con N = 7, 15, 31, 63.
       
    (Son todas de de tipos unsigned long long int).
   
    Funcionamiento esperado:
    ========================
   
    Respetando todas las indicaciones anteriores es de esperar que las macros
    funcionen correctamente para todos los tipos enteros que figuran en forma
    explícita en el estándar C99.

    La única excepción a esta regla puede suceder en un caso improbable,
    aunque teóricamente factible, con los enteros "mixtos":
    Estos enteros tienen la intención en el estándar de tomar valores
    relativamente pequeños respecto unsigned/signed long long int.
    Pero si intmax_t tiene 64 bits en la implementación local (en particular
    coincidirá con signed long long int), mientras que uno de los tipos mixtos
    coincide justo con unsigned long long int,
    es casi seguro que fallará la macro PRINTF_INFO_M.
   
    No obstante, dada la ambigüedad de los tipos "mixtos",
    es muy probable que las implementaciones locales elijan darle un tipo,
    ya sea unsigned o signed, que de cualquier manera "quepa" en intmax_t.
   
*/
         
/* [[-D]] */

/* La macro PAUSE, frena la ejecución del programa y espera a que el usuario
   presione la tecla ENTER. Tras esto, el programa continúa.
*/
   
#define PAUSE getchar()

/* Nota: La macro PAUSE es sólo una solución casera. Puede fallar si antes se
         han hecho operaciones de lectura de datos con scanf, por ejemplo.
         Usar con precaución.
         Si no funciona, descomentar el siguiente trozo de código:
*/

/*

    #include <stdlib.h>
   
    #ifdef PAUSE
      #undef PAUSE
    #endif

   #define PAUSE system("PAUSE")
*/

/* Si se desea que el programa no haga pausas
   ni con getchar() ni con system("PAUSE"),
   descomentar el siguiente trozo de código:
*/

/*
    #ifdef PAUSE
      #undef PAUSE
    #endif

    #define PAUSE ;
*/   

     

int main (void) {
   printf( 
     "\n"
     "\n____________________________________________________________________\n"
     "\n"
     "Ana'lisis de rangos de tipos de datos enteros.\n"
     "\n____________________________________________________________________\n"
     "\n"
     "Si alguna comparacio'n entre el rango exigido por el esta'ndar y el\n"
     "rango del presente compilador resultara erro'nea,\n"
     "Por favor avise al creador de su compilador!!!!\n"
     "\n"
     "\n____________________________________________________________________\n"
     "\n\n"           
    );  /* Cerramos paréntesis de printf(...) */

    printf(           
            "1ra. Parte: Tipos ba'sicos (unsigned).\n"
            "===========\n"
            "\n"
        );
       
   PRINTF_INFO_U("unsigned char",          UCHAR_MAX,  CASI_2_a_la_8);   
   PRINTF_INFO_U("unsigned short int",     USHRT_MAX,  CASI_2_a_la_16);
   PRINTF_INFO_U("unsigned int",           UINT_MAX,   CASI_2_a_la_16);
   PRINTF_INFO_U("unsigned long int",      ULONG_MAX,  CASI_2_a_la_16);
   PRINTF_INFO_U("unsigned long long int", ULLONG_MAX, CASI_2_a_la_64);
   
   printf( "Adema's los rangos de los tipos que aparecen primero, deben\n"
           "estar contenidos en los rangos de los tipos que aparecen despues's.\n"
           "\n"
    );
   
   PAUSE;
   
   printf(  "\n"
            "2da. Parte: Tipos ba'sicos (signed).\n"
            "===========\n"
            "\n"
        ); 

   PRINTF_INFO_S("signed char",          SCHAR_MIN, SCHAR_MAX, CASI_2_a_la_7,  CASI_2_a_la_7);
   PRINTF_INFO_S("signed short int",     SHRT_MIN,  SHRT_MAX,  CASI_2_a_la_15, CASI_2_a_la_15);
   PRINTF_INFO_S("signed int",           INT_MIN,   INT_MAX,   CASI_2_a_la_15, CASI_2_a_la_15);
   PRINTF_INFO_S("signed long int",      LONG_MIN,  LONG_MAX,  CASI_2_a_la_15, CASI_2_a_la_15);
   PRINTF_INFO_S("signed long long int", LLONG_MIN, LLONG_MAX, CASI_2_a_la_63, CASI_2_a_la_63);
   
   printf( "Adema's los rangos de los tipos que aparecen primero, deben\n"
           "estar contenidos en los rangos de los tipos que aparecen despues's.\n"
           "\n"
    );

   PAUSE;
   
   printf(  "\n"
            "3ra. Parte: Tipo char.\n"
            "===========\n"
            "\n"
        ); 

   PRINTF_INFO_M("char", CHAR_MIN, CHAR_MAX, CASI_2_a_la_8, CASI_2_a_la_7, CASI_2_a_la_7);
   
   printf( "El rango de char debe coincidir con signed char, si es signed,\n"
           "y con unsigned char, si es unsigned.\n"
           "\n"
        );
   
   PAUSE;

   printf(  "\n"
            "4ta./5ta. Partes: Tipos de longitud fija.\n"
            "=================\n"
            "\n"
        ); 

   PRINTF_INFO_U("uint8_t",  UINT8_MAX,   CASI_2_a_la_8);
   PRINTF_INFO_U("uint16_t", UINT16_MAX,  CASI_2_a_la_16);
   PRINTF_INFO_U("uint32_t", UINT32_MAX,  CASI_2_a_la_32);
   PRINTF_INFO_U("uint64_t", UINT64_MAX,  CASI_2_a_la_64);
   
   PRINTF_INFO_S("int8_t",  INT8_MIN,  INT8_MAX,  EXACTO_2_a_la_7,  CASI_2_a_la_7);
   PRINTF_INFO_S("int16_t", INT16_MIN, INT16_MAX, EXACTO_2_a_la_15, CASI_2_a_la_15);
   PRINTF_INFO_S("int32_t", INT32_MIN, INT32_MAX, EXACTO_2_a_la_31, CASI_2_a_la_31);
   PRINTF_INFO_S("int64_t", INT64_MIN, INT64_MAX, EXACTO_2_a_la_63, CASI_2_a_la_63);
 
   PAUSE;
   
   printf(  "\n"
            "6ta. Parte: Tipos de longitud mi'nima prefijada (unsigned).\n"
            "===========\n"
            "\n"
        ); 

   PRINTF_INFO_U("uint_least8_t",  UINT_LEAST8_MAX,   CASI_2_a_la_8);
   PRINTF_INFO_U("uint_least16_t", UINT_LEAST16_MAX,  CASI_2_a_la_16);
   PRINTF_INFO_U("uint_least32_t", UINT_LEAST32_MAX,  CASI_2_a_la_32);
   PRINTF_INFO_U("uint_least64_t", UINT_LEAST64_MAX,  CASI_2_a_la_64);
   
   printf( "Adema's los rangos de los tipos que aparecen primero, deben\n"
           "estar contenidos en los rangos de los tipos que aparecen despues's.\n"
           "\n"
    );
   
   PAUSE;
   
   printf(  "\n"
            "7ma. Parte: Tipos de longitud mi'nima prefijada (signed).\n"
            "===========\n"
            "\n"
        ); 

   PRINTF_INFO_S("int_least8_t",  INT_LEAST8_MIN,  INT_LEAST8_MAX,  EXACTO_2_a_la_7,  CASI_2_a_la_7);
   PRINTF_INFO_S("int_least16_t", INT_LEAST16_MIN, INT_LEAST16_MAX, EXACTO_2_a_la_15, CASI_2_a_la_15);
   PRINTF_INFO_S("int_least32_t", INT_LEAST32_MIN, INT_LEAST32_MAX, EXACTO_2_a_la_31, CASI_2_a_la_31);
   PRINTF_INFO_S("int_least64_t", INT_LEAST64_MIN, INT_LEAST64_MAX, EXACTO_2_a_la_63, CASI_2_a_la_63);
   
   printf( "Adema's los rangos de los tipos que aparecen primero, deben\n"
           "estar contenidos en los rangos de los tipos que aparecen despues's.\n"
           "\n"
    );
   
   PAUSE;
   
   printf(  "\n"
            "8va. Parte: Tipos ra'pidos de longitud mi'nima prefijada (unsigned).\n"
            "===========\n"
            "\n"
        ); 

   PRINTF_INFO_U("uint_fast8_t",  UINT_FAST8_MAX,  CASI_2_a_la_8);
   PRINTF_INFO_U("uint_fast16_t", UINT_FAST16_MAX, CASI_2_a_la_16);
   PRINTF_INFO_U("uint_fast32_t", UINT_FAST32_MAX, CASI_2_a_la_32);
   PRINTF_INFO_U("uint_fast64_t", UINT_FAST64_MAX, CASI_2_a_la_64);
   
   printf( "Adema's los rangos de los tipos que aparecen primero, deben\n"
           "estar contenidos en los rangos de los tipos que aparecen despues's.\n"
           "\n"
    );
   
   PAUSE;
   
   printf(  "\n"
            "9na. Parte: Tipos ra'pidos de longitud mi'nima prefijada (signed).\n"
            "===========\n"
            "\n"
        ); 

   PRINTF_INFO_S("int_fast8_t",  INT_FAST8_MIN,  INT_FAST8_MAX,  EXACTO_2_a_la_7,  CASI_2_a_la_7);
   PRINTF_INFO_S("int_fast16_t", INT_FAST16_MIN, INT_FAST16_MAX, EXACTO_2_a_la_15, CASI_2_a_la_15);
   PRINTF_INFO_S("int_fast32_t", INT_FAST32_MIN, INT_FAST32_MAX, EXACTO_2_a_la_31, CASI_2_a_la_31);
   PRINTF_INFO_S("int_fast64_t", INT_FAST64_MIN, INT_FAST64_MAX, EXACTO_2_a_la_63, CASI_2_a_la_63);
   
   printf( "Adema's los rangos de los tipos que aparecen primero, deben\n"
           "estar contenidos en los rangos de los tipos que aparecen despues's.\n"
           "\n"
    );
   
   PAUSE;
   
   printf(  "\n"
            "10ma. Parte: Tipos ma'ximos y asociados a punteros.\n"
            "============\n"
            "\n"
        ); 

   PRINTF_INFO_U("uintmax_t", UINTMAX_MAX, CASI_2_a_la_64);
   PRINTF_INFO_S("intmax_t",  INTMAX_MIN, INTMAX_MAX,  EXACTO_2_a_la_63, CASI_2_a_la_63);
   PRINTF_INFO_U("uintptr_t", UINTPTR_MAX, CASI_2_a_la_16);
   PRINTF_INFO_S("intptr_t",  INTPTR_MIN, INTPTR_MAX,  EXACTO_2_a_la_15, CASI_2_a_la_15);
   
   printf( "Adema's los rangos de todos los tipos signados deben encajar en intmax_t,\n"
           "y los rangos de todos los tipos no signados deben encajar en uintmax_t.\n"
           "\n"
    );
   
   PAUSE;
   
   printf(  "\n"
            "11va. Parte: Tipos especiales.\n"
            "============\n"
            "\n"
        ); 

   PRINTF_INFO_U("size_t",    SIZE_MAX, CASI_2_a_la_16);
   PRINTF_INFO_S("ptrdiff_t", PTRDIFF_MIN, PTRDIFF_MAX, CASI_2_a_la_16, CASI_2_a_la_16);
   PRINTF_INFO_M("sig_atomic_t (caso unsigned)", SIG_ATOMIC_MIN, SIG_ATOMIC_MAX, CASI_2_a_la_8, CASI_2_a_la_7, CASI_2_a_la_7);
   PRINTF_INFO_M("wchar_t (caso unsigned)", WCHAR_MIN, WCHAR_MAX, CASI_2_a_la_8,  CASI_2_a_la_7,  CASI_2_a_la_7);
   PRINTF_INFO_M("wint_t (caso unsigned)",  WINT_MIN,  WINT_MAX,  CASI_2_a_la_16, CASI_2_a_la_15, CASI_2_a_la_15);
   
   printf( "Adema's el rango de wchar_t debe encajar en el de wint_t.\n"
           "\n"
    );
   
   PAUSE;

}



[cerrar]


 ;D

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

15 Enero, 2013, 04:56 am
Respuesta #17

argentinator

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

   Antes de abordar el tema de los números reales y complejos en C, necesitamos estudiar el estándar ISO/IEC/IEEE 60559:2011.

   ¿Otro estándar?  :'( :'( :'( ¡Oh, por Dios!  :banghead: :banghead: :banghead:

   Bueno, penas aparte, empecemos.

   Los números en punto flotante (o en coma flotante), en inglés: floating-point, son un tipo especial de números que se definen para uso en computación, que intentan representar el cálculo con números reales.

   De entrada digamos que las computadoras, sin importar cuál sea su arquitectura interna, son incapaces de albergar números cualesquiera con infinitos dígitos detrás del punto de fracción. Esto se debe a varias razones.
   Primero, si los números se albergan o procesan como datos codificados con una cantidad fija o máxima de bits, digamos \( N \), entonces no pueden representarse más que \( N \) dígitos binarios de un número cualquiera de punto flotante.
   Si se intenta sortear esta dificultad usando otros métodos de guardar números en una máquina, se requerirá seguramente un método para memorizar sus dígitos en algún dispositivo de memoria, y en todo momento la cantidad de memoria disponible de una máquina está acotada.

   En particular, habrá números reales que no podrán representarse, y así muchos cálculos darán resultados aproximados por redondeo.
   Al hacer esto, inevitablemente se introducen cálculos que son matemáticamente erróneos, porque ya son aproximaciones y no datos exactos.

   Asumiendo esta problemática, se debe pensar en situaciones de error típicas producidas por estos redondeos y cálculos con pequeños errores.
   Es así que se habla de la precisión de un número de punto flotante.

   Cuanto más dígitos puedan representarse en el conjunto de números de punto flotante de una máquina, se dirá que son más precisos.
   La precisión, pues, se refiere a la cantidad de dígitos que un dato numérico tiene asignado en el interior del sistema (hardware+software) que estemos utilizando.

   Sin embargo creo conveniente de aquí en más usar dos palabras distintas a este respecto, con un significado bien delimitado.
   La palabra precisión se referirá a la cantidad de dígitos significativos que un cierto formato de números es capaz de almacenar, mientras que la palabra exactitud se referirá a qué tan buenos son los valores aproximados a un determinado valor considerado exacto.

   Por otro lado, los números en punto flotante están usualmente estructurados en 3 partes bien especificadas:

Signo (\( \sigma \)), mantisa (\( \mu \)) y exponente (\( r \)).

   Así, un número de punto flotante tendrá la forma: \( \sigma \mu \cdot b^r \), en donde \( b \) representa la base de numeración utilizada (para las computadoras suele ser \( b=2 \), y para los humanos \( b=10 \)).
   La mantisa tiene esta forma: \( n.dddddd... \), en donde queremos expresar que hay un único dígito \( n \), que luego está seguido por un punto fraccionario, y detrás el resto de los dígitos.

   Es la típica notación científica o exponencial. El exponente \( r \) indica entre qué potencias sucesivas de la base se halla el número en cuestión.

   Así, para decir cuántos planetas de tamaño similar a la Tierra hay en la galaxia, lo indicamos con \( 1.7\cdot 10^{10}  \), que quiere decir que hay 10 dígitos aún detrás del 1, antes de llegar a la parte fraccionaria, con lo cual estamos hablando de un número en el orden de los 10 mil millones. (Precisamente, son 17 mil millones los del ejemplo).

   Si queremos expresar cuántos gramos pesa un electrón, decimos que son \( 9.10952\cdot 10^{-28} \), lo cual indica que detrás del punto fraccionario hay 27 ceros antes de llegar a los dígitos 910952 de nuestro número.

   La notación científica permite, pues, que podamos representar números de tamaños muy grandes o muy pequeños.
   El problema aquí es que, si bien la promesa es grande, el alcance de esto no lo es tanto.

   Supongamos que nuestro sistema tiene una precisión de 16 dígitos decimales, y que permite expresar números en notación científica tan grandes como los cincuentillones (del orden de \( 10^{300} \)).

   En tal caso, si bien podemos "hablar" de cantidades grandes en nuestra máquina, no podemos acceder a las 300 cifras que estarían antes del punto fraccionario.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Las leyes de los exponentes nos dan las siguientes reglas para cálculos con estos números:

\( (\mu\cdot  b^r)(\nu\cdot  b^s)=\mu\nu\cdot  b^{r+s} \)
\( (\mu\cdot  b^r)/(\nu\cdot  b^s)=\mu\nu\cdot  b^{r-s} \)
\( (\mu\cdot  b^r)^s=\mu^s\cdot  b^{rs} \)

   Como se ve, los cálculos de productos, cocientes y potencias resultan bastante cómodos en el formato exponencial.
   Se hace difícil, en cambio, establecer relaciones claras entre mantisas y exponentes, al efectuar sumas y restas.

   En cualquier caso, siempre habrá redondeos automáticos efectuados por la máquina, que nos obligarán a ir arrastrando errores.
   Este tipo de errores de imprecisión en los cálculos suelen denominarse errores numéricos. Es un nombre que se refiere al efecto de acumular errores con la computadora, tras una serie de varios cálculos, cada uno con su contribución al desastre.
   Se trata de una realidad de las computadoras, inevitable (al menos a nivel de hardware), y que conviene que reflexionemos sobre ello.

¿Por qué hay que reflexionar sobre esto? Bueno, porque el modo en que el lenguaje C representa los números reales sigue el formato de los números con punto flotante, y adolece de los problemas comunes a estos números.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Hemos visto también que el grado de exactitud en los cálculos con números en punto flotante depende de la cantidad fija de dígitos que se utilicen en su representación.

   La exactitud depende del hardware y del software.
   Cada sistema (combinación de hardware y software) tendrá sus propios números de punto flotante.
   Peor todavía, cada procesador matemático tendrá sus propios criterios sobre cómo hacer redondeos, u otras convenciones sobre los cálculos y el manejo de los errores numéricos.

   En consecuencia, si hacemos un programa que nos da determinados resultados en una máquina, puede que nos dé otros resultados en otra máquina distinta, dependiendo de cuánta precisión y cuáles convenciones tenga cada una.

   Los microprocesadores, corazón de las computadoras, suelen tener adosado un coprocesador matemático que, entre otras cosas, se encarga de realizar los cálculos con números en punto flotante.
   A fin de obligar a los fabricantes a mantener la mayor compatibilidad posible, es que se ha establecido un estándar sobre los números en punto flotante.
   Es el ISO/IEC/IEEE 60559:2011. También se le conoce como estándar IEEE 754. Ambos tienen el mismo contenido.

   Este estándar no se refiere a ningún lenguaje en particular, sino a especificaciones sobre los sistemas (hardware+software) de computación de cálculo matemático.
   A su vez, el estándar del lenguaje C define tipos de datos de punto flotante que procuran respetar al pie de la letra el estándar ISO/IEC/IEEE 60559:2011.
   Es importante pues, para nosotros, conocer los detalles que dan ambos estándares respecto a los números en punto flotante.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Nuestro plan para el futuro es como sigue:

\( \bullet \)   Estudiar el estándar IEEE 754 de formatos de punto flotante, y conocer sus mínimos requerimientos obligatorios y sugerencias.
\( \bullet \)   Estudiar cómo el estándar C99 del lenguaje C impone reglas sobre los tipos de datos floating point, qué relación tienen con el estándar IEEE 754, y qué exigencias adicionales presenta el lenguaje C.
\( \bullet \)   Entender nuestro particular compilador GCC, el cual puede que no respete ni el estándar de IEEE 754, ni el estándar de C99, y que incluso tenga características propias. Debemos analizar los detalles para estar prevenidos.

   De hecho, en la versión actual de GCC, su punto más problemático en relación al estándar C99 es justamente el de los tipos de punto flotante.
   Tendremos que tener la mente abierta para esperar problemas de incompatibilidad, entender sus motivos, y ver las soluciones o alternativas que hay a mano.
   Este ciclo de 3 etapas se va a repetir varias veces, cada vez que tengamos que estudiar un nuevo tópico sobre los números de punto flotante.

   También tendremos ocasión de estudiar los detalles matemáticos de los cálculos en punto flotante, y analizaremos los detalles finos de las operaciones aritméticas con esos números.
   El tema de los números en punto flotante se ve, pues, que es arduo de manejar con absoluta exactitud.
   No tienen la sencillez de los números enteros, y hay involucrados temas profundos de las ciencias de la computación].

   Pero todo a su debido tiempo. No se extrañen que vaya dejando algunos temas postergados.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

La bibliografía que consultaremos para este tema es la siguiente:

\( \bullet \)  IEEE Standard for Binary Floating-Point Arithmetic

   La versión de 1985 que contempla el caso de los formatos binarios, lo incluimos como adjunto a este post.

\( \bullet \)  What Every Computer Scientist Should Know About Floating-Point Arithmetic, David Goldberg (March 1991). ACM Computing Surveys 23 (1): 5–48.

   Utilizaremos de ese trabajo el apéndice D, el cual incluimos como archivo adjunto en este post.

http://en.wikipedia.org/wiki/IEEE_754-2008
http://en.wikipedia.org/wiki/Half_precision_floating-point_format
http://en.wikipedia.org/wiki/Single_precision_floating-point_format
http://en.wikipedia.org/wiki/Double_precision_floating-point_format
http://en.wikipedia.org/wiki/Quadruple_precision_floating-point_format
http://en.wikipedia.org/wiki/Decimal32_floating-point_format
http://en.wikipedia.org/wiki/Decimal64_floating-point_format
http://en.wikipedia.org/wiki/Decimal128_floating-point_format

   Sobre el estado actual del compilador GCC respecto C99, se puede consultar una tabla aquí:

\( \bullet \)  http://gcc.gnu.org/c99status.html


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC



Organización

Comentarios y Consultas

15 Enero, 2013, 06:32 am
Respuesta #18

argentinator

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

   El estándar internacional ISO/IEC/IEEE 60559:2011 tiene idéntico contenido al estándar IEEE 754.
   Lo podemos encontrar en la literatura de las dos formas, y nosotros usaremos la segunda nomenclatura sólo por brevedad.

   La bibliografía que hemos de utilizar está indicada en el post anterior.

   Cuando el estándar define un formato de números de punto flotante, no indica cómo tienen que representarse exactamente en hardware (el procesador), software (el sistema operativo, o programas ejecutables, o el lenguaje o el compilador), o una combinación de ambos.
   Al mismo tiempo, el estándar está basado en la experiencia de funcionamento de los procesadores y diversos sistemas en cuanto al manejo de los números de punto flotante.
   Cada fabricante tiene sus propios y válidos criterios, pero un estándar intenta uniformar estos criterios, a fin de que los resultados sean compatibles entre distintos sistemas de computación, sin importar el hardwara o el software utilizado. 

\( \bullet \)   Un formato IEEE 754 se refiere a un conjunto de números, y posibles reglas de representación, reglas de redondeo y otros cálculos entre ellos.

   Los formatos se dividen en básicos y no básicos, y el estándar da indicaciones para ambos, aún cuando los no básicos no los define con exactitud.
\( \bullet \)   Los formatos básicos de base 2 se denominan binary32, binary64, binary128, y les corresponden tamaños en memoria de 32, 64 y 128 bits respectivamente.
\( \bullet \)   Los formatos básicos de base diez se denominan decimal64, decimal128.

   No obstante, hablaremos en abstracto de lo que se supone que es un formato básico, y en lo que sigue procuramos explicar en detalle las especificaciones del estándar para formatos tanto básicos como formatos cualesquiera.

   En cualquier caso, un tal conjunto de números es un subconjunto de los números reales, que está determinado por los siguientes parámetros globales:

\( b \): base
\( p \): precisión
\( emin \): exponente mínimo
\( emax \): exponente máximo

   La base limita el alfabeto de dígitos que se pueden usar. Quiere decir que se usan sólo los números enteros de \( 0 \) a \( b-1 \) como dígitos.
   Si la base es \( b = 2 \) (binaria), estamos en el típico caso de dígitos binarios de las computadoras: 0 y 1.
   Si \( b = \textsf{diez} \) (decimal), estamos ante la forma humana de representación, con dígitos que van del 0 al 9.
   Aparentemente el estándar no da indicaciones para bases distintas que la binaria y la decimal. Podría pensarse que, en relación a la base binaria, sería buena idea decir algo sobre las bases octal y hexadecimal. Veremos luego que conviene "quedarse a vivir" en la base binaria.

   Recordemos la notación científica: \( \sigma\cdot m \cdot b^r \),  donde tenemos un signo \( \sigma \), una mantisa \( m \) y un exponente \( r \).

   La precisión \( p \) es la cantidad de dígitos que se admitirán en una mantisa. Este número es fijo por cada conjunto de números de punto flotante. También se dice que \( p \) es la cantidad de dígitos significativos.
   Los coeficientes \( e_{min} \) y \( e_{max} \) se entenderán cuando estudiemos otros detalles.

   Los elementos del conjunto que definen un formato dado se clasifican en: números finitos, infinitos, NaN (Not-a-Number: un-no-número).
   A su vez, los números finitos se dividen en números normalizados y números subnormales (o denormalizados).
                                                                                             
   Un número finito del conjunto de números de punto flotante indicado por \( (b,p,emin,emax) \), estará determinado por 3 números enteros \( (s,m,r) \), de la siguiente manera:

\( s \): signo
\( m \): mantisa (en realidad el estándar le llama significando)
\( q \): exponente

   Son tales que \( s=0 \) ó \( 1 \), \( m \) es un entero no negativo, y \( q \) es un entero. Representan el número:

\( \bullet \)         \( (-1)^s\cdot m\cdot b^q \)

   Obviamente, \( s=0 \) da signo positivo, y \( s=1 \) da signo negativo: \( (-1)^0=+1, (-1)^1=-1 \).
   Es incómodo que se le llame signo, a lo que en realidad es un exponente que te ayuda a obtener el signo. Pero tomémoslo como viene sin discutir demasiado...  :-\

   Procedamos ahora con sumo cuidado.  :o :o :o :-*
   Las mantisas tienen la forma \( d_0.d_1...d_{p-1} \), donde cada \( d_k \) es un dígito binario (0 ó 1).
   Aquellos valores cuyas mantisas tienen el dígito líder \( d_0=1 \), son los números normalizados.
   Aquellos que tienen \( d_0 = 0 \) son los subnormales.
   En cualquier caso, se denomina fracción, y se denota con \( f \), a la parte que está a la derecha del punto fraccionario. Así:

\( f = .d_1...d_{p-1}. \)

   Observemos que todo número binario escrito en notación científica se puede escribir de forma normalizada, es decir, con \( d_0 = 1 \).
   Eso hace que almacenar en memoria el dígito líder 1 sea redundante, ya que siempre está implícitamente en ese caso.

   El estándar exige que los formatos básicos binarios (¡ojo! esto no se exige para los no básicos) representen a los valores normalizados

\( \pm (1.d_1...d_{p-1}) 2^{e} \), con \( e_{min}\leq e\leq e_{max} \),

de manera que el primer dígito 1 sea considerado implícito. O sea que no se almacena ese bit en memoria real.
   Sólo se almacenan los bits de la fracción \( f \). Así, hablaremos del tamaño (o ancho, width) en bits de la fracción, la cual será en este caso 1 menos que la precisión \( p \).

   Ahora bien, los números de la forma \( \pm (0.d_1...d_{p-1}) 2^{e_{min}} \) serán los subnormales.
   Sin embargo, tienen que tener una representación distinta, porque una fracción \( f = .d_1...d_{p-1} \) con un exponente \( e_min \), representa como ya vimos al número normalizado \( 1.d_1...d_{p-1} 2^{e_{min}} \).
   Para los números subnormales se usarán, pues, las potencias con exponente \( e_{min}-1 \), conviniendo que una terna \( (s, m, e_{min}-1), \quad(s=\textsf{0 ó 1}) \), donde \( m = d_1...d_{p-1} \), codificará un número subnormal de la forma\(  \pm (0.d_1...d_{p-1}) 2 ^{e_{min}} \).

   Nótese que, con estas convenciones, todo número finito distinto de cero, ya sea normal o subnormal, tiene una única codificación posible.
   Esto asegura que no hay ambigüedad en el formato. Hay una excepción a esto, y la constituyen los ceros signados.

   Cuando los dígitos \( d_1,...,d_{p-1} \) son todos 0 y el exponente es \( e_{min}-1 \), se conviene que representa el valor matemático 0.
   Sin embargo, aquí hay ambigüedad, porque todavía hay un bit de signo.
   Si \( s = 0 \), se considera que el valor 0 es positivo, y se expresa \( +0 \), mientras que si \( s = 1 \), el valor 0 es negativo, y se expresa \( -0 \).
   La notación \( +0, -0, \) es sólo una abreviatura, que de paso nos recuerda que "internamente" coexisten dos representaciones distintas del mismo valor 0.
   Desde el punto de vista aritmético y lógico, no hay diferencia entre ellos: se consideran el mismo número 0, que no tiene signo alguno.

   Observemos también que si todos los dígitos son 0, pero \( e \geq e_{min} \), el número representado no es 0, sino \( \pm 2^e \) (el signo depende, claro, del bit de signo).

   Ahora hablemos un poco de cómo se codifica el exponente \( e \).
   Si \( e_{min} \leq e \leq e_{max} \), se utiliza una representación sesgada o descentrada (biased).
   Sea \( H = e_{max} - e_{min}+1 \). Los números de \( 1 \) hasta \( H \) codificarán exponentes desde \( e_{min} \) hasta \( e_{max} \), de la siguiente manera:
   El número \( \tilde e \) codifica el exponente \( e - H \).
   El número \( H \) en inglés se llama bias. Podríamos traducirlo como el "sesgo", el "corrimiento".
   De esta manera, tenemos la relación \( e = \tilde e + H \).
   Especial cuidado ha de tenerse al sumar y restar exponentes sesgados, ya que debe tenerse en cuenta que los números \( \tilde e \) representan un valor distinto a ellos mismos.
   No voy a apabullarles con ejemplos. En todo caso, podemos retomar esto después.

   En cambio, \( \tilde e = 0 \) se usará para guardar los ceros signados y los números subnormales.

   El exponente descentrado \( \tilde e = H+1 \) se usará para los valores infinitos y los NaN.

   Hablemos ahora de las convenciones del estándar acerca de los infinitos y los valores NaN.

\( \bullet \)   En todo formato debe h aber "al menos" un valor \( -\infty \) y uno \( +\infty \).
\( \bullet \)   No se exige que haya otrosposibles valores "infinitos".

   En los formatos básicos binarios se especifica además cómo tiene que ser la codificación en bits de estos valores infinitos (al menos, de los dos que se exigen).
   Ellos utilizan el exponente \( e = e_{max}+1 \), que es lo mismo que decir que usan el exponente descentrado \( \tilde e = H+1 \).
   Más precisamente, si tenemos la terna \( (s, m, e) \) donde \( m = 0...0 \), \( e = e_{max}+1 \) (o sea, \( \tilde e = H+1 \)), el valor representado es \( (-1)^s \infty \).

   En cuanto a los valores NaN, el estándar exige que todo formato contenga "al menos" un valor NaN silencioso y "al menos" un valor NaN señalizador.
   Los NaN se deben representar como una terna \( (s, m, e) \), donde \( m \neq 0...0 \), y \( e = e_{max+1} \) (o sea, \( \tilde e =H+1 \)).
   El estándar no obliga a que el signo \( s \) de un valor NaN tenga significado.
   Así, valores NaN con signos opuestos representarían el mismo valor NaN.
   Los valores NaN surgen en operaciones inválidas. Estudiaremos esto más adelante.
   Además, los valores NaN señalizadores son capaces de arrastrar alguna información sobre la circunstancia que los generó.

   Se aprecia que son posibles muchos valores NaN, uno por cada valor de \( m \) (distinto de 0).
   No quiere decir esto que todos estos posibles valores estén asignados a un NaN.
   Estas asignaciones dependen de la implementación.

   Dado que los valores infinitos y los valores NaN comparten el mismo exponente \( e = e_{max}+1 \), una implementación particular podría querer usar algunos de esos valores para representar nuevos valores de infinitos (por ejemplo, infinitos obtenidos en cálculos con números complejos, o quizá, más probablemente, distintos niveles de overflow).
   En ese caso, habría que especificar con claridad cuáles valores se usan para infinitos, y cuáles para NaNs. 

   Como vemos, aparecen dentro del conjunto de valores posibles unos entes extraños, que encima se clasifican, y reaccionan a quién sabe qué procesos... la cosa ya se complicó.  :-\

   Por ahora, nosotros sólo estamos haciendo el recuento de los valores posibles que puede tomar un conjunto de números de un formato floating point dado por el estándar.
   Les podemos seguir diciendo "números" aunque haya objetos allí que matemáticamente no son números: los infinitos y los NaN.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Además de los valores antes especificados, el estándar exige que en un formato dado existan, "al menos", 5 tipos de señales, asociadas a errores de diversa índole.
   Estas señales no se codifican como los valores \( (s, m, e) \) que hemos explicado, sino que se deben indicar como "banderines" (flags) que andan "por ahí".
   Se supone que el sistema utilizado (hardware/software) tiene "por ahí" unos bits de estado que indican si las señales de error están activas o no.
   Los 5 tipos de señales exigidas por el estándar son:

Operación inválida (invalid operation).
División por cero  (division by zero).
Desbordamiento     (overflow).
Rebase por defecto (underlow).
Inexacto           (inexact).

   Es importante conocer las nomenclaturas en inglés porque son la manera típica en que aparecen comunmente en la literatura.

Las situaciones que originan esas señales, y lo que el estándar exige que se haga en consecuencia, incluyendo los valores que deben tomar los resultados de las operaciones bajo esas circunstancias, son un tema que estudiaremos más adelante.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   El estándar también sugiere que se definan procedimientos para captura y manejo de errores, así como la existencia de ciertas funciones específicas para valores de punto flotante.
   No vamos a explicar esto en este momento. Sólo enfatizamos que se trata de sugerencias, y no reglas de los formatos del estándar.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Cuando utilizamos base 2, los formatos básicos del estándar aprovechan el bit extra que se gana en la forma normalizada.
   Ese bit se denomina oculto, y así la precisión se expresa como el número de bits que se usan en memoria más 1, el bit oculto.
   El estándar define cinco formatos básicos y dos no básicos, cada uno determinando su correspondiente conjunto de números de punto flotante.


binary16:   base = b = 2,  precisión = p = 10+1,  emin = -14,    emax = +15.
binary32:   base = b = 2,  precisión = p = 23+1,  emin = -126,   emax = +127.
binary64:   base = b = 2,  precisión = p = 52+1,  emin = -1022,  emax = +1023
binary128:  base = b = 2,  precisión = p = 112+1, emin = -16382, emax = +16383.
decimal32:  base = b = 10, precisión = p = 7,     emin = -95,    emax = +96.
decimal64:  base = b = 10, precisión = p = 16,    emin = -383,   emax = +384.
decimal128: base = b = 10, precisión = p = 34,    emin = -6143,  emax = +6144.


Los formatos binarios se conocen con los siguientes nombres:

binary16  Half Precision      (Mitad de Precisión Simple).
binary32  Single Precision    (Precisión Simple).
binary64  Double Precision    (Precisión Doble).
binary128 Quadruple Precision (Precisión Cuádruple Binaria).

   El tamaño (o ancho, width) \( w \) de un formato básico binario binaryXX es de XX bits, como su nombre indica.
   Se usa 1 bit para el signo, \( p \) bits para la fracción de la mantisa, y los bits restantes para el exponente.
   El estándar también impone reglas sobre las operaciones aritméticas que deben estar definidas para un determinado formato, funciones matemáticas que deben estar definidas, conversiones entre formatos de punto flotante distintos, conversiones entre formatos de punto flotante y tipos de datos enteros, reglas de redondeo de punto flotante a número entero, conversiones de binario a decimal y viceversa, reglas de comparación (o sea, definición de una relación de orden), aritmética infinita, aritmética con NaN, y reglas para el bit de signo.

(Todo esto será analizado en el momento oportuno. Hacerlo aquí sería muy tedioso.)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Para terminar esta exposición necesitamos hablar de los formatos extendidos.

El estándar define dos formatos denominados extendidos, de precisión simple y doble.
Sus requerimientos son los siguientes:


Extended Single precision: \( p \geq 32, e_{max}\geq +1023, e_{min} \leq -1022, w_e \geq 11, w\geq 43. \)
Extended Double precision: \( p \geq 64, e_{max}\geq +16383, e_{min} \leq -16382, w_e \geq 15, w\geq 79. \)


   Hemos indicado con \( w_e \) la cantidad de bits que se usa para representar el exponente.
   Con \( w \) indicamos el ancho total en bits del formato.
   El estándar NO EXIGE a los formatos extendidos que utilicen la convención del bit implícito, o sea, no presuponen la forma normalizada para los valores finitos.
Esto puede hacer que se pierda 1 bit de precisión respecto a lo que uno esperaría.
   Tampoco se especifica cuál ha de ser el modo en que se representan los exponentes, y así el sesgo (bias), no está especificado.
   Como se ve arriba, tampoco está especificado el tamaño exacto del formato.
   Tan sólo hay valores mínimos para la precisión \( p \), el exponente máximo \( e_{max} \), el ancho del exponente \( w_e \), y valores máximos para el exponente mínimo \( e_{min} \).
   El ancho total \( w \) del formato será la suma de esos valores más 1 bit de signo: \( w = 1+p+w_e \).

   ¿Qué sentido tiene que un estándar tenga tantas imprecisiones? ¿Qué función cumplen los formatos extendidos?
   La idea detrás de los formatos extendidos es ayudar en la exactitud de los cálculos con los formatos básicos.
   Recordemos por ejemplo lo que ocurre con las calculadoras de bolsillo. En ellas se muestran diez dígitos en el visor de resultados, mientras que internamente se realizan los cálculos con 2 o 3 dígitos más que no se muestran.
   Estos dígitos extra permiten disimular mejor la pérdida de exactitud que se tiene por los errores de redondeo, inevitables en un sistema que usa una cantidad fija de dígitos.

   Así, el Extended Single precision: ayudará a mantener mejor exactitud en los cálculos que involucren valores del formato Single Precision, y el Extended Double Precision hará lo propio con el Double Precision.
   Sin embargo, el estándar procura no obligar a ningún fabricante, programador o sistema, a caber en un formato específico y rígido, y así sólo establece lo mínimo que requiere un formato extendido.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

¿Son obligatorios los formatos básicos y extendidos?

   Los formatos binary16 y binary32 existen para propósitos de almacenamiento de datos numéricos, antes que para propósitos de cálculo.
   Luego, se consideran formatos básicos a los cinco restantes: binary32, binary64, binary128, decimal64, decimal128.
   El estándar sólo obliga a un sistema dado a implementar uno solo "al menos" de los formatos básicos. (¿Binarios, decimales, cualquiera? Debo revisar este detalle).
   No obliga a utilizar ninguno de los formatos extendidos.
   Sin embargo, sugiere fuertemente que se incorpore un formato extendido cuyo ancho sea estrictamente mayor que el más ancho de los formatos básicos presentes en el sistema.
   Por ejemplo, si en nuestro sistema se soporta el Double Precision como máximo formato básico, entonces se recomienda que haya al menos un Extended Double Precision presente.
   El estándar además dice que, una vez presente este formato extendido, no hace falta ningún otro en el sistema.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

17 Enero, 2013, 12:31 am
Respuesta #19

argentinator

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

   Veamos lo que dice el estándar C99 del lenguaje C, acera de los números de punto flotante.

Ante todo, debemos tener paciencia...  :'(

   En primer lugar se define en abstracto aquello que ha de considerarse un modelo para los tipos de punto flotante.
   Esto da la definición matemática de los números de un tipo de punto flotante, según se entienden en el estándar C99.
   Dados un signo \( s \) (\( = +1 \) ó \( -1 \)), una base \( b \) (un entero \( > 1 \)), un exponente \( e \) (un entero cuyo valor está entre dos valores \( e_{min}, e_{max} \), \( e_{min}\leq e  \leq e_{max} \)), una precisión \( p \), unos dígitos \( f_1, f_2 ..., f_{p} \) en base \( b \) (es decir, enteros \( 0\leq f_k\leq b-1 \)), definen un modelo \( x \) de números de punto flotante, así:

\( x=s\cdot b^e\cdot \sum_{k=1}^{p} f_kb^{-k}. \)

   Esto define el término número de punto flotante.

   Por un lado, el estándar C99 recomienda tener en cuenta el estándar ISO/IEC/IEEE 60559 (brevemente IEEE 754), y la terminología que se usa sobre los números de punto flotante está inspirada en el IEEE 754.
Pero no parece que en ninguna parte el C99 obligue a ajustarse al IEEE 754.

Por un lado, esto nos ayuda a entender la terminología referida a los tipos de punto flotante, a la vez que nos previene de que muchas cuestiones pueden ser opcionales.
Y cuando para un estándar algo es opcional, quiere decir que en las implementaciones reales no hay acuerdo, y pueden haber problemas de portabilidad de los programas.

Para el estándar C99 hay tres tipos de datos considerados tipos de punto flotante reales. Ellos son float, double, long double.

(En programas antiguos se puede ver el uso de long float como sinónimo de double, pero actualmente no se soporta ese uso).

   Lo que se acostumbra es hacer coincidir los tipos de C con los formatos de IEEE 754, así:
el tipo float se corresponde con Single Precision (o binary16),
el tipo double se corresponde con Double Precision (o binary64),
el tipo long double se corresponde con Extended Double Precision.

Esa correspondencia usémosla sólo como un ejemplo que nos sirva de ejemplo en nuestra exposición, a fin de no tener definiciones tan genéricas dando vueltas en la cabeza.

   Pero en las implementaciones reales no hay razón para creer que float, double, long double, corresponden a un formato u otro, ni tan siquiera del IEEE 754 o no.
   El comité de estandarización para el C99 ha discutido mucho a este respecto, y el resultado de toda esa discusión no se ve en el resultado final que vemos nosotros: todo es opcional.
   Eso es porque hay multitud de fabricantes, implementaciones y demás desarrollos importantes que siguen cada cual su especificación propia, y el estándar pretende ser compatible con todas ellas.

   Es muy común que en una implementación concreta, coincidan double y long double.
   O bien que coincidan los tres tipos de punto flotante.
   Cuando decimos que coinciden, queremos decir que tienen el mismo conjunto de valores numéricos, la misma precisión, etc. Sin embargo, sintácticamente hablando, siguen siendo tipos de datos distintos.

   El estándar sin embargo nos dice algunas cosas:

   El conjunto de valores representado por float es un subconjunto de los valores representados por double.
   El conjunto de valores representado por double es un subconjunto de los valores representados por long double.

   También define la macro __STDC_IEC_559__, cuyo valor es 1 si el compilador de la implementación local "dice que"  ;) ;) soporta compatibilidad con la normativa ISO/IEC/IEEE 60559 (IEEE 754), y vale 0 en caso contrario.

   Aún en el caso de que __STDC_IEC_559__ esté definida como 1, eso no especifica de qué manera o en qué medida la implementación local es compatible con ISO/IEC/IEEE 60559 (IEEE 754).
   Y aún si fuera totalmente compatible, el susodicho ISO/IEC/IEEE 60559 (IEEE 754) está abierto a tantas opciones, que no se logra demasiada certidumbre.

   Lo que se debe hacer es estudiar la documentación del compilador que usamos (en nuestro caso GCC 4.7), y establecer qué dice ahí.

   Veremos que el estándar C99 ofrece algunos valores mínimos que hay que respetar, en la librería <float.h>.
   Pero antes vamos a ver cómo se escriben las constantes de punto flotante en un programa en C.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Otros tipos de punto flotante declarados en C

   En la librería <math.h> del estándar C están especificados, acorde a lo requerido por C99, los siguientes tipos de punto flotante:

float_t
double_t

   Estos tipos de datos están pensados de la siguiente manera:

   Supongamos que en nuestro sistema es posible acceder a tipos de punto flotante que tienen al menos la misma exactitud que float y/o double, pero que además permiten realizar cálculos más eficientes.
   En ese caso, el programador puede elegir tomar ventaja de esa eficiencia, y para ello no tendría que hacer muchos cambios en su programa fuente en C, sino hacer algo tan simple como escribir float_t en vez de float y/o double_t en vez de double.

   Qué es lo que representan exactamente estos tipos depende del hardware y/o de la implementación local del compilador, etc.
Sin embargo, el estándar C99 establece una pautas mínimas que han de cumplir,
y que dependen del valor asignado a la macro FLT_EVAL_METHOD, que está definida en la librería float.h:

Si el valor de FLT_EVAL_METHOD es 0,
   entonces float_t coincide con float, y double_t coincide con double.

Si el valor de FLT_EVAL_METHOD es 1,
   entonces float_t y double_t coinciden ambos con double.

Si el valor de FLT_EVAL_METHOD es 2,
   entonces float_t y double_t coinciden ambos con long double.

Para otros valores de FLT_EVAL_METHOD, es la implementación local la que debe especificar qué tipos son float_t y double_t.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Constantes de punto flotante en C.

   Desde el estándar C99 hay dos tipos de constantes de punto flotante: decimales y hexadecimales.
   El formato general de una constante decimal de punto flotante es éste:

n.de+r

Representa al número real:

\(   \alpha \) = (n+0.d)\( \cdot 10^{{\color{blue}\pm}{\color{red}\mathbb r} } \).

\( \bullet \)   En n va una secuencia finita de dígitos decimales (del 0 al 9), que denotan la parte entera.
\( \bullet \)   El . es el punto decimal (que separa la parte entera de la parte fraccionaria).
\( \bullet \)   En d va una secuencia finita de dígitos decimales, que denotan la parte fraccionaria.
\( \bullet \)   La letra e es el indicador de exponente, y puede usarse indistantemente e ó E.
\( \bullet \)   El signo + es el signo del exponente, y puede ser \( + \) ó \( - \).
\( \bullet \)   En r va una secuencia de dígitos del 0 al 9, que representa el valor absoluto del exponente.

   La parte entera n es opcional. Si no está, se interpreta que la parte entera es 0. Pero en este caso, tiene que estar presente algún dígito en la parte fraccionaria.
   La parte fraccionaria d es opcional. Si no está, se interpreta que la parte fraccionaria es .0. Pero en este caso, tiene que estar presente algún dígito en la parte entera.

   El punto decimal . y el indicador de exponente e ó E, son opcionales.
   Pero al menos tiene que estar presente alguno de los dos.
   El punto decimal es obligatorio cuando se indica una parte fraccionaria.

   Si el indicador de exponente e ó E no está,  quiere decir que el signo del exponente + es \( + \) y que el exponente r es 0.
   En tal caso, no pueden estar escritos explícitamente ni el signo + ni r.

   Cuando e ó E está presente, el signo + es opcional (si no está se toma por defecto como \( + \)), pero el exponente r tiene que estar presente.

   Las constantes que hemos descrito arriba se consideran por defecto de tipo double.
   Se puede agregar a la derecha un sufijo de estos:

f ó F: fuerza a la constante a ser de tipo float.
l ó L: fuerza a la constante a ser de tipo long double.


Ejemplos:

12.81e-25F, 1.281e-24f, .1281e-0023f, 000000.00001281e-19, 0.00000000000000000000000128, .000000000000000000000000128e+1L, 0.00000000000000000000000000128e3l

   Todas esas constantes representan al mismo número real, pero escrito en distintas formas, combinando partes entera, fraccionaria, y exponentes.
   Además, tienen tipos distintos: las primeras tres son float, las siguientes dos son double, y las últimas dos son long double.


Algunas consideraciones:

\( \bullet \)   Es importante notar que tanto las partes entera, fraccionaria, como el exponente, se indican con dígitos decimales. El compilador interpreta todo eso en base diez.

\( \bullet \)   Si la parte entera tuviera 0's a la izquierda, el número no se considera "octal" (como ocurría con las constantes enteras), sino que sigue siendo decimal, y esos 0's extra no alteran el valor matemático del numero, ni la representación interna del mismo, ni el tipo de datos al que pertenece la constante.

\( \bullet \)   Si la parte fraccionaria tuviera 0's a la derecha, no producen ningún efecto matemático, ni de representación interna, ni cambia el tipo de datos al que pertenece la constante.

   Agregando muchos 0's a la derecha se puede dar la falsa impresión de que estamos ante una constante que tiene tantos dígitos de precisión (a nivel de bits, internamente), como 0's se muestran. Pero esto no es así, y por lo tanto debe considerarse una mala práctica de programación.

\( \bullet \)   Notar que la presencia del punto decimal en cualquier caso impide que se confundan las constantes enteras con las constantes de punto flotante. A nivel sintáctico ambas están perfectamente diferenciadas.

\( \bullet \)   En cuanto a si conviene usar e ó E, la recomendación general es usar las minúsculas, porque supuestamente se lee mejor...  ::)

\( \bullet \)   En cuanto a los sufijos f, F, l, L, en cambio, se sugiere usar las mayúsculas. Por ejemplo, una l], en minúsculas, se confundiría fácilmente con un 1.

\( \bullet \)   Las constantes que pueden escribirse con el formato anterior son positivas ó cero (0.0), pero nunca negativas.

   Anteponer un signo - delante es posible, pero no se considera parte de la constante, sino como un operador que calcula el negativo de la constante en cuestión.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Constantes hexadecimales:

   El formato general de una constante hexadecimal de punto flotante es éste:

0xn.dp+r

Representa al número real:

\( \alpha \) = (n+0.d)\( {}_{16}\cdot 2^{{\color{blue}\pm}{\color{red} \matbb r}_{10} } \).

   El prefijo 0x ó 0X indica que la constante es hexadecimal;) ;)
   Los dígitos en la parte entera n y en la parte fraccionaria d son hexadecimales (0 a 9, y de A a F, y se admiten las minúsculas de a hasta f).
   La letra p ó P es el indicador de exponente binario.
   Indica que la potencia 2 será elevada al exponente que viene a la derecha: +r.

Importante:  :o :o :o :o :o el exponente r se sobreentiende expresado en base decimal.  :banghead: :banghead:

(Sí: un número en base 16, tiene adosado un exponente para una potencia de base 2, expresado en base 10... es para hacerle saltar las tuercas a cualquiera  :'( :'( )

   Las demás consideraciones sobre las posibles maneras de escribir una constante hexadecimal, son similares al caso decimal, en que puede faltar parte entera o parte fraccionaria, etc.
   Por ejemplo, es posible usar los sufijos f, F, l, L.

Sin embargo:

   En el caso hexadecimal siempre  :o tiene que estar presente el indicador exponencial binario p ó P.

   Esto logra evitar ambigüedades que surgirían al querer indicar constantes hexadecimales de tipo float, ya que la letra f se usa tanto para indica el "dígito hexadecimal quince" como "constante de tipo float".

Ejemplos:

\( 1.0p11: \qquad\qquad\qquad\quad = 2^{(11)_{10}} \)                (double)
\( 0x31FCp-17f: \qquad\qquad = (31FC)_{16}\cdot 2^{-(17)_{10}} \) (float)
\( 0xEF.CC1400A5p0L: \qquad = (EF.CC1400A5)_{16} \) (long double)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   ¿Por qué el C99 incorpora constantes de punto flotante hexadecimales?
   El motivo es que internamente las constantes se almacenan, generalmente, en base binaria.
   Es más fácil traducir de base 16 a base 2 y viceversa.
   Más aún, no se pierde exactitud en la conversión de una base a otra.

   Pero cuando de números con parte fraccionaria se trata, si bien "es posible"  ;) no perder precisión al convertir de base binaria a base decimal, en cambio de base decimal a base binaria lo más común es que se obtengan desarrollos binarios periódicos, con la consecuente pérdida de precisión por truncamiento.

   En algunas circunstancias, el programador necesita tener mayor control sobre la precisión de sus constantes, y entonces la notación hexadecimal es de mucha ayuda al respecto.
   También hay otros inconvenientes mucho más sutiles.
   Por ejemplo, el compilador puede elegir convertir de decimal a binario con determinadas reglas de redondeo, entre otras pautas durante la conversión, mientras que el programa ejecutable tendrá otros criterios (interpretando las constantes decimales de otra manera), e incluso puede haber incompatibilidad con criterios de conversión adoptados por funciones de librería (como printf()).
   A fin de estar seguros de una representación binaria uniforme de ciertas cantidades, la notación hexadecimal da una gran ayuda al programador.

   Debemos notar también que, aunque la notación hexadecimal de constantes de punto flotante permite una traducción directa al formato binario interno utilizado, esto no sirve para que "hagamos trampa".
   Por ejemplo, si en nuestro sistema el tipo float coincide con el Single Precision de IEEE 754, podríamos querer generar un valor NaN mediante: 0x1.FFFFFEp128f (obsérvese que el exponente binario es 128, coincidiendo con el exponente \( e = e_{max}+1 \), utilizado para los valores NaN).
Por el contrario, una constante así sólo producirá un error de desbordamiento, indicando que la constante es demasiado grande para caber en un float.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Normativa ISO/IEC/IEEE 60559 (ó IEEE 754)

   En cuanto a los temas que hemos desarrollado en este post, veamos qué dice el estándar C99 respecto el ISO/IEC/IEEE 60559.

   Cuando la macro __STDC_IEC_559__ tiene valor 1, el estándar C99 se ajusta al estándar ISO/IEC/IEEE 60559 de la siguiente manera:

\( \bullet \)   El tipo float coincide con el Single Precision de IEEE 754.
\( \bullet \)   El tipo double coincide con el Double Precision de IEEE 754.
\( \bullet \)   El tipo long double coincide con algún formato Extended de IEEE 754.
         Si esto no fuera posible, entonces coincide con algún formato extendido, que no pertenece al IEEE 754.
         Si esto tampoco se puede, entonces coincide con el Double Precision de IEEE 754.

   Todo formato extendido no perteneciente a IEEE 754 usado para long double, debe tener más precisión que el Double Precision de IEEE 754, y "al menos" el mismo rango de valores (del Double Precision).

Nota: Acorde a las reglas vistas al principio del post, se deduce también que el long double debe contener al conjunto de valores del double, de C.

   El estándar C99 no especifica valores NaN señalizadores (aunque está permitido implementarlos).

   Las constantes en la etapa del compilador tienen que tener el mismo valor que tendrían en la etapa de ejecución del programa.
   Esta norma se refiere a que el compilador puede elegir traducir constantes escritas a mano, convirtiendo de decimal a binario según criterios distintos a los que se usan durante un programa en ejecución (en donde el compilador no está actuando, sino nuestro programa compilado).
   Es una preocupación legítima acerca de la coherencia de nuestro sistema en torno al manejo de las constantes de punto flotante.

Hay más consideraciones, pero las explicaremos en el momento oportuno.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas