Rincón Matemático

Revista, Técnicas, Cursos, Problemas => Cursos del Rincón => Dictado de cursos del Rincón => Mensaje iniciado por: argentinator en 03 Enero, 2013, 05:13 pm

Título: Proyecto de Curso (Dictado - Notas): Programación en C (2013, por Argentinator)
Publicado por: argentinator en 03 Enero, 2013, 05:13 pm
Proyecto de Curso (Dictado - Notas Teóricas): Programación en C

3/Enero/2013

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Bibliografía sugerida

\( \bullet \)  C standard documents

http://port70.net/~nsz/c/ (http://port70.net/~nsz/c/)

(Una web con muy valiosa información sobre los documentos del estándar C, desde el original ANSI C hasta la actualidad).

\( \bullet \)  Programando con wxDevC++, archivo electrónico disponible en:

Programando con wxDevC++.zip (http://sourceforge.net/projects/wxdevcpp-book/files/Programando%20con%20wxDev-C%2B%2B%20PDF/Programando%20con%20wxDev-C%2B%2B%20PDF%20Alpha%200.2.0/Programando_con_wxDev-C%2B%2B_PDF_Alpha_0_2_0.zip/download)

Programando con wxDevC++.pdf (archivo adjunto a este post) (https://foro.rinconmatematico.com/index.php?action=dlattach;topic=64835.0;attach=11478)

(Manualcito para entender y utilizar el IDE wxDevC++).

\( \bullet \)  C Programming, 2nd. edition, K. N. King  (incluye estándares C89 y C99).

(Un completo y muy buen manual de referencia del lenguaje C, con información técnica ajustada a los estándares C89 y C99).

\( \bullet \)  El Lenguaje de Programación C, 2da. edición, B. W. Kernighan y D. M. Ritchie.

(Es el clásico libro de los creadores de C, Kernighan y Ritchie. No obstante, hay que recordar que ya es viejo, y que el estándar C se ha independizado de sus creadores originales).

\( \bullet \)  The C book, por: gbDirect, acceso online en:

The C Book (http://publications.gbdirect.co.uk/c_book/)

(Un manual de referencia de C de gbDirect Publications, cómodo, gratuito y disponible online).

\( \bullet \)  Final version of the C99 standard with corrigenda TC1, TC2, and TC3 included

Final version of the C99 standard with corrigenda TC1, TC2, and TC3 included, formatted as a draft (http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf)

(Texto que define el lenguaje C, según el estándar C99 (ISO año 1999), versión corregida, año 2007).

\( \bullet \)  The New C Standard, D. M. Jones

The New C Standard (http://www.coding-guidelines.com/cbook/cbook1_1.pdf)

(Texto que comenta una a una las reglas del estándar C99, con valiosa información técnica y ejemplos interesantes).

\( \bullet \)  XL C/C++ V7.0 (for AIX), IBM (online)

Manual de C de IBM (http://publib.boulder.ibm.com/infocenter/comphelp/v7v91/index.jsp?topic=%2Fcom.ibm.vacpp7a.doc%2Flanguage%2Fref%2Fclrc05preeval.htm)

(El C de IBM no es igual al estándar, pero aún así puede servirnos su manual para hallar información rápida sobre temas comunes de programación en C).


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Índice

Índice por áreas temáticas:

Información general, instalaciones, configuraciones, pruebas:

1.      Instalación, configuración, primeros pasos (http://rinconmatematico.com/foros/index.php?topic=64835.msg260266#msg260266)

2.      Explicando el HolaMundo.c (http://rinconmatematico.com/foros/index.php?topic=64835.msg260280#msg260280)

3.      Un sencillo Hola Mundo en Windows (http://rinconmatematico.com/foros/index.php?topic=64835.msg260313#msg260313)

4.      De la eficacia a la elegancia - Macros del Preprocesador (parte I) (http://rinconmatematico.com/foros/index.php?topic=64835.msg260354#msg260354)

5.      De la eficacia a la elegancia - Macros del Preprocesador (parte II) (http://rinconmatematico.com/foros/index.php?topic=64835.msg260403#msg260403)

6.      Etapas del Preprocesador (http://rinconmatematico.com/foros/index.php?topic=64835.msg260430#msg260430)

10.     Pare de sufrir: Alternativas a la consola CMD (http://rinconmatematico.com/foros/index.php?topic=64835.msg260469#msg260469)

11.     Configuración de opciones del Compilador. Compatibilidad con C99. (http://rinconmatematico.com/foros/index.php?topic=64835.msg260748#msg260748)

12.     Uso básico de la función printf(). (http://rinconmatematico.com/foros/index.php?topic=64835.msg260748#msg260748)

30.     ¿Cómo lograr que todo funcione? (Parte I) (http://rinconmatematico.com/foros/index.php?topic=64835.msg264790#msg264790)

31.     ¿Cómo lograr que todo funcione? (Parte II) (http://rinconmatematico.com/foros/index.php?topic=64835.msg264793#msg264793)

Caracteres y strings:

7.      Caracteres (char) y Strings (char*) (http://rinconmatematico.com/foros/index.php?topic=64835.msg260453#msg260453)

33.     Caracteres en el lenguaje C (I) (http://rinconmatematico.com/foros/index.php?topic=64835.msg278094#msg278094)

34.     Caracteres en el lenguaje C (II) (http://rinconmatematico.com/foros/index.php?topic=64835.msg278617#msg278617)

35.     Caracteres en el lenguaje C (III) (http://rinconmatematico.com/foros/index.php?topic=64835.msg278639#msg278639)

36.     Caracteres en el lenguaje C (IV) (http://rinconmatematico.com/foros/index.php?topic=64835.msg278640#msg278640)

37.     Caracteres en el lenguaje C (V) (http://rinconmatematico.com/foros/index.php?topic=64835.msg278663#msg278663)

38.     Caracteres en el lenguaje C (VI) (http://rinconmatematico.com/foros/index.php?topic=64835.msg279601#msg279601)


Números enteros, de punto flotante y complejos:

8.      Enteros en C (parte I) (http://rinconmatematico.com/foros/index.php?topic=64835.msg260467#msg260467)

9.      Enteros en C (parte II) (http://rinconmatematico.com/foros/index.php?topic=64835.msg260468#msg260468)

12.     Uso básico de la función printf(). (http://rinconmatematico.com/foros/index.php?topic=64835.msg260748#msg260748)

13.     Testeando tipos de datos enteros de C (parte I) (http://rinconmatematico.com/foros/index.php?topic=64835.msg260792#msg260792)

14.     Testeando tipos de datos enteros de C (parte II) (http://rinconmatematico.com/foros/index.php?topic=64835.msg260963#msg260963)

15.     Testeando tipos de datos enteros de C (parte III) (http://rinconmatematico.com/foros/index.php?topic=64835.msg260972#msg260972)

16.     Testeando tipos de datos enteros de C (parte IV) (http://rinconmatematico.com/foros/index.php?topic=64835.msg260976#msg260976)

17.     Números de punto flotante. Generalidades (http://rinconmatematico.com/foros/index.php?topic=64835.msg261434#msg261434)

18.     Números de punto flotante. Estándar IEEE 754 (http://rinconmatematico.com/foros/index.php?topic=64835.msg261438#msg261438)

19.     Números de punto flotante. Estándar C99. Constantes (http://rinconmatematico.com/foros/index.php?topic=64835.msg261637#msg261637)

20.     Números de punto flotante. Redondeos y cambio de base (http://rinconmatematico.com/foros/index.php?topic=64835.msg261758#msg261758)

21.     Números de punto flotante. Valores fuera de rango (http://rinconmatematico.com/foros/index.php?topic=64835.msg261830#msg261830)

22.     Números de punto flotante. Estándar C99. FLOAT.H (http://rinconmatematico.com/foros/index.php?topic=64835.msg261925#msg261925)

23.     Números de punto flotante. Compilador GCC (http://rinconmatematico.com/foros/index.php?topic=64835.msg262097#msg262097)

24.     Números de punto flotante. Programas de Testeo (parte I) (http://rinconmatematico.com/foros/index.php?topic=64835.msg262464#msg262464)

25.     Números de punto flotante. Programas de Testeo (parte II) (http://rinconmatematico.com/foros/index.php?topic=64835.msg262798#msg262798)

26.     Números de punto flotante. Programas de Testeo (parte III) (http://rinconmatematico.com/foros/index.php?topic=64835.msg262904#msg262904)

27.     Números complejos en C. Preliminares (http://rinconmatematico.com/foros/index.php?topic=64835.msg262917#msg262917)

28.     Números complejos en C. Estándar C99. Conversión de tipos (http://rinconmatematico.com/foros/index.php?topic=64835.msg263088#msg263088)

29.     Números complejos en C. Programa de testeo. (http://rinconmatematico.com/foros/index.php?topic=64835.msg264661#msg264661)


(Índice por orden de edición)

1.      Instalación, configuración, primeros pasos (http://rinconmatematico.com/foros/index.php?topic=64835.msg260266#msg260266)

2.      Explicando el HolaMundo.c (http://rinconmatematico.com/foros/index.php?topic=64835.msg260280#msg260280)

3.      Un sencillo Hola Mundo en Windows (http://rinconmatematico.com/foros/index.php?topic=64835.msg260313#msg260313)

4.      De la eficacia a la elegancia - Macros del Preprocesador (parte I) (http://rinconmatematico.com/foros/index.php?topic=64835.msg260354#msg260354)

5.      De la eficacia a la elegancia - Macros del Preprocesador (parte II) (http://rinconmatematico.com/foros/index.php?topic=64835.msg260403#msg260403)

6.      Etapas del Preprocesador (http://rinconmatematico.com/foros/index.php?topic=64835.msg260430#msg260430)

7.      Caracteres (char) y Strings (char*) (http://rinconmatematico.com/foros/index.php?topic=64835.msg260453#msg260453)

8.      Enteros en C (parte I) (http://rinconmatematico.com/foros/index.php?topic=64835.msg260467#msg260467)

9.      Enteros en C (parte II) (http://rinconmatematico.com/foros/index.php?topic=64835.msg260468#msg260468)

10.     Pare de sufrir: Alternativas a la consola CMD (http://rinconmatematico.com/foros/index.php?topic=64835.msg260469#msg260469)

11.     Configuración de opciones del Compilador. Compatibilidad con C99. (http://rinconmatematico.com/foros/index.php?topic=64835.msg260748#msg260748)

13.     Testeando tipos de datos enteros de C (parte I) (http://rinconmatematico.com/foros/index.php?topic=64835.msg260792#msg260792)

14.     Testeando tipos de datos enteros de C (parte II) (http://rinconmatematico.com/foros/index.php?topic=64835.msg260963#msg260963)

15.     Testeando tipos de datos enteros de C (parte III) (http://rinconmatematico.com/foros/index.php?topic=64835.msg260972#msg260972)

16.     Testeando tipos de datos enteros de C (parte IV) (http://rinconmatematico.com/foros/index.php?topic=64835.msg260976#msg260976)

17.     Números de punto flotante. Generalidades (http://rinconmatematico.com/foros/index.php?topic=64835.msg261434#msg261434)

18.     Números de punto flotante. Estándar IEEE 754 (http://rinconmatematico.com/foros/index.php?topic=64835.msg261438#msg261438)

19.     Números de punto flotante. Estándar C99. Constantes (http://rinconmatematico.com/foros/index.php?topic=64835.msg261637#msg261637)

20.     Números de punto flotante. Redondeos y cambio de base (http://rinconmatematico.com/foros/index.php?topic=64835.msg261758#msg261758)

21.     Números de punto flotante. Valores fuera de rango (http://rinconmatematico.com/foros/index.php?topic=64835.msg261830#msg261830)

22.     Números de punto flotante. Estándar C99. FLOAT.H (http://rinconmatematico.com/foros/index.php?topic=64835.msg261925#msg261925)

23.     Números de punto flotante. Compilador GCC (http://rinconmatematico.com/foros/index.php?topic=64835.msg262097#msg262097)

24.     Números de punto flotante. Programas de Testeo (parte I) (http://rinconmatematico.com/foros/index.php?topic=64835.msg262464#msg262464)

25.     Números de punto flotante. Programas de Testeo (parte II) (http://rinconmatematico.com/foros/index.php?topic=64835.msg262798#msg262798)

26.     Números de punto flotante. Programas de Testeo (parte III) (http://rinconmatematico.com/foros/index.php?topic=64835.msg262904#msg262904)

27.     Números complejos en C. Preliminares (http://rinconmatematico.com/foros/index.php?topic=64835.msg262917#msg262917)

28.     Números complejos en C. Estándar C99. Conversión de tipos (http://rinconmatematico.com/foros/index.php?topic=64835.msg263088#msg263088)

29.     Números complejos en C. Programa de testeo. (http://rinconmatematico.com/foros/index.php?topic=64835.msg264661#msg264661)

30.     ¿Cómo lograr que todo funcione? (Parte I) (http://rinconmatematico.com/foros/index.php?topic=64835.msg264790#msg264790)

31.     ¿Cómo lograr que todo funcione? (Parte II) (http://rinconmatematico.com/foros/index.php?topic=64835.msg264793#msg264793)

32.      (http://)

33.     Caracteres en el lenguaje C (I) (http://rinconmatematico.com/foros/index.php?topic=64835.msg278094#msg278094)

34.     Caracteres en el lenguaje C (II) (http://rinconmatematico.com/foros/index.php?topic=64835.msg278617#msg278617)

35.     Caracteres en el lenguaje C (III) (http://rinconmatematico.com/foros/index.php?topic=64835.msg278639#msg278639)

36.     Caracteres en el lenguaje C (IV) (http://rinconmatematico.com/foros/index.php?topic=64835.msg278640#msg278640)

37.     Caracteres en el lenguaje C (V) (http://rinconmatematico.com/foros/index.php?topic=64835.msg278663#msg278663)

38.     Caracteres en el lenguaje C (VI) (http://rinconmatematico.com/foros/index.php?topic=64835.msg279601#msg279601)

(http://)

[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 1. Instalación, configuración, primeros pasos
Publicado por: argentinator en 03 Enero, 2013, 06:35 pm
1. Instalación, configuración, primeros pasos

3/Enero/2013

   El lenguaje C es un lenguaje de programación de computadoras, de alto nivel, estructurado e imperativo.
   No voy a explicar qué significan esos términos.  ::)
   A fines técnicos digamos que:

\( \bullet \)  Trabajaremos con el estándar C99.

Elección de un estándar
   El lenguaje C ha sido implementado con diversas variantes según las necesidades y las empresas que lo usaron. Naturalmente, esto es fuente de incompatibilidades, y por lo tanto se hace necesario establecer un estándar.

   Los estándares que podemos encontrar son los de ANSI (Estados Unidos) e ISO (internacional).
   Podemos esperar que, en lo referido al lenguaje C, ambos estándares coinciden.
   La historia de los estándares puede verse en:

http://es.wikipedia.org/wiki/ANSI_C#Compiladores_compatibles_con_ANSI_C (http://es.wikipedia.org/wiki/ANSI_C#Compiladores_compatibles_con_ANSI_C)

   Se aprecia que al pasar de los años el estándar C ha tenido modificaciones, nombradas como C89 (ANSI 1989), C90 (ISO 1990, coincide con C89), C99 (ANSI/ISO 1999) y C11 (ANSI/ISO 2011).

   Aunque los estándares están definidos en teoría, o sea en un papel firmado por un comité, no quiere decir que sea fácil hallar compiladores reales compatibles con esas versiones, ni bibliografía adecuada.

   Debido a estas complicaciones, elegiremos trabajar con el estándar C99, ya que el C11 es aún demasiado reciente, y se me ha complicado hallar compiladores adecuados y bibliografía.

[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Elección de un compilador

   Nosotros utilizaremos el compilador GCC, bajo la distribución MinGW.

   Para más información:  http://mingw.org/wiki/MinGW (http://mingw.org/wiki/MinGW)


¿Qué es un compilador y cuál elegir?

   Un lenguaje de programación es, en realidad, algo meramente teórico, está definido en un texto. Para que efectivamente podamos realizar programas en dicho lenguaje, necesitamos de un compilador, esto es, un programa que traduce código escrito según las reglas del lenguaje a instrucciones que la computadora pueda procesar (esto se llama lenguaje de máquina).

   Hay muchos compiladores disponibles para C, sin embargo debemos elegir uno que sea compatible con el estándar C99 que hemos elegido, y que además sea gratuito.
   Y más importante aún: que el compilador no tenga errores.

   Este último requisito quizá no siempre se cumpla, y para ello hay que ir pensando en la posibilidad de ponerse en contacto con la(s) personas que se ocupan del desarrollo del compilador que vayamos a usar.

   En este curso usaremos el compilador GCC bajo la distribución MinGW, que es un proyecto gratuito, serio, y aún en desrrollo.
   Por diversas razones en su historia, GCC tiene compatibilidad más bien con el compilador de Microsoft C, y por lo tanto no es compatible del todo con el C estándar.
   No obstante, el proyecto MinGW apunta a lograr una compatibilidad completa con el estándar C99.

   Por lo tanto, nosotros utilizaremos sólo aquellas herramientas de C99 que están implementadas en GCC/MinGW.

   La elección de esta distribución también nos queda cómoda, pues viene incluida en el IDE wxDevC++.

[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Elección de un IDE

   El IDE (Integrated development environment: entorno integrado de desarrollo) que vamos a utilizar es wxDevC++ 7. La versión a la fecha es 7.4.2.569.
   Incluye la distribución MinGW del compilador GCC (evitando instalar un compilador y un IDE, ambos por separado).

¿Qué es un IDE y cuál elegir?

   Además de un compilador, necesitamos un editor de texto especializado, que esté acondicionado para las tareas comunes de un programador, así como también que tenga opciones que enlacen con el compilador de nuestra preferencia. Este tipo de programas suelen conocerse como Entornos Integrados de Desarrollo, Entornos de Trabajo o IDEs.

   El IDE que voy a utilizar es el más popular, dentro de los gratuitos: wxDevC++.

   Originalmente el DevC++ fue desarrollado por Colin Laplace, Hongli Lai, Yiannis Mandravellos and Mike Berg, hasta la versión 4.9.9.2, y luego quedó trunco.
   A partir de ahí el proyecto fue retomado por dos vías actualmente disponibles: una es la de un equipo bajo el nombre de wxDevC++ (ver Desarrolladores de wxDevC++ (http://wxdsgn.sourceforge.net/?q=node/19)) y otro por un desarrollador independiente, creo que un tal Orwell (http://orwelldevcpp.blogspot.com.ar/2012/12/dev-c-5304-released.html (http://orwelldevcpp.blogspot.com.ar/2012/12/dev-c-5304-released.html)).

   Voy a preferir wxDevC++ por dos razones. Una, porque al parecer el grupo de desarrollo está muy activo, lo que asegura continuidad y actualización del proyecto, y así nuestras herramientas de programación no quedan obsoletas.
   Otra razón es que parecen tomarse en serio el tema de las widgets, que son herramientas pensadas para la presentación visual de los programas, tan necesario en la informática de este siglo.

[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

¿Cómo instalar wxDevC++?

   Para poder escribir nuestros programas en C y compilarlos, hemos de descargar e instalar el IDE wxDevC++, que incluye el compilador MinGW.

   Para esto, basta ir a la siguiente página web:

Web de descarga de wxDevC++ (http://wxdsgn.sourceforge.net/?q=node/4)

   Allí seleccionamos la 1era opción (la que dice wxDev-C++ 7 Dynamic installer) y hacemos clic sobre ella.
   Nos descargará un muy pequeño programa instalador, que bajará enseguida.

   Buscamos este programa en la carpeta de descargas de nuestro navegador y lo ejecutamos.
   Al hacerlo, se ejecutará el programa instalador. Aceptemos todas las opciones que vayan apareciendo, y esperemos a que termine el proceso de descarga, descomprensión e instalación.

   Una vez finalizada la instalación tendremos en el menú Programas de nuestro sistema Windows un acceso directo a wxDevC++.

   La primera vez que corremos el IDE wxDevC++ nos hará algunas preguntas.
   Para nuestros futuros propósitos, da igual responder que Sí o que No. Responder que No nos permitirá comenzar más rápido.
   Lo importante es seleccionar nuestro idioma: Spanish, a fin de trabajar más cómodos.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   En las notas que siguen supondré que siempre tenemos instalado y abierto el IDE wxDEVC++.

   La primera vez que ejecutemos el IDE wxDevC++, deberíamos configurarlo para nuestros propósitos futuros.

   Los cambios de configuración que vamos a hacer son estos: ninguno.  ;D

   Y lo digo muy en serio. Por favor no hacer cambios de configuración sólo porque se nos ocurre hacerlo.

   Vayamos al menú Herramientas, y luego clic en Opciones del Compilador.
   Veremos que está seleccionada la pestaña Compiler o Compilador.
   Encima de ella está seleccionado el compilador GCC (a mí me aparece como Default GCC Compiler).
   Se puede seleccionar allí otros compiladores, pero no lo haremos.

   En la pestaña se ve que aparece como Tipo de Compilador (o Compiler Type) el MinGW.
   Tampoco tocaremos esta opción.

   Nos interesa que nuestro compilador respete lo más posible el estándar C99. Pero considero que es mejor discutir esto más adelante, y allí les indicaré cómo configurar el compilador para esto.
   Si bien hemos de tener en cuenta que la compatibilidad de GCC con el estándar C99 no es completa, y está lejos de serlo.
   Para más información sobre este tema, consultar:

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

   Se ven varias pestañas allí: Compilador, Configuración, Directorios, etc.
   Les permito que las vayan mirando, pero no cambien ninguna configuración.

   Para salir de esta ventana, en la parte inferior de la ventana: clic en el botón Cancelar.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Antes de realizar nuestros programas, conviene ser ordenados y crear una carpeta para nuestros proyectos.

   Vamos por ejemplo en Windows a la carpeta  Mis Documentos y allí creamos la carpeta  Mis_Proyectos_C.
   A continuación debemos indicarle al IDE wxDevC++ que esa será nuestra carpeta de trabajo.

Para ello: vamos al
menú Herramientas,
clic en Opciones del Entorno,
clic en pestaña Directorios, y luego
vamos al campo Directorio del Programa,
o también User's Default Directory (en cualquier caso, es el primero de la lista de directorios).

   Allí hacemos clic en el ícono de la carpeta, y buscamos nuestra carpeta recién creada, hasta que encontramos por fin Mis_Proyectos_C, y la seleccionamos.
   Una vez seleccionada, clic en Aceptar.

Por las dudas, recomiendo en este momento cerrar el IDE wxDevC++, y luego reabrirlo, a fin de que las modificaciones en la configuración queden correctamente activadas.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   A fin de comprobar que todo funciona, hagamos un programa sencillo, el clásico "Hola Mundo".

   Vayamos al menú Archivo, clic en Nuevo, luego clic en Código Fuente.

   Nos aparece un cuadro de texto con el nombre SinNombre1.
   Para darle un nombre, y de paso ya tenerlo guardado en el disco duro, vamos al menú Archivo, clic en Guardar Como....
   Aparece un cuadro donde hemos de escribir un nombre para nuestro archivo.
   También aparece debajo un desplegable con opciones para el Tipo de Archivo.

   La opción por defecto para archivos nuevos es la C++ source files.
   Entonces abrimos el desplegable y seleccionamos la opción C source files

   Ahora en el Nombre de Archivo ponemos HolaMundo.c.

   Nos aseguramos de que va a guardar nuestro archivo en la carpeta Mis_Proyectos_C. Si no aparece, la buscamos hasta encontrarla.

   Por fin, hacemos clic en Guardar.

   Aún nuestro código está vacío. Escribimos las siguientes líneas de código:



#include <stdio.h>

int main(void) {   
   printf("Hola mundo!\n");
   getchar();                        /* Espera que se presione tecla ENTER */   
}



   Guardamos los cambios yendo al menú Archivo, clic en Guardar.

   En el futuro, cada vez que hagamos un cambio importante en nuestros proyectos, conviene inmediatamente guardar.

   Para poder correr finalmente nuestro programa, es necesario compilarlo.
   Para ello vamos al menú Ejecutar, y luego clic en Compilar.
   Esto traduce nuestro programa HolaMundo.c a un programa ejecutable, llamado HolaMundo.exe.

   Por último ejecutamos el programa compilado, yendo al menú Ejecutar, y luego clic en Ejecutar.

   En general conviene realizar estos dos pasos por separado, ya que la compilación se hace una sola vez, y luego podemos ejecutar el programa varias veces, sin tener que perder tiempo compilándolo de nuevo todas las veces.

   Por otra parte, el programa puede ejecutarse directamente en Windows, yendo a la carpeta donde guardamos nuestros proyectos, y haciendo doble clic directamente en el ejecutable HolaMundo.exe.

   Si todo está correcto, se ha de abrir la Línea de Comandos (una  ventana con fondo negro) y allí ha de aparecer la frase Hola Mundo!.
   Debajo estaría titilando el cursor, a la espera de que el usuario presione la tecla ENTER.
   Una vez presionada la tecla ENTER, el programa termina, y la ventana de la línea de comandos debiera cerrarse automáticamente (aunque esto puede depender de cómo tengamos configurada la ventana de la línea de comandos en nuestro sistema Windows).

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 2. Explicando el HolaMundo.c
Publicado por: argentinator en 03 Enero, 2013, 08:53 pm
2. Explicando el HolaMundo.c

   Nuestro primer programa, el HolaMundo.c, fue esto:

01 #include <stdio.h>
02
03 int main(void) {   
04    printf("Hola mundo!\n");
05    getchar();                        /* Espera que se presione tecla ENTER */   
06 }
07


   He agregado unos números de línea 01, 02, 03, ..., que en realidad no deben escribirse en el programa, sino que sólo están ahí para referencia.

   La línea 01 contiene la cláusula:

#include <stdio.h>

   El símbolo # se utiliza para indicar directivas del compilador.
   Otro día explicaré qué es eso.
   Basta saber que #include es una directiva del compilador.
   Lo que hace es indicarle al compilador que hay una lista de objetos previamente definidos (datos y procesos) en una determinada librería, y que estarán disponibles en el programa como si nosotros mismos las hubiéramos definido en nuestro programa.

   La librería que se incluye en este caso es el archivo <stdio.h>.

   Hay librerías estándar del lenguaje C, y librerías creadas por el usuario.
   Las librerías estándar se indican encerradas entre ángulos, como es el caso de <stdio.h>.
   Mientras que las librerías definidas o creadas por el usuario se indican entre comillas: "milibreria.h".

   La librería <stdio.h> tiene definidas funciones estándar de entrada y salida de datos.
   Allí aparecen pues la salida estándar (o pantalla), la entrada estándar (teclado), y otras funciones para el intercambio de datos con archivos en general.

   La he incluido porque necesitaba usar la función de salida estándar a pantalla printf(), y la función estándar de entrada por teclado getchar().

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Para que un programa C realmente haga algo, necesita que se declare una función con el nombre main().

   Una función en C tiene un nombre, una lista de parámetros de entrada, un parámetro de salida, un cuerpo o bloque de sentencias, y una sentencia de retorno.

   Por ejemplo, una función que suma dos números enteros tiene este aspecto:

int suma (int a, int b) {

   int s;
   s = a + b;

   return s;
}


   El nombre de la función sería suma, los parámetros de entrada serían int a, int b, el parámetro de salida o retorno sería un valor de tipo int, el cuerpo o bloque de instrucciones de la función es todo lo que aparece encerrado entre llaves { }, y finalmente la instrucción de retorno es return s;.

   Lo que hace esta función es recibir dos números enteros como entrada, sumarlos, y devolver el resultado al programa principal.
   Asi, al efectuar la llamada suma(5, 3), se obtiene como resultado 8.

   Ahora volvamos a la función main().
   Esta función es muy especial en C, es la más importante de todas.
   Es la única función que el compilador de C reconoce como cuerpo principal del programa.
La busca para comenzar a ejecutar instrucciones.
   Si main() no está presente, el programa no realiza ninguna acción.

   Lo importante aquí es que, si no nos interesa que main() reciba parámetros de entrada, podemos escribir void en su lista de parámetros.
   Por su parte, main() retorna siempre un valor de tipo int, cuestión que explicaremos en otro momento.
   Por ahora no nos interesa, y no retornaremos ningún valor específico.

   Entonces nos queda el encabezado así:

int main(void)

   Luego, encerramos entre llaves { } el cuerpo de la función main(), que será la lista de instrucciones que ejecutará nuestro programa.

   Vemos dos instrucciones. La primera es:

printf("Hola Mundo!\n");

   Lo que hace es invocar la función printf() de la librería <stdio.h>, para mostrar el mensaje Hola Mundo! en Línea de Comandos.
   Los caracteres \n lo que hacen es indicar que allí debe insertarse una nueva línea, con lo cual el cursor baja a la siguiente línea, y por eso aparecerá titilando debajo de la frase Hola Mundo! al ejecutar el programa.

   A fin de que la línea de comandos no se cierre sin que veamos la maravillosa frase, hemos de insertar algún tipo de pausa.
   Lo hemos hecho a través de la instrucción getchar(), que se encarga de esperar que el usuario ingrese información en línea de comandos desde el teclado.

   La función getchar() espera que el usuario ingrese caracteres con el teclado, seguido de la pulsación de la tecla ENTER.
   Hasta que no se presiona la tecla ENTER, el programa queda estancado a la espera de un dato.

   Tras esta instrucción, ya no hay más, y al alcanzar la llave de cierre } el programa termina su ejecución.
   Observamos que no hay una instrucción de retorno return, ya que en nuestro caso main() no devuelve ningún valor.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Una consideración importante aquí es que toda instrucción en C tiene que terminar con el signo de punto y coma: ;

   Esto es una regla del lenguaje, y es importante respetarla, porque si no se producen molestos errores de compilación, y el programa no ejecuta.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Observemos también que la línea 02 está vacía.
   En general, en el lenguaje C podemos intercalar libremente líneas en blanco, así como también es posible escribir muchos espacios en blanco, ya que muchos de ellos se cuentan como uno solo.

   Vemos en la línea 05 que, a la derecha de la sentencia getchar(), aparece una frase que dice:

/* Espera que se presione tecla ENTER */

   Todo texto que aparece encerrado entre los signos /* y */ se considera un comentario.

   Los comentarios son, para el lenguaje C, como espacios en blanco, es decir, no ejecutan instrucción alguna.
   Sin embargo, sirven de útiles indicaciones para quienes estén leyendo el código del programa y quieran entender qué hace una determina instrucción.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Finalmente digamos que el uso que hemos hecho de la función getchar() sería algo así como una mala práctica de programación.
   En efecto, la función getchar() está pensada para recibir datos de entrada, y luego guardarlos en alguna variable para su futura utilización.

   No hemos hecho nada de eso, sino que sólo la hemos utilizado para forzar al programa a hacer una pausa y terminar tras la presión de la tecla ENTER. Este uso es extraño y artificioso.

   Pero lo hicimos así porque es rápido, efectivo, simple, y además no involucra conceptos más complicados de la programación en C.
   Es un uso perfectamente apto para un programa inicial de testeo, tal como el HolaMundo.c.

   En particular, este uso irregular o extraño de la función getchar() es lo que ameritó el comentario aclaratorio que hemos puesto a la derecha en la línea 05.

   En programas sencillos, en los que tengamos el mismo inconveniente de querer frenar el programa en ejecución antes de que termine abruptamente, vamos a utilizar la misma técnica.
   Pero es sólo un truquillo para ahorrarnos tiempo, y no es parte de un aprendizaje serio de la programación.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 3. Un sencillo Hola Mundo en Windows
Publicado por: argentinator en 04 Enero, 2013, 05:19 am
3. Un sencillo Hola Mundo en Windows

   Los programas compilados y ejecutados desde GCC corren en el shell de comandos de Windows, esto es, una ventanita negra e inestética donde se muestran resultados de forma secuencial, en modo de texto exclusivamente. En realidad lo que se está corriendo es un programa llamado CMD.EXE, que sirve para ejecutar los comandos del sistema operativo. Se le puede decir también la consola, aunque considero que esta terminología no es técnicamente correcta.
   La ventana del sistema es donde C envía resultados por defecto. Es lo que se llama salida estándar. En cambio el teclado es la entrada estándar. Ambas en conjunto conforman la consola estándar.
   Esa ventanita es lo bastante fea y precaria como para que deseemos escapar de ella. Sin embargo, hacerlo supone aprender técnicas de programación bajo Windows, que quisiéramos evitar en un primer momento. Necesitamos que, al principio del curso, las cosas sean relativamente sencillas. Queremos estudiar el lenguaje C en la forma más pura posible.

   Sin embargo, para aquellos interesados, vamos a mostrar una posible alternativa (no muy sofisticada), que permitiría mostrar resultados sencillos mediante mensajes de Windows.

(Los detalles de esto son material opcional, y lo ponemos es spoiler: se discute sobretodo el tema de compilación condicional, con la idea de optar entre un mensaje mostrado en línea de comandos y un mensaje mostrado en una ventana de Windows).

(MessageBox(), compilación condicional)

   Vamos a utilizar la librería windows.h que provee GCC. En ella existe una función MessageBox() que muestra una ventanita con un mensaje de aviso.
   Nuestra versión de Hola Mundo para Windows será un nuevo programa llamado HolaMundoWin.c con el siguiente contenido:


#include <windows.h>

int main(void) {
    MessageBox(NULL,"\n\nEste es un Hola Mundo con una MessageBox.\n\n","Hola Mundo",0);
}


   Notar que la librería windows.h debe ser incluida en nuestro código.

   La función MessageBox() tiene este formato:


int WINAPI MessageBox(
  _In_opt_  HWND hWnd,
  _In_opt_  LPCTSTR lpText,
  _In_opt_  LPCTSTR lpCaption,
  _In_      UINT uType
);


   El 1er parámetro hWnd ha de especificar un manejador a la ventana dueña (o sea, la que se supone que proviene del programa principal que generó la ventanita de mensaje).
   Como estamos haciendo un uso casero de esta función, no necesitamos esto. Y por lo tanto no hay una ventana dueña o principal. Sólo tenemos la ventanita de mensaje, y por lo tanto especificaremos un manejador NULL (nulo).
   El 2do parámetro es el texto del mensaje que queremos mostrar.
   De nuevo, podemos usar \n para agregar saltos de línea, si queremos que el mensaje se vea un poco mejor.
   El 3er parámetro es el título de la ventanita de mensaje.
   El 4to parámetro es un número entero que indica qué botones adicionales han de aparecer.
   Nosotros usamos el valor 0, que por defecto nos pone solamente el botón OK (o Aceptar), que termina de mostrar el mensaje, cerrando la ventanita.

   Este tipo de funciones utilizan las herramientas de programación orientada a objetos de C++, o sea que me estoy saliendo del estándar C para hacer esto.
   Intentaré no abusar demasiado de esto, y utilizar estos trucos sólo para obtener una entrada/salida que sea más confortable.
   Más aún, los programitas que vayamos a hacer en el futuro tienen que poder funcionar tanto con MessageBox() de windows.h como con printf() de stdio.h para la consola estándar.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Podemos hacer algo más general como lo que sigue, agregando directivas del preprocesador. Podemos indicarle al compilador cuál de las dos versiones de "Hola Mundo" nos interesa compilar. Para ello, definir constantes del preprocesador, esto es, valores que sólo tienen sentido durante la compilación del programa.


#define Me_Conformo_con_CMD  0
#define Me_Gusta_mas_Windows 1


   La directiva #define sirve en este caso para definir expresiones que el compilador sustituirá por valores. Así, la expresión Me_Conformo_con_CMD se sustituirá por el valor 0, mientras que Me_Gusta_mas_Windows se sustituirá por el valor 1.
   En general, el uso de #define sería éste:

#define EXPRESION VALOR

   Lo cual indica que el compilador tomará toda ocurrencia de EXPRESION en nuestro código y la sustituirá automáticamente por VALOR.
   Es importante entender que se hace un reemplazo automático de este estilo, y así se entiende por qué no aparecen los famosos punto y coma terminadores de instrucción de C.
   La directiva #if permite indicar opciones al momento de realizar la compilación del programa.
   Esto quiere decir que el compilador elegirá distintos trozos de código para compilar, según las opciones que le indiquemos.
   La estructura genérica es ésta:

#if EVAL1
   /* Si el resultado de la expresión evaluada en EVAL1 es distinto de 0,
       entonces se compila este trozo de código,
       y se salta hasta la línea debajo de #endif.
       Si no, se sigue aquí debajo.
  */
#elif EVAL2
   /* Si el resultado de la expresión evaluada en EVAL2 es distinto de 0,
       entonces se compila este trozo de código,
       y se salta hasta la línea debajo de #endif.
       Si no, se sigue aquí debajo.
   */     
#else
   /* Si el resultado de todas las expresiones es 0,
       entonces se compila este trozo de código,
       y se salta hasta la línea debajo de #endif.
   */     
#endif


   Las cláusulas #elif y #else pueden faltar, pero no puede faltar #endif.
   Es importante respetar correctamente la sintaxis para que todo esto funcione.
   Ahora bien, con la combinación de estas cláusulas es posible indicarle al compilador distintos caminos en la compilación, pudiendo así tener varias versiones de un mismo programa dentro de un solo archivo.
   Definamos una constante del preprocesador llamada Creo_que para poner la información de qué es lo que preferimos, si el CMD o Windows, o ninguno.
   Nuestra función main() quedaría así:

int main(void) {

#if (Creo_que == Me_Conformo_con_CMD)
   printf("Hola mundo en Línea de Comandos.\n");
   getchar();                        /* Espera que se presione tecla ENTER */ 
#elif (Creo_que == Me_Gusta_mas_Windows)
   MessageBox(NULL,"\n\nHola Mundo con MessageBox.\n\n","Hola Mundo",0);
#else
   /* No se ha elegido una opción válida. El programa no hará nada. */
#endif
}


   Hemos comparado la constante Creo_que con cada una de las constantes Me_Conformo_con_CMD y Me_Gusta_mas_Windows.
   El operador == compara dos valores y determina si son iguales o no.
   Si son iguales, arroja el valor 1, que significa VERDADERO, y si son distintos, arroja 0, que significa FALSO.
   En general en C, todo valor distinto de 0 se sobreentiende como VERDADERO en una comparación. Pero esto lo veremos más adelante.
   La expresión EVAL1 fue, en nuestro caso, la comparación

Creo_que == Me_Conformo_con_CMD

   Además, esa expresión la rodeamos con paréntesis ( ).
   En general recomiendo esto de usar paréntesis alrededor de las expresiones EVAL1, EVAL2, pues el preprocesador podría confundirse creyendo que la expresión es sólo Creo_que y que lo que sigue a la derecha es la porción de código lista para ser compilada... Todo un lío.
   Falta asignarle un valor a la constante Creo_que. Ahí es cuando elegimos una de las dos opciones siguientes:

#define Creo_que Me_Conformo_con_CMD
#define Creo_que Me_Gusta_mas_Windows

   A continuación mostramos el programa terminado. Además, elegimos para compilar la opción de Windows, así que esa es la definición que daremos a Creo_que:

HolaMundo2.c

#define Me_Conformo_con_CMD  0
#define Me_Gusta_mas_Windows 1

#define Creo_que Me_Gusta_mas_Windows

#if (Creo_que == Me_Coformo_con_CMD)
   #include <stdio.h>
#elif (Creo_que == Me_Gusta_mas_Windows)
   #include <windows.h>
#else
   /* No se incluye ninguna de las dos librerías */
#endif

int main(void)
{

#if (Creo_que == Me_Conformo_con_CMD)
   printf("Hola mundo en Línea de Comandos.\n");
   getchar();                        /* Espera que se presione tecla ENTER */ 
#elif (Creo_que == Me_Gusta_mas_Windows)
   MessageBox(NULL,"\n\nHola Mundo con MessageBox.\n\n","Hola Mundo",0);
#else
   /* No se ha elegido una opción válida. El programa no hará nada. */
#endif
}


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Recordar:
La compilación condicional que tenemos gracias a las directivas #if produce como resultado un programa ejecutable, en este caso HolaMundo2.exe, tal que sólo aparecen compilados los trozos de código elegidos mediante #if.
   Así, si ponemos:

#define Creo_que Me_Conformo_con_CMD

el programa resultante HolaMundo2.exe será exactamente el mismo que nuestro ejemplo original HolaMundo.exe.
   Y si ponemos

#define Creo_que Me_Gusta_mas_Windows

el programa resultante HolaMundo2.exe será exactamente el mismo que el HolaMundoWin.exe (salvo por la frase que a propósito pusimos diferente en cada caso).
   El programa ejecutable HolaMundo2.exe jamás se entera de que en algún lugar se evaluaron condiciones de algún tipo, y no quedan en él rastros de ello.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Una última observación: Si ejecutamos la versión para CMD de este programita veremos que el texto "Línea" aparece mal escrito en la ventana de comandos CMD, mostrando incorrectamente el caracter especial de la i acentuada.
   Este desfasaje entre la codificación de caracteres especiales desde Windows a Línea de Comandos es algo común de esperar, y es por tanto una razón más para odiar CMD y que yo no quiera usarla.
   (El problema de los acentos tiene solución, que veremos en otro post).

[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: De la eficacia a la elegancia - Macros del Preprocesador (I)
Publicado por: argentinator en 04 Enero, 2013, 02:47 pm
4. De la eficacia a la elegancia - Macros del Preprocesador (parte I)

   La programación estructurada consiste en tomar un problema informático y dividirlo en subproblemas, cada uno de ellos más fácil de entender y de resolver. A esto se le llama técnica de divide y vencerás.
   En C esto se lleva a cabo con las funciones: trozos de código con un determinado nombre que resumen una lista de tareas.
   Dado que nos falta analizar varios temas antes de introducir las funciones, no lo haremos en este post.
   Sin embargo, tenemos a mano una versión simplificada del comportamiento funcional: abreviar con un nombre a toda una porción de código. Esto puede hacerse en C con las directivas #define del preprocesador, tal como hemos hecho hasta ahora. Pero más todavía, se pueden usar parámetros a fin de "pasar información" al bloque de código.
   Así, si deseamos tener abreviada una expresión que nos dé el promedio de dos números, escribiríamos algo como esto:

#define PROMEDIO(A, B) ((A + B)/2.0)

   Esto lo que hace es que, cada vez que el compilador encuentra la palabra PROMEDIO en nuestro programa, se fija cuáles son los valores A, B, y luego reemplaza toda la expresión PROMEDIO(A, B) por el resultado de la operación (A + B)/2.0.
   ¿Por qué escribo 2.0 en vez de un simple 2?
   Es para que no se nos escapen resultados con decimales cuando A y B son enteros.
   Esta cuestión será analizada luego, al estudiar tipos de datos y conversiones de tipos.
   Además, se ha rodeado la operación con un juego adicional de paréntesis, otra vez para prevenir posibles desastres.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Macros en C.

   Esto que acabamos de definir se llama una macro en lenguaje C: Consiste en darle un nombre a una porción de código, y permitir que reciba parámetros que modifiquen su comportamiento.
   Ahora, podemos usarla para calcular el promedio de dos números cualesquiera, y mostrarlo luego en la línea de comandos:

printf("Promedio: %g\n", PROMEDIO(1.2, 3.14));

   El compilador reemplazará automáticamente la ocurrencia de PROMEDIO(1.2, 3.14) por la expresión ((1.2 + 3.14)/2.0), pues reconoce como parámetro A al valor 1.2 y como parámetro B al valor 3.14.
   Vemos que aparece un modificador %g en la sentencia printf(). Eso indica que justo en esa posición irá insertado el valor del 1er parámetro que aparezca después de la coma (en nuestro caso, el resultado de PROMEDIO(1.2, 3.14), que es el número 2.17), y que además se trata de un número con dígitos tras el punto decimal.
   Pronto estudiaremos en detalle todas las opciones de la función printf().

   Nota importante: Al invocar la macro PROMEDIO, es posible pasar como parámetros cualquier cosa que no sean números.
   O sea, la macro no tiene manera de adivinar qué tipo de datos se le están pasando.
   Sin embargo, el resultado de poner cualquier cosa será que el compilador intentará realizar operaciones inválidas entre tipos de datos distintos, y el programa no compilará. Entonces ni siquiera ejecutará.
   Aquí va finalmente el programa terminado y funcionando:

MacroPromedio.c:

#include <stdio.h>

#define PROMEDIO(A, B) ((A + B)/2.0)

int main(void) {
  printf("Promedio: %g\n", PROMEDIO(1.2, 3.14));
  getchar();                                  /* Presionar ENTER para terminar */ 
}


   Al correrlo, en línea de comandos ha de aparecer:


Promedio: 2.17


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   He continuado discutiendo este tema, pero me parece preferible ponerlo como material opcional, dentro del spoiler que sigue a continuación.
   Algunas temas que tocaremos en forma tangencial, pero que retomaremos con más seriedad en un futuro, son los siguientes: compilación condicional, operador coma, diferencias entre usar "coma" y "punto y coma" para separar acciones del programa, operador condicional.

(Continuación de la discusión...)

   Todo esto en realidad es pura conversación, ya aquí me interesa realizar una macro con intenciones más burdas.
   Estoy obsesionado con el "Hola Mundo" todavía.  :banghead: :banghead:
   Sería bueno tener una versión más elegante de nuestro programa HolaMundo2.c.
   Aprovechando que ahora sabemos hacer macros con parámetros, pondríamos como parámetro la frase de salutación "Hola Mundo" o cualquier otra información.
   Esto combinado con las directivas #if, nos daría una versión más resumida y elegante del programa, que vamos a llamar HolaMundo2bis.c.
   Queremos definir una macro llamada Mostrar_INFO(S), que toma un parámetro S, de manera que si estamos compilando una versión con salida para linea de comandos, entonces el mensaje contenido en S se imprima mediante printf(), mientras que si estamos compilando una versión con una ventanita de mensaje para Windows, entonces el mensaje contenido en S se muestre con MessageBox().

   Lo que haremos, en un primer intento, será definir una macro diferente para cada caso, en cada alternativa determinada por la directiva #if. Queremos resumir todo en una sola declaración #if.

HolaMundo2bis.c

#define Me_Conformo_con_CMD  0
#define Me_Gusta_mas_Windows 1

#define Creo_que Me_Gusta_mas_Windows

#if (Creo_que == Me_Conformo_con_CMD)
   #include <stdio.h>
   #define Mostrar_INFO(S) printf(S); getchar();   
#elif (Creo_que == Me_Gusta_mas_Windows)
   #include <windows.h>
   #define Mostrar_INFO(S) MessageBox(NULL,S,"Info",0);
#else
   /* Nada */
#endif

int main(void) {
   Mostrar_INFO("Hola Mundo!!\n\n")
}


   Para probar que las dos opciones funcionan, basta editar la línea que define la (macro) constante Creo_que, y ponerle alguno de los valores Me_Conformo_con_CMD  ó Me_Gusta_mas_Windows.
   En ambos casos hemos definido la macro Mostrar_INFO, pero hay que notar que la definición definitiva es completamente distinta, según qué camino tome el compilador en la directiva #if.
   Nota adicional: Tengamos en cuenta que la macro Mostrar_INFO(S) permite que el parámetro S sea cualquier cosa, y no hay manera de verificar si es una cadena de caracteres.
   Las macros no hacen verificaciones de tipos de datos, y entonces puede ser difícil descifrar donde ha surgido algún error con el compilador.
   Por ejemplo, si se pone en S un número, se pasará ese número finalmente en la sentencia printf(S), y esto dará un error de compilador, ya que se espera que el primer parámetro de printf() sea una cadena de caracteres.

   Aquí hay un cierto riesgo de programación, ya que hay que confiar en la buena intención del programador, que definirá la macro Mostrar_INFO en todos los casos, sin olvidarse de nada.
   Para evitar este riesgo se puede proceder de otra manera, pero ya estaríamos obligados a usar una nueva cláusula #if en el código, como en el siguiente programa, que habremos de llamar HolaMundo2bisbis.c.

   Definiremos prolijamente una macro para cada situación. Es decir, una macro Mostrar_INFO_CMD para el caso que compilemos para línea de comandos, y una macro Mostrar_INFO_WIN para el caso que compilemos para ventanita de Windows.

   Ahora querríamos colocar una cláusula #define Mostrar_INFO(S), y hacer que se reemplace por Mostrar_INFO_CMD(S) en el primer caso, y por Mostrar_INFO_WIN(S) en el 2do caso.
   Al parecer, estamos pensando en anidar una cláusula #if dentro de la cláusula #define.
   ¿Se pueden anidar directivas del preprocesador?  >:D
   En general NO: evitémoslo (detalles en el futuro).   ::)
   Dado que tendremos que evaluar varias veces las condiciones acerca de cuál de las dos opciones de compilación nos interesan, preferimos definir unas abreviaturas en forma de macros, para que el programa sea más legible:


#define Me_Conformo_con_CMD  0
#define Me_Gusta_mas_Windows 1

#define Creo_que Me_Conformo_con_CMD

#define CMD_OK (Creo_que == Me_Conformo_con_CMD)
#define WIN_OK (Creo_que == Me_Gusta_mas_Windows)


   Así que CMD_OK es VERDADERO (tiene valor 1), mientras que WIN_OK es FALSO (tiene valor 0).
   Si en la definición de Creo_que cambiamos el valor, también cambiarán los valores de CMD_OK y WIN_OK.
   Ahora seguimos con la definición de las macros Mostrar_INFO_CMD y Mostrar_INFO_WIN, lo cual se hace con un pequeño cambio en el código de HolaMundo2.c:


#if CMD_OK
   #include <stdio.h>
   #define Mostrar_INFO_CMD(S) ( printf(S), getchar(), 0 )
#elif WIN_OK
   #include <windows.h>
   #define Mostrar_INFO_WIN(S) ( MessageBox(NULL,S,"Info",0), 0 )
#else
   /* Nada */
#endif


   ¿Qué diablos hemos hecho ahí?  :o

   Prestemos atención a unos sutiles pero terroríficos cambios.
   En vez de punto y coma para separar sentencias, hemos puesto comas.   >:D
   ¿Se puede hacer esto? El signo de coma (,) tiene un significado muy distinto del punto y coma (;).
   Mientras el punto y coma es un terminador de instrucción, la coma es un operador cuyo resultado es, simplemente el valor que figura más a la derecha.
   Así, si escribimos la operación (9.11, 7.14), el resultado es 7.14, por ser el valor más a la derecha del operador coma.  ;)
   Por otro lado, se supone que todas las funciones en C devuelven un cierto valor, de algún tipo, que puede ser número, cadena de caracteres, puntero, etc., etc., etc.
   Las funciones printf(), getchar() y MessageBox() devuelven un valor.
   Así que el lenguaje C lo que hace es llamar a esas funciones, digamos printf(), seguida de getchar, hace lo que cada una tenga que hacer, y luego el operador coma simplemente se dedica a retornar un valor, el cual nosotros no vamos a usar para nada.  >:D

   Estos trucos encierran algunos vicios de mala programación, que en su momento tendremos que ver cómo corregir.
   Por ahora nos ponemos contentos de nuestra astucia, y vemos que a fines prácticos nos resulta lo mismo escribir:

printf("Hola!"); getchar();

que

printf("Hola!"), getchar();

   Los valores que devuelven son compatibles, por ser todos números enteros.
   Pero resulta que no "me la quiero jugar" a que voy a tener la suerte de que esos valores sean compatibles. Quiero asegurarme que el resultado final del operador coma será siempre de tipos compatibles, para evitar efectos no deseados.
   La manera más sencilla de hacer esto es agregando un valor inútil, como un 0, así:

printf("Hola!"), getchar(), 0;

   Recién al final de la lista de funciones separadas por comas es que ponemos el terminador de instrucción punto y coma, que en alguna parte tiene que estar, si no, se nos desmadra el programa.
   Sin embargo, vemos que en nuestras macros no está ese dichoso punto y coma, y de hecho no está "a propósito", porque necesitamos diferir su colocación, ya veremos por qué.
   En realidad vamos a seguir jugando con los operadores de C, y mientras estamos evaluando fórmulas no podemos poner el "punto y coma" porque nos terminaría abruptamente la expresión, dando resultados erróneos.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Vamos a usar el operador de decisión ?:.
   Este operador toma una cierta expresión y se pregunta si es verdadera.
   En caso afirmativo evalúa la expresión que está inmediatamente a la derecha del signo ?, y en caso contrario salta hasta la expresión inmediatamente a la derecha del signo :.
   El resultado final del operador es la evaluación elegida de una de estas dos opciones.
   Un ejemplo sencillo sería éste:


(1 == 1)?"Hola": "Chau";
(1 == 2)?"Hola": "Chau";


   Aquí se pregunta si 1 es igual 1. Como es verdadero, el resultado de la operación será la frase "Hola".
   Luego se pregunta si 1 es igual a 2. Como es falso, el resultado de la operación será la frase "Chau".
   Lo que nosotros haremos será preguntar si CMD_OK es verdadero.
   Si lo es, evaluamos la expresión Mostrar_CMD(S), la cual incluye una lista de llamadas a funciones separadas por comas, y cuyo resultado final es un 0, que no usamos para nada.
   Si nos da falsa, entonces volveremos a hacer otra pregunta, otra vez con el operador ?:, esta vez indagando si WIN_OK es verdadera.
   En ese caso evaluamos la expresión Mostrar_WIN(S), la cual incluye una llamada a la función MessageBox().
   Por último, si esta consulta nos da falsa, no hacemos nada, y simplemente rellenamos con un 0, para que toda la expresión tenga algún valor como resultado.

   Todo esto se encierra con paréntesis para mayor seguridad en el código, y finalmente es posible colocar un terminador punto y coma.
   Nuestra macro tiene ahora esta forma:

#define Mostrar_INFO(S) ( CMD_OK? Mostrar_INFO_CMD(S) : (WIN_OK? Mostrar_INFO_WIN(S) :(0) ) )

   Se puede apreciar que es difícil de leer, y además rebasa la longitud del renglón.
   Para poder separar una línea del preprocesador en varios trozos, se lo puede hacer indicándolo con la barra invertida: \. La reescribimos, pues, así:


#define Mostrar_INFO(S) \
   (CMD_OK? Mostrar_INFO_CMD(S) : \
      ( WIN_OK? Mostrar_INFO_WIN(S) : (0) ) )


   ¿Por qué esto funciona?
   Puede parecer extraño que un operador del lenguaje C funcione bien con el preprocesador, sobretodo en esta situación en la cual aparecen funciones de librerías distintas.
   Por ejemplo, en la expresión anterior, si tenemos que CMD_OK es verdadero, esto significa que la librería windows.h no se ha cargado, y entonces la función MessageBox() queda inaccesible.
   ¿Cómo es que el programa compila igual?
   La respuesta está en el modo en que trabaja el preprocesador: Intentará evaluar todas las expresiones que involucren constantes, dejando como resultado final la expresión resultante a fin de cuentas.
   Como CMD_OK es una constante que el compilador conoce, pues la hemos definido con el preprocesador, el operador ?: inmediatamente reconoce que el valor es 1, por ejemplo, y pasa directamente a la expresión correspondiente, ignorando el trozo que contiene la llamada a MessageBox().
   Como todo esto sucede en la etapa de compilación, el programa final no se entera de una posible referencia a MessageBox().

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   El resultado final es el siguiente programa:

HolaMundo2bisbis.c:

#define Me_Conformo_con_CMD  0
#define Me_Gusta_mas_Windows 1

#define Creo_que Me_Conformo_con_CMD

#define CMD_OK (Creo_que == Me_Conformo_con_CMD)
#define WIN_OK (Creo_que == Me_Gusta_mas_Windows)

#if CMD_OK
   #include <stdio.h>
   #define Mostrar_INFO_CMD(S) ( printf(S), getchar(), 0)   
#elif WIN_OK
   #include <windows.h>
   #define Mostrar_INFO_WIN(S) ( MessageBox(NULL,S,"Info",0), 0)
#else
   /* Nada */
#endif

#define Mostrar_INFO(S) \
   (CMD_OK? Mostrar_INFO_CMD(S): \
      (WIN_OK? Mostrar_INFO_WIN(S): (0)) )

int main(void) {   
   Mostrar_INFO("Hola Mundo!!\n\n") ;
}


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Estos trucos funcionan sólo si el compilador tiene determinadas características. Ha de ocurrir que el preprocesador funciona realmente como hemos descripto: ignorando las partes de una expresión condicional en forma automática, cuando la condición puede saberse si es verdadera o falsa en la etapa misma de la compilación.
   Esto es posible en GCC, pero no es parte de la definición estándar de ISO C99.
   En general recomiendo no confiar en que el compilador actuará de este modo.

[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: De la eficacia a la elegancia - Macros del Preprocesador (II)
Publicado por: argentinator en 04 Enero, 2013, 07:40 pm
5. De la eficacia a la elegancia - Macros del Preprocesador (parte II)

   Veamos lo que dice el comité de estandarización de C sobre algunas cuestiones del post anterior.
   Sobre el operador ,:

Citar

6.5.17 Comma operator

The left operand of a comma operator is evaluated as a void expression; there is a
sequence point after its evaluation. Then the right operand is evaluated; the result has its
type and value.

   Fuente: http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf (http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf)

Claramente especifica que en una expresión de la forma:

(A, B)

en que aparece el operador "coma", 1ero se evalúa la expresión A, se hacen vaya uno a saber qué gestiones (se da término a todos los llamados "efectos colaterales"), y recién cuando esto ha terminado se evalúa la expresión B.
   Por lo tanto, si escribimos alegremente una lista de acciones como:

printf("Hola"), getchar();

se ejecutará 1ro printf("Hola"), y sólo tras haber terminado esta acción se pasará a ejecutar getchar().
   En general, ¿qué ocurre cuando tenemos una lista con más de dos operandos separados por comas? ¿En qué orden se evalúan?
   Si tenemos una lista de 3 acciones como:

(A, B, C);

   ¿Qué dice el estándar?
   En primer lugar el operador , es asociativo de izquierda a derecha.
   Así, la terna (A, B, C) se asocia como ((A, B), C).
   Luego, debido a la regla de evaluación, tenemos que efectivamente se evalúa primero (A, B) hasta terminar, y luego se pasa a evaluar C.
   Pero para que termine de evaluarse (A, B), primero se debe terminar de evaluar A, y luego se pasa a B.
   Así que el orden de izquierda a derecha está asegurado para el operador ,.
   Notar que $reste efecto de ordenamiento de los efectos colaterales en general no se puede asegurar con los demás operadores asociativos del lenguaje C. El estándar lo deja sin especificar$R.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Pasemos a analizar el operador condicional ?:.
   Según el estándar, en la expresión

(A)? B: C

primero se evalúa A hasta terminar todos los efectos colaterales.
   Si A es no nulo (verdadero), entonces se pasa a evaluar B, y nunca se hace nada con la expresión C.
   En cambio, si A es nulo (falso), se pasa a evaluar C, y nunca se hace nada con B.

(En spoiler continuamos el spoiler del post anterior...)

(continúa la discusión del post anterior...)

   Ahora nos preguntamos qué ocurre si A es una constante en la etapa de compilación. Esto quiere decir que su valor está previamente definido, y de entrada, antes de la compilación misma, se puede saber qué expresión se ejecutará, si la B o la C.
   El compilador tiene el derecho a decidir eliminar la rama B ó C que nunca va a ejecutarse, y esto es lo que nos ha permitido (con el GCC invocado dentro de nuestro wxDevC++) directamente eliminar en la mera etapa de compilación aquella porción de instrucciones que no nos interesa mantener.
   Como esto ocurre antes de la compilación definitiva, el compilador no se las tiene que ver nunca con la aparición de una función MessageBox(), por ejemplo, sin que hayamos incluido la librería windows.h.
   Pero la clave de este asunto es que esta práctica no está avalada por el C estándar, sino que depende de la implementación específica de nuestro compilador.
   Con el siguiente experimento, podemos ver cómo funciona el compilador GCC que estamos usando:

ExperimentoBasura.c

#include <stdio.h>

int main(void) {
    int x = (('a' == 'a')? 1111: (int) (basura_sin_sentido())  )  ;

    printf("x = %d", x);
    getchar();
}


   Este programa ignorará la llamada a la función basura_sin_sentido(), que no está declarada en ninguna parte, y no dará mensaje de error en la compilación, debido al optimizado modo en que evalúa las expresiones con constantes.
   El compilador detecta que siempre la condición 'a' == 'a' es verdadera, y deja asignado el valor x = 1111.
   Lo demás, es como si nunca hubiera existido.
   Obviamente, esto es un efecto indeseado del compilador, y debemos procurar evitarlo, porque introduce errores indetectables a la vez que extraños en nuestros programas.

   Así, tendremos que investigar una manera diferente de realizar la compilación condicional que hicimos en HolaMundo2bisbis.c.
   Por ahora, como resultado de esta discusión, voy a retractarme del programa HolaMundo2bisbis.c, que sólo funciona para el compilador GCC, y en cambio voy a dejar una versión definitiva, que es correcta para cualquier compilador que respete el estándar:

HolaMundo2bisbisOk.c

#define Me_Conformo_con_CMD  0
#define Me_Gusta_mas_Windows 1

#define Creo_que Me_Conformo_con_CMD

#define CMD_OK (Creo_que == Me_Conformo_con_CMD)
#define WIN_OK (Creo_que == Me_Gusta_mas_Windows)

#if CMD_OK
   #include <stdio.h>
   #define Mostrar_INFO_CMD(S) ( printf(S), getchar(), 0)   
#elif WIN_OK
   #include <windows.h>
   #define Mostrar_INFO_WIN(S) ( MessageBox(NULL,S,"Info",0), 0)
#else
   /* Nada */
#endif

#if CMD_OK
   #define Mostrar_INFO(S) Mostrar_INFO_CMD(S)
#elif WIN_OK
   #define Mostrar_INFO(S) Mostrar_INFO_WIN(S)
#else
   /* Al parámetro S no se le hace nada, y lo dejamos pasar como resultado */
   #define Mostrar_INFO(S) S
#endif

int main(void) {   
   Mostrar_INFO("Hola Mundo!!\n\n") ;
}


[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 6. Etapas del Preprocesador
Publicado por: argentinator en 05 Enero, 2013, 12:12 am
6. Etapas del Preprocesador

   ¿Qué hace el preprocesador?
   ¿Podemos ver directamente lo que hace el preprocesador?

   El preprocesador transforma un programa en C en otro programa en C, pero con menos directivas de preprocesador que antes...
   Luego el preprocesador actúa tantas veces como sea necesario hasta lograr una versión definitiva sin cláusulas que contengan el símbolo # del preprocesador.

   Demos un ejemplo sencillo.

ObsPrepr.c


#define EJEMPLO_MACRO(X) (X + X)
#define DOBLE_1000 EJEMPLO_MACRO(1000)

int main(void) {

    return DOBLE_1000;   
}


   Es un programa que no hace nada, aunque retorna un número al sistema operativo, precisamente el valor 2000.

   Pero el preprocesador sí que hace cosas. Toma el código y reemplaza la macro EJEMPLO_MACRO en todas sus ocurrencias por la definición dada en la 1er línea.
   Tras esta traducción hecha por el preprocesador, se obtiene, en el aire, un nuevo programa en C, con este aspecto:

ObsPrepr.c (tras primera fase de traducción del preprocesador):


#define DOBLE_1000 (1000+1000)

int main(void) {

    return DOBLE_1000;
}


   Todavía quedan directivas de preprocesador, así que se realiza una nueva traducción, y nos queda:

ObsPrepr.c (tras segunda fase de traducción del preprocesador):


int main(void) {

    return (1000+1000);

}


   El compilador (GCC) toma la expresión (1000+1000), que contiene constantes, y las evalúa antes de finalizar la compilación, poniendo ahí directamente el valor 2000.
   La mayoría de compiladores hacen esta evaluación previa de expresiones constantes, sin embargo el estándar C no nos asegura que este cálculo se efectúe realmente en etapa de compilación.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Esas "traducciones" que hemos mencionado no hay modo de visualizarlas (en general) durante una compilación real.
   Sin embargo, es posible obtener alguna información de lo que ocurre con las acciones del preprocesador.
   Para ello usaremos el operador (de preprocesador) # , cuya acción es agregarle comillas a una expresión cualquiera.
   ¿Para qué sirve esto? Lo que hace es producir una cadena de caracteres que dice "lo mismo" que ponemos a la derecha del signo #.
   Con esto, tenemos disponible una cadena de caracteres que podemos usar luego para imprimir en pantalla, y así visualizar información de acciones del preprocesador.

   En el ejemplo anterior, podemos hacer algunos cambios para "depurar" más o menos cuál ha sido el resultado de los reemplazos del preprocesador.
   Vamos a definir una macro DEB_(A), que toma el argumento A y lo rodea con comillas a fin de convertirlo en una cadena de caracteres.
   Esto no nos sirve de mucho en principio, porque no nos da información de la traducción hecha por el procesador. Pero igual nos será útil luego.
   Su definición es así:

#define DEB_(A) #A

   Así, si invocamos la macro con algo como DEB_(Hola Mundo), nos genera la cadena entrecomillada "Hola Mundo".
   Esta cadena es una constante en la etapa de la compilación. O sea que en el programa finalmente compilado será un objeto constante, estático.

   Ahora daremos una macro ligeramente distinta, llamada DEB(X).
   Con ella es llamaremos a DEB_ con el argumento X, así:

#define DEB(X) DEB_(X)

   ¿Qué diablos hace esto?
   Cuando hacemos una invocación a la macro DEB(X), el preprocesador primero evalúa el valor de X, hasta "desenrollarlo" por completo, si es posible.
   Una vez terminado esto, realiza la "llamada" a la macro DEB_(X), pero con el valor de X ya sustituido apropidamente por su valor.

   Escribamos un programa y veamos el resultado que produce:

ObsPreprBis.c


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


#define DOBLE(X) (X + X)
#define N DOBLE(1000)

#include <stdio.h>

int main(void) {
   
    printf("%d\n", N);
    printf(DEB_(N) " = " DEB(N) "\n\n");
    printf("%d\n", DOBLE(17));
    printf(DEB_(DOBLE(17)) " = " DEB(DOBLE(17)) "\n\n");
    getchar(); 

}


   El resultado al ejecutar el programa es esto:


2000
N = (1000 + 1000)

34
DOBLE(17) = (17 + 17)


   El efecto de DEB_(N) ha sido simplemente agregarle comillas a N, quedando la cadena "N".
   En cambio, el efecto de DEB(N) ha sido expandir N a su definición: DOBLE(1000), que a su vez se traduce en (1000 + 1000).
   Luego de eso se invoca la macro DEB_ con (1000 + 1000) como argumento, produciendo la cadena entrecomillada "(1000 + 1000)".
   Algo análogo ha sucedido con DOBLE(17).

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Bien, el compilador (GCC) hace las traducciones del preprocesador en el orden en que hemos indicado.
   ¿Es esto así en el estándar C?
   La respuesta es afirmativa, como puede verse en la sección 6.10.3 de The New C Standard, libro de nuestra lista bibliográfica.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 7. Caracteres (char) y Strings (char*)
Publicado por: argentinator en 05 Enero, 2013, 01:26 pm
7. Caracteres (char) y Strings (char*)

   Son muy comunes las tareas en las cuales se muestra información mediante mensajes de texto. Luego, es muy importante entender cómo se representan los caracteres en C, así como las cadenas de caracteresstrings).
   Por un lado está su representación en memoria, que se hace mediante bits (dígitos binarios 0 y 1), por otro lado está el modo en que el lenguaje C permite indicar caracteres y strings.
   Hay diversas vicisitudes, y la mayoría de ellas serán abordadas en este post.

Caracteres en C

Entendiendo los caracteres

   Primero veamos qué cosa es un caracter.
   En nuestra pantalla de la computadora vemos letras, números y signos de todo tipo.
   Todos ellos son caracteres, los cuales tienen un "dibujito" asociado y su significado es inteligible sólo por seres humanos.
   Cada caracter tiene una representación (o codificación) con bits (0s y 1s) a nivel interno en la máquina.

   Así, la codificación para el caracter 'A' es 01000001.
   Este código es un número escrito en base 2 (binaria), el cual tiene una equivalencia en base decimal (la que usamos los humanos, porque tenemos diez dedos para contar), que equivale a 65.

   Hay una tabla que asigna en forma estandarizada códigos binarios a caracteres, para los códigos que van del 00000000 (decimal 0) al 01111111 (decimal 127).
   Esa tabla es la famosa ASCII, y es compartida y respetada por una vastísima mayoría de entre todos los sistemas de computación. (Las excepciones pueden considerarse "rarezas").
   Para códigos del 128 en adelante ya no podemos estar seguros de nada.

http://es.wikipedia.org/wiki/ASCII (http://es.wikipedia.org/wiki/ASCII)

   Aún cuando los códigos del 10000000 (128) al 11111111 (255) no tengan caracteres ASCII asignados, podemos esperar (de nuevo, en la vasta mayoría de sistemas) que sigan teniendo algún tipo de caracter asignado. Son caracteres especiales, que varían según el sistema, la región o idioma, entre otros menjunjes.

   De cualquier manera, el lenguaje C reconoce como caracter a cualquiera de los códigos binarios que van del 0 al 255, por más que sean o no de la tabla ASCII.

   ¿Puede haber valores más allá del 255? Sí. ¿El C los acepta? Sí. Son temas que discutiremos mucho más adelante.

[cerrar]

   Si en el lenguaje C queremos referirnos a un caracter determinado, por ejemplo el @, no podemos simplemente escribir el caracter y esperar que todo funcione.
   Más concretamente, si quisiéramos verlo impreso en pantalla mediante, por ejemplo, la función printf(), no podemos escribir printf(@), porque eso estaría mal, y enseguida el compilador se nos quejaría de que eso no tiene sentido.
   La manera correcta de indicar en C una constante de caracter es rodeando al caracter con comillas simples, así: '@'.

   Los caracteres ASCII con códigos del 0 al 31 no son imprimibles, sino que se usan para realizar operaciones especiales, como saltos de línea, tabulaciones, marcas especiales en los archivos, etc.
   Se denominan caracteres de control. Excepcionalmente, el ASCII 127 también se considera un caracter de control (tiene código binario 01111111, suele ser no visible ni imprimible, y se lo asocia con la tecla de borrado hacia la derecha DEL ó DELETE).
   Como no se pueden imprimir, es difícil referirse a ellos en forma directa.
   Además, es posible que en distintos sistemas las mismas funciones se realicen por caracteres de control con distintos códigos.
   Para evitar esta ambigüedad, el lenguaje C permite que el programador se refiera a algunos caracteres de control (no a todos), mediante una notación especial, que luego el compilador se encarga de gestionar y traducir a los códigos verdaderos usados en el sistema.
   Se accede a ellos mediante una secuencia de escape, anteponiendo una barra invertida: \.
   He aquí la lista:

Secuencias de escape para caracteres especiales en C

\( \bullet \) \a   Sonido audible (sí, hay un caracter que hace un pitido :D )
\( \bullet \) \b   Retroceso (Corresponde a la tecla backspace, que borra un caracter hacia la izquierda)
\( \bullet \) \f   Salto de formato (formfeed  ??? )
\( \bullet \) \n   Salto de línea (linefeed)
\( \bullet \) \r   Retorno de carro (carriage return, se refiere a que el cursor vuelve al principio de la línea en pantalla, o bien que la impresora vuelve a comenzar desde la izquierda, pero sin bajar a la línea siguiente)
\( \bullet \) \t   Tabulación horizontal (Tab, simula un salto de varios espacios en blanco, mediante la tecla TAB)
\( \bullet \) \v   Tabulación vertical (V Tab  ??? )
\( \bullet \) \\   \ Barra invertida (backslash)
\( \bullet \) \'   ' Apóstrofo (comilla simple)
\( \bullet \) \"   " (comilla doble)
\( \bullet \) \?   ? (Interrogación)

   Así por ejemplo, si queremos indicar la constante de caracter "avance de línea", debiéramos escribir '\n'.
   Como es lógico, dado que el signo \ lo estamos usando con un significado especial en C, como secuencia de escape de caracteres especiales, necesitamos una manera de poder generarlo sin ambigüedad alguna.
   Esto se hace poniendo una doble barra: '\\', lo cual, al mostrarlo en pantalla dará el resultado: \, o sea, se muestra una sola barra.
   Un problema similar lo tenemos con las comillas simples, ya que ellas se usan para encerrar caracteres. Si queremos referirnos explícitamente al caracter "comilla simple" necesitamos usar una secuencia de escape, la siguiente: \', que con el entrecomillado queda así: '\''. Al correr el programa se verá tan solo: '
   Finalmente, las comillas dobles se usan, como hemos visto, para encerrar cadenas de caracteres: "Hola Mundo".
   Si necesitamos mostrar en pantalla el caracter "comilla doble", se presenta una situación ambigua, y tenemos que usar de nuevo una secuencia de escape, que en este caso es: \", la cual, con el entrecomillado queda así: '\"'. Al correr el programa, se verá en la pantalla tan sólo: "

   Se aprecia que hay también una secuencia de escape para el signo de interrogación: '\?'. Esto está relacionado con los trigrafos, tema que no estudiaremos por ahora.

[cerrar]

   Lo que nos va a interesar a nosotros es tener a mano una lista resumida de las secuencias de escape más utilizadas:

\( \bullet \) \n   Salto de Línea
\( \bullet \) \\   Caracter \
\( \bullet \) \'   Caracter '
\( \bullet \) \"   Caracter "

   El salto de línea lo que hace es bajar a la siguiente línea en la pantalla, y posicionar el cursor al principio de dicha línea.
   En la impresora, esto se consigue con un avance a la siguiente línea, y posicionando el cabezal de impresión al principio de dicha línea (a la izquierda de todo). (¡¡Bienvenidos a la prehistoria!! :P )
   Ahora bien. Desconocemos el código binario que los caracteres especiales, como el salto de línea '\n', tendrán en cada sistema. Si por alguna razón necesitamos trabajar directamente con los códigos numéricos de los caracteres de control, o de los caracteres ASCII, es aún posible acceder a ellos mediante el número que les corresponde, otra vez mediante secuencias de escape especiales.
   No obstante, estos códigos no están en números decimales, ni tampoco binarios, sino en bases 8 (octales) y 16 (hexadecimales). La explicación, en el Spoiler:

Secuencias de Escape Octales y Hexadecimales

   Los dígitos en base 8 son:
   
0  1  2  3  4  5  6  7

   Traducir de binario a octal es fácil, porque se toman ternas de dígitos binarios y se las traduce a su correspondiente octal, así:

000 ------- 0
001 ------- 1
010 ------- 2
011 ------- 3
100 ------- 4
101 ------- 5
110 ------- 6
111 ------- 7

   Por ejemplo, el caracter 't', cuyo código binario es 01110100, en octal es 164.
   (La traducción tiene que hacerse de derecha a izquierda para que el resultado sea el correcto, o sea, separar las ternas de dígitos así: 01-110-100).
   En decimal 't' es el ASCII 116.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Los números hexadecimales están en base 16, y sus dígitos son:

0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F

   Se usan las letras A, B, C, D, E, F, para denotar dígitos, porque la base 16 necesita 6 dígitos adicionales, y se estila usar esas letras para tal fin.
   Así, la A equivale al 10 en decimal, la B al 11 en decimal, etc.
   La traducción de binario a hexadecimal también es directa:

0000 ------- 0
0001 ------- 1
0010 ------- 2
0011 ------- 3
0100 ------- 4
0101 ------- 5
0110 ------- 6
0111 ------- 7
1000 ------- 8
1001 ------- 9
1010 ------- A
1011 ------- B
1100 ------- C
1101 ------- D
1110 ------- E
1111 ------- F

   Así, el caracter 't', cuyo código binario es 01110100, en hexadecimal es 74.
   Otro ejemplo: el caracter ']', cuyo código binario es 01011101, en hexadecimal es 5D.
   En decimal sería el ASCII 93.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Para expresar un caracter mediante su código en octal, se escribe:

   \***, donde *** son los tres (también pueden ser sólo uno o dos) dígitos en octal del código que corresponda.

   Por ejemplo, para 't' hacemos '\164'.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Para expresar un caracter mediante su código en hexadecimal, se escribe:

\x**, donde ** son los dos dígitos en hexadecimal del código que corresponda.

   Por ejemplo, para 't' hacemos '\x74', y para ']' hacemos '\x5D'.

   Si bien la x tiene que estar en minúsculas, los dígitos hexadecimales pueden estar tanto en mayúsculas como en minúsculas.
   O sea que es lo mismo poner '\x5d' en el último ejemplo.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Hay países donde ciertos caracteres están inaccesibles en el teclado. Así que en ese caso se usan convenciones que permiten generar esos caracteres mediante la combinación de otros tres, de fácil acceso. Se denominan trigrafos.
   También se pueden utilizar símbolos alternativos.
   No voy a detallar esto (por ahora).

   Para una consulta rápida, ver por ejemplo: http://c.conclase.net/curso/?cap=903 (http://c.conclase.net/curso/?cap=903)

[cerrar]

   Todo esto de las secuencias de escape puede marearlo a uno.
   Pero para salir de dudas conviene siempre hacer un programita, para entender lo que pasa.
   Las secuencias de escape las escribe el programador, mientras que el caracter definitivo que le corresponde es lo que sale en la pantalla.
   Para verlo, utilizaremos la función putchar() de la biblioteca estándar <stdio.h>, la cual se encarga de enviar a la pantalla un caracter a la vez.
   El siguiente programa (en spoiler) muestra el uso de las comillas simples para denotar constantes de caracter, el uso de secuencias de escape octales y hexadecimales, y también el uso de la secuencia de escape '\n' para generar un salto de línea mediante putchar().

TestingChars.c

(programa TestingChars.c)


#include <stdio.h>

int main(void) {
   printf("Probando caracteres!!\n\n");
   
   printf("Primero los codigos octales: ");
   putchar('t');
   putchar('\164');
   putchar(']');
   putchar('\135');

   putchar('\n');
   
   printf("Ahora los codigos hexadecimales: ");
   putchar('t');
   putchar('\x74');
   putchar(']');
   putchar('\x5d');

   putchar('\n');
   
   getchar();   
}

[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   El tipo de datos que se usa  8^) en C para representar caracteres es char.
   (Nota técnica: esto no significa que los caracteres son constantes de tipo char. En realidad son de tipo int, pero esto será estudiado mucho más adelante en este curso).
   Una constante de tipo char necesita exactamente 1 byte (8 bits o dígitos binarios) de memoria para ser almacenado en la computadora.

   El lenguaje C permite operar directamente con los códigos numéricos asociados a cada caracter.
   Tal es así que el tipo de datos char se considera uno de los tantos tipos numéricos de C.
   Los números representados por char son enteros.

   No es siempre muy claro, y depende del compilador o el sistema, si los números char admiten signo o no.
   En el caso de que admitan signo, los números que representan van del -128 al 127.
   Mientras que si no admiten signo, el rango de números va del 0 al 255.
   (Nota ténica: estamos asumiendo bytes de 8 bits. Puede haber sistemas cuyos bytes tengan más de 8 bits).
   El bit de más a la izquierda se considera el "bit indicador de signo" para el caso de enteros con signo, o si sumar o no el número 128, en caso de enteros sin signo.

   En cualquier caso, los números del 0 al 127 siempre están disponibles en el rango de char.
   ¿Representan estos valores los códigos de los caracteres del estándar ASCII?
   En la mayoría de sistemas informáticos, podemos responder que sí. Pero como hay unos pocos casos en que esto no se puede saber de antemano, el estándar C no da nada por seguro.

   Si nos interesa realizar operaciones aritméticas con los char, y queremos estar seguros de si tiene o no tiene signo, y así controlar el rango de valores, podemos utilizar los tipos más específicos siguientes:

signed char      (con signo, de -128 a 127)
unsigned char    (sin singo, de 0 a 255)

   El char por sí solo podría ser cualquiera de esos dos, y no está especificado por el estándar.
   Puede que el compilador sí haya elegido cuál de las dos variantes (con o sin signo) elegir, pero a nosotros no nos interesa, porque:

   No debemos utilizar características que son dependientes de nuestro compilador, a fin de que nuestros programas sean portables a otros sistemas sin problemas.

   Para eso se inventaron, justamente, los estándares: para la portabilidad.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Strings: Cadenas de caracteres

   Una string o cadena de caracteres es una secuencia ordenada de caracteres.
   Si quisiéramos escribir la frase Hola Mundo, sería incómodo especificar uno a uno los caracteres que la componen:

'H' 'o' 'l' 'a' ' ' 'M' 'u' 'n' 'd' 'o'

   A fin de abreviar este tedio, se procede a poner todos los caracteres en cadena ordenada, y luego encerrar toda la cadena entre comillas dobles, así:

"Hola Mundo"

   El tipo de datos básico asociado en C a las strings es char*.
   Otro día les explico qué es ese asterisco: *.  ::)

   Lo importante es que, aquellas funciones que desean mostrar o manipular cadenas de caracteres, tienen parámetros de tipo char*, que no es lo mismo que un simple char.
   Así, printf("Hola"); es correcto, porque printf acepta strings (de tipo char*).
   También printf("x"); es correcto, y muestra una x en pantalla.
   Pero printf('x'); es incorrecto, porque 'x' es una constante de tipo char, no una string.

   ¿Cómo hace C para diferenciar char de char*, y cómo almacena las char* en la memoria?[/b]

   En la memoria, se necesita indicarle al compilador de C que una cadena de caracteres termina en tal o cual lugar.
   En el lenguaje C se presupone que una string se almacena con sus caracteres colocados en posiciones contiguas de la memoria, y se toma la convención de que el final de la cadena de caracteres se alcanza sólo cuando se encuentra un caracter con código binario 00000000.
   Éste se llama caracter nulo, su código ASCII es el 0, y se puede especificar en octal como '\000' (o simplemente '\0'), o en hexadecimal como '\x00'.

   Las cadenas así formadas se denominan strings terminadas en nulo.

   Son típicas del lenguaje C, y no están presentes en general en otros lenguajes.
   Por lo tanto, la cadena entrecomillada "Hola Mundo", en realidad es la secuencia de caracteres siguiente:

'H' 'o' 'l' 'a' ' ' 'M' 'u' 'n' 'd' 'o' '\0'

   Esos caracteres se almacenan todos en posiciones contiguas de la memoria (en el ejemplo son 11 bytes, contando el byte ocupado por el caracter nulo), que han de estar reservados sólo para esa string.
   No se pueden usar para otra cosa, porque se producen violaciones de acceso de memoria RAM, lo cual repercute en errores de ejecución del programa.
   Lo más probable es que la computadora se bloquee.  :o :o :o :o

   Este es un ejemplo claro de que el lenguaje C permite situaciones problemáticas, que son responsabilidad del programador el evitarlas.
   El acceso indebido a ciertas posiciones de memoria puede ser un problema de aparición muy frecuente en C.
   Con las strings estamos expuestos ya a estas dificultades.  :banghead: :o

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Y ahora vemos por qué "x" es distinto de 'x'.
   En la memoria, "x" se almacena como la secuencia de caracteres:

'x' '\0'

la cual ocupa 2 bytes de memoria, mientras que 'x' es un solo caracter que ocupa 1 solo byte.

   Ahora bien, podemos tener también una cadena vacía, sin caracteres.
   Se la denota con "", y se ve que no hay nada entre ambas comillas.
   En memoria, la cadena vacía ocupa 1 byte, debido a que aún está presente el caracter nulo, que indica el punto donde termina la cadena, por más vacía que sea:

'\0'

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   En cualquier caso, el caracter nulo '\0' sólo está presente en la representación interna (o sea, en memoria RAM) de una string dada, pero los caracteres nulos no se muestran en pantalla, ni se imprimen (al menos mediante funciones que, como printf(), envían o manipulan strings).
   Otra curiosidad es que, una constante de cadena que intercale un caracter nulo en el medio de ella, quedará cortada en ese punto, desde el punto de vista del compilador de C.
   Así, si probamos con la sentencia:

printf("Hola\0 Mundo");

lo que se verá en la pantalla es sólo esto:

Hola

   Esto es así porque el C considera que el caracter nulo \0 marcó el fin de la cadena de caracteres, e ignora lo que quedó a la derecha de él (la palabra Mundo)
   ¿Y esto no sería un desperdicio de memoria RAM? La verdad es que sí. Pero bueno, el lenguaje C es así.

   Y nos hacemos una última pregunta:
   ¿Qué ocurre si deseamos utilizar cadenas de caracteres que contengan al caracter con código ASCII 0 (el caracter nulo) como parte de ellas?
   Bueno, simplemente nos aguantamos la mala suerte de no poder hacer tal cosa.  :(

   Sin embargo, es posible mediante putchar() o funciones similares, enviar caracteres en forma individual. En particular esto permitiría enviar caracteres nulos a un dispositivo de salida.

putchar('\0');

   (Eso en la pantalla se ve como un espacio en blanco...)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Espacios en blanco:

   El espacio en blanco, ése que se genera presionando la tecla "barra espaciadora", tiene código ASCII 32, que en binario es 00100000, en octal es 040@, y en hexadecimal es 20.
   Para generarlo, tenemos pues estas alternativas:

' '       (poner el espacio en blanco entre comillas simples)
'\040'    (octal 040)
'\x20'    (hexadecimal 20)

Discusión sobre los espacios en blanco

   Hay otros caracteres que al mostrarlos generan el efecto visual de un espacio en blanco, pero internamente no son espacios en blanco.
   Uno de ellos es '\t' (tabulador), que genera (el efecto visual de) uno o más espacios en blanco.
   En las impresoras representa un salto a la siguiente marca (que suelen ser columnas múltiplos de 8).
   Otro es el ASCII 255 (binario 11111111, octal 377, hexadecimal FF), que tradicionalmente se usa para indicar un espacio en blanco de "no separación", ya que comunmente el ASCII 32 se usa para indicar separación de palabras.
   La diferencia está en que los espacios en blanco "comunes" (ASCII 32) suelen ser manipulados por los programas editores de texto de forma relajada y flexible, agregando o quitando espacios en blanco según convenga.
   En cambio, el ASCII 255 sería una versión de "espacio blanco rígido", que no admite esas manipulaciones.
   Por último, el '\0' se muestra como un espacio en blanco.

   Podemos agregar las siguientes líneas a nuestro programa TestingChars.c, a ver qué pasa:


   printf("\\t_\t_");
   printf("\n");
   printf("\\xFF_\xFF_");                /* '\xFF' es el ASCII 255 */
   printf("\n");

   printf("\\0_"); putchar('\0'); printf("_");    /* Caracter nulo */
   printf("\n");


   Lo que queremos decir con todo esto es que, visualmente, hemos perdido información.
   Se ven espacios en blanco, pero no son el caracter espacio en blanco con código ASCII 32.
[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 8. Enteros en C (parte I)
Publicado por: argentinator en 05 Enero, 2013, 04:35 pm
8. Enteros en C (parte I)

0. Números enteros, reales y complejos:

   Tenemos tres clases tipos de datos numéricos en C: enteros, reales y complejos.

Representación de números en C y en la computadora

   Los enteros son los que normalmente usamos para contar: 0, 1, 2, 3, 4, etc., y pueden ser positivos o negativos (-1, -2, -3, -4, etc.).
   Los reales son aquellos que admiten un punto y dígitos decimales, o parte fraccionaria, a la derecha. Por ejemplo: 3.141592, 1.414213, 0.25, -7.777777, etc.
   Los complejos están compuestos por dos componentes, llamadas parte real y parte imaginaria. Ambas partes son números reales, e \( i \) representa la unidad imaginaria, es decir la raíz cuadrada algebraica de \( -1 \):

\( i=\sqrt{-1}. \)

   Recordemos siempre: Los números en una computadora no se comportan como los de la matemática.

   La razón principal es que el procesador de una computadora tiene espacio limitado para alojar números.
   Esto obliga a que todo tipo de datos numérico tenga un rango de valores restringido.
   Asimismo, los números reales no pueden tener, un número arbitrariamente grande de dígitos. Así, sólo pueden pertenecer a un conjunto reducido de la clase de los racionales.
   Las últimas versiones del lenguaje C permiten incorporar números complejos.
   Estos se implementan simplemente como un par de números reales, y tienen las mismas limitaciones que ellos.

   Aunque los tipos aritméticos básicos de C tienen las limitaciones mencionadas, pueden remediarse con ciertas técnicas de programación avanzadas.

   ¿Para qué distinguir tipos enteros y reales? Es que las operaciones con números fraccionarios son proclives a producir errores de redondeo, mientras que los enteros nunca pierden exactitud.

   El tipo de datos estándar que se usa en C para trabajar con números reales son los llamados números de punto flotante.
[cerrar]

1. Constantes numéricas de tipo entero:

   Las constantes numéricas enteras se escriben en C en formato decimal (o sea, en base diez), en octal (base ocho) o en hexadecimal (base dieciséis).  ichas constantes pueden ser positivas o cero, y sólo están limitadas por el tamaño máximo que el compilador o el sistema subyacente puede interpretar. El signo - delante de una constante positiva ya no se considera parte de una constante, sino que es un operador de negación.)
   Si un número es demasiado grande, requiere más bytes en memoria RAM para poder ser representado. En el siguiente Spoiler mostramos una tabla con el máximo entero positivo que puede representarse, según la cantidad de bytes disponibles para un tipo dado:

1.1. Tabla de enteros positivos máximos

Suponiendo bytes de 8 bits, tenemos la siguiente tabla:

Bytes    Entero positivo máximo que puede representare

 1        \( 2^{8} - 1   = 255 \)
 2        \( 2^{16} - 1  = 65535 \)
 3        \( 2^{24} - 1  = 16'777215 \)
 4        \( 2^{32} - 1  = 4294'967295 \)
 5        \( 2^{40} - 1  = 1'099511'627775 \)
 6        \( 2^{48} - 1  = 281'474976'710655 \)
 7        \( 2^{56} - 1  = 72057'594037'927935 \)
 8        \( 2^{64} - 1  = 18'446744'073709'551615 \)
 9        \( 2^{72} - 1  = 4722'366482'869645'213695 \)
10        \( 2^{80} - 1  = 1'208925'819614'629174'706175 \)
11        \( 2^{88} - 1  = 309'485009'821345'068724'781055 \)
12        \( 2^{96} - 1  = 79228'162514'264337'593543'950335 \)

[cerrar]

   ¿Qué ocurre si escribimos enteros demasiado grandes? ¿El compilador los entiende? En principio, eso depende del compilador. Pero aún si el compilador sabe que se trata de una constante entera, no necesariamente "sabe cuánto vale".
   El C99 asegura la disponibilidad de constantes enteras positivas tan grandes como \( 2^{64}-1=18'446744'073709'551615 \), es decir, del orden de los 18 trillones (en nomenclatura inglesa, se diría "18 quintillones", lo cual es confuso...). Así que no vamos a dar por sentado la existencia de constantes enteras mayores que esa en nuestro compilador. De hecho, el compilador (GCC) reconoce ese número.

   Los tipos de datos de enteros sin signo (o sea positivos) en C son los siguientes (según C99):

unsigned char
unsigned short int          (declaración abreviada: unsigned short)
unsigned int                (declaración abreviada: unsigned)
unsigned long int           (declaración abreviada: unsigned long)
unsigned long long int      (declaración abreviada: unsigned long long)

   En todos ellos el mínimo entero representable es 0, pero el máximo en cada uno no está claramente definido en el estándar, dejándolo a criterio de los compiladores.

Rango de valores admitidos para los tipos enteros en C.
   El estándar C99 reclama que:

\( \bullet \)   unsigned char:
       Representa valores al menos 8 bits, con lo cual se asegura al menos el rango de 0 a \( 2^8-1=255 \).
\( \bullet \)   unsigned short int         
       Representa valores al menos en el rango de 0 a \( 2^{16}-1=65535 \).
       Además se exige que, en cualquier circunstancia, contenga el rango de unsigned char.
\( \bullet \)   unsigned int
       Representa valores al menos en el rango de 0 a \( 2^{16}-1=65535 \).
       Además se exige que, en cualquier circunstancia, contenga el rango de unsigned short int.
\( \bullet \)   unsigned long int
       Representa valores al menos en el rango de 0 a \( 2^{16}-1=65535 \).
       Además se exige que, en cualquier circunstancia, contenga el rango de unsigned int.
\( \bullet \)   unsigned long long int
       Representa valores al menos en el rango de 0 a \( 2^{64}-1=18'446744'073709'551615 \).
       Además se exige que, en cualquier circunstancia, contenga el rango de unsigned long.

   Los tipos char se consideran tipos numéricos enteros. Detalles en el spoiler:

1.2. Relación entre el tipo char y los tipos enteros
   Es posible hacer cálculos con valores de tipo char, igual que con cualquier otro tipo entero. Además, las constantes de caracteres, tales como 'm', '@', '&', etc., representan números enteros en C, aunque nos resulte extraño concebir esto así. Tiene sentido una operación como ésta: 'm' + 5 - '@', cuyo resultado es 50.  ;)
   Para mostrar un número tipo char en pantalla, el programador elegirá el formato que prefiera, según el contexto: caracter o número.

   Digamos además que el tipo unsigned char en general siempre ocupa exactamente 8 bits en (casi) todas las implementaciones reales. Pero el estándar C99 no exige esto, y podría ocupar más bits, si hiciera falta. Sin embargo, su rango de valores no podrá ser mayor que el de un dato de tipo unsigned short int.
[cerrar]

   También debemos mencionar el tipo entero sin signo booleano (disponible desde el estándar C99):

_Bool   Ocupa (al menos) 1 byte, y es capaz de alojar los valores 0 y 1.

   El 0 se usa para indicar FALSO, y el 1 es VERDADERO.
   El tipo _Bool no ocupa más bits que el unsigned char.



   Ahora pasemos a estudiar los tipos de datos de enteros con signo. Para C99 son los siguientes:

signed char             
signed short int         (formas abreviadas: signed short, short int, short)
signed int                 (forma abreviada:   int)
signed long int          (formas abreviadas: signed long, long int, long)
signed long long int   (formas abreviadas: signed long long, long long int, long long)

   Las formas abreviadas son sinónimas.
   Lo típico en C es usar las formas abreviadas: short, int, long, long long.

Rangos de valores de los tipos enteros signados

   Los enteros con signo necesitan 1 bit para indicar el signo del número, influyendo en el rango de valores admisibles. C99 asegura los siguientes rangos de valores para los tipos signados:

\( \bullet \)  signed char:
       de \( -2^7=-127 \) a \( 2^7-1=127 \)
\( \bullet \)  signed short:
       al menos de \( -2^{15}=-32767 \) a \( 2^{15}-1=32767 \).
\( \bullet \)  signed int:
       al menos el mismo rango que signed short.
\( \bullet \)  signed long:
       al menos el mismo rango que signed int.
\( \bullet \)  signed long long:
       al menos de \( -2^{63}=-9223372036854775807 \) a \( 2^{63}-1=9223372036854775807 \).
       Además, el rango de valores debe ser al menos tan grande como el de signed long int.

2. Compatibilidad del tipo char:

   La forma aparentemente breve char no significa signed char.  >:(
   El tipo char siempre se considera distinto de unsigned char y signed char, aunque su rango de valores coincide siempre con alguno de los dos.
   Desde el estándar no se asegura si char tiene signo o no, pero claramente contiene siempre los valores del rango de 0 a 127.
   Además, los tres tipos de datos char, unsigned char y signed char, ocupan todos la misma cantidad de bytes.

   Nota técnica: Los tipos con igual denominación, en general ocupan la misma cantidad de bytes en sus versiones unsigned y signed. (Hay excepciones, pero es un tema complicado de abordar aquí).



3. Enteros de tamaño fijo.

   Según vimos, no es posible predecir la longitud exacta en bits que tiene cada tipo entero en C. La librería <stdint.h> del estándar C99 define nuevos tipos de enteros, que tienen una longitud fija medida en bits.
   Se definen los siguientes tipos enteros:

Sin signo con longitud fija en bits:
    uint8_t, uint16_t, uint32_t, uint64_t.
Con signo con longitud fija en bits:
    int8_t, int16_t, int32_t, int64_t.
Sin signo con longitud en bits mínima asegurada:
    uint_least8_t, uint_least16_t, uint_least32_t, uint_least64_t.
Con signo con longitud en bits mínima asegurada:
    int_least8_t, int_least16_t, int_least32_t, int_least64_t.
Sin signo, rápidos, con longitud en bits mínima asegurada:
    uint_fast8_t, uint_fast16_t, uint_fast32_t, uint_fast64_t.
Con signo, rápidos, con longitud en bits mínima asegurada:
    int_fast8_t, int_fast16_t, int_fast32_t, int_fast64_t.

   Los tipos con longitud fija tienen la cantidad de bits que su nombre indica. Ejemplo: uint8_t es un entero de 8 bits.
   La longitud mínima asegurada fija un mínimo de bits, pero la longitd real puede ser aún mayor.  Ejemplo: uint_least8_t es un tipo entero con al menos 8 bits, pero podría ser mayor en una implementación dada.
   Los tipos rápidos con longitud mínima asegurada también fijan un mínimo en bits para tipos enteros, y que además son rápidos. ¿Qué significa rápidos? El estándar no lo define ni lo sugiere. Pero se supone que un sistema dado puede aprovechar alguna característica interna que implemente versiones de enteros que permiten operar más rápido.

   También se definen los tipos de longitud máxima en bits que la implementación local detecta:

uintmax_t: Tipo entero sin signo de longitud máxima.
intmax_t:  Tipo entero con signo de longitud máxima.

   Dado que C99 asegura la existencia del tipo de 64 bits long long int, podemos asegurar que uintmax_t y intmax_t tienen al menos 64 bits.
   El estándar estipula además que es posible, aunque no obligatorio, definir los tipos intptr_t, uintptr_t. Son las versiones sin signo y con signo de un tipo de enteros portable a utilizar en operaciones con punteros (que involucran cálculos con posiciones en la memoria RAM).



4. Especificación de constantes enteras y establecimiento de tipos enteros en forma implícita.

   Si escribimos un número entero en nuestro programa, ¿de qué tipo de todos los anteriores es? Según las reglas de C99:

\( \bullet \)   Dada una constante numérica entera, escrita con números decimales, su tipo es el más pequeño en el que "aún" cabe el número que pretendemos representar, de entre:

int
long
long long

Un ejemplo en el Spoiler:

4.1. Ejemplo de asignación de tipos a constantes
   Supongamos que nuestro sistema admite int de 16 bits, long de 32 bits y long long de 64 bits.
   Números como -35, 0, 19, 126, 1848, 31009, serían considerados de tipo int.
   En cambio 32768 ya no cabe en nuestro int, y pasa a ser long. (El -32768 puede o no estar en el rango de int.)
   Números como 267543, -999999, 1012345678, serían aún de nuestro tipo long.
   En cambio 2147483648 ya no cabe, y es de tipo long long.
   En nuestro long long ya no cabe 9223372036854775808.
   ¿Qué ocurre con ese número tan grande, que no cabe ni siquiera en un long long?
   El estándar ya no especifica nada al respecto, y así la constante entera más grande que podemos asegurar que funciona  ;) en C es:

9223372036854775807  :aplauso: :aplauso: :aplauso:

   Las constantes escritas en base decimal nunca pueden adjudicarse a un tipo entero sin signo. Ver: Sección 7.1 del libro de King.

[cerrar]



5. Constantes enteras octales y hexadecimales:

   Si delante de un número anteponemos un 0, el compilador de C interpreta que el número está en base 8 (octal). Estos números sólo admiten los dígitos 0, 1, 2, 3, 4, 5, 6, 7, así que si aparecen los caracteres 8 ó 9, dará un error, por no ser un número octal válido.
   Ejemplos: 017, 033, 041234, -0167216.

   Atención:  :o :o No hay que confundirse con estos números. Si en C escribimos un 0 a la izquierda de un número, se lo considera octal, y así no es lo mismo 17 (en decimal) que 017, que está en octal, y que como número decimal equivale a 15.   :-* Así que hay que extremar las precauciones y, en general:

   No anteponer un 0 a la izquierda de una constante de número entero en un programa en C, a menos que a propósito queramos escribir un número en base octal.

   Es posible escribir números en base 16 (hexadecimal), anteponiendo el prefijo 0x delante del número. Por ejemplo, los siguientes son números hexadecimales:

0x34 (hexadecimal 34 = decimal 52), 0x7FC (hexadecimal 7FC = decimal 2044), etc.

   Los dígitos hexadecimales A, B, C, D, E, F, pueden escribirse en mayúsculas y/o en minúsculas, e incluso se pueden mezclar ambos estilos. Ejemplo: 0x1Aa (hexadecimal 1AA = decimal 426).
   En vez de 0x, se puede usar también mayúsculas poniendo 0X, por ejemplo: 0X4D (hexadecimal 4D = decimal 77).
   Observación: No hay que confundirse con el uso de la x al definir caracteres con código hexadecimal, ya que allí no está permitida la X mayúscula: '\x3F' está bien, pero '\X3F' está mal, mientras que para constantes numéricas, tanto 0x3F como 0X3F son ambas correctas.



   Para las constantes en octal y en hexadecimal existe una regla distinta de asignación de tipo entero:

\( \bullet \)   Dada una constante de número entero escrito en octal o hexadecimal, el tipo de datos entero que se le asigna es el primero de los siguientes, tal que el número puede representarse (o cabe) en dicho tipo:

int
unsigned int
long int
unsigned long int
long long int
unsigned long long int

   La máxima constante entera positiva que el estándar C99 nos asegura en un programa en C es \( 2^{63}-1 \), como ya vimos. En octal es: 0777777777777777777777. Y en hexadecimal es: 7FFFFFFFFFFFFFFF.



6. Forzando el tipo de una constante numérica.

   Es posible forzar el tipo de una constante numérica entera, mediante el uso de sufijos.

   Para forzar al compilador a interpretar una constante de número entero como unsigned (del tamaño que sea), se agrega como sufijo al número una U ó una u (es indistinto el uso de mayúsculas o minúsculas).
   Así, la constante 317, que normalmente sería un int, al escribir 317u pasa a ser un unsigned int.
   El compilador, al tomar un número entero con sufijo u, le asignará el primero de los siguientes tipos en que el valor del número "cabe":
   
unsigned int
unsigned long
unsigned long long

(Esto vale para números decimales, octales y hexadecimales).

   Otro sufijo es L ó l (de nuevo es indistinto el uso de mayúsculas o minúsculas), para indicar long.
   El efecto de agregar este sufijo es que el compilador trata de encajar la constante decimal en la más pequeña de las opciones: long ó long long.
   En cambio, para el sufijo L o l de una constante octal o hexadecimal, el compilador tratará de asignar el primero que sea posible de los siguientes tipos:

long int
unsigned long int
long long int
unsigned long long int

   También existe el sufijo LL ó ll (no se pueden mezclar mayúsculas y minúsculas como: Ll ó lL). Con este sufijo, una constante entera escrita en base decimal pasa a considerarse enseguida de tipo long long int.
   Mientras que si la constante es octal o hexadecimal, se intentará asignarle el primerlo de los tipos siguientes, que sea posible:

long long int
unsigned long long int


   Se pueden combinar los sufijos U con los L ó LL, para obtener UL (asegura unsigned long o mayor) y ULL (asegura tipo unsigned long long).
   Se pueden cambiar de orden o escribir en indistintamente en mayúsculas y minúsculas, y así todas estas opciones son válidas:

ul  uL  Ul  UL  ull  uLL  Ull  ULL  lu  Lu  lU  LU  llu  LLu  llU  LLU

   Todos los sufijos U, L, LL, UL, ULL, pueden colocarse detrás de constantes octales y hexadecimales: 0331413L, 0x35ULL.
   Al colocar el sufijo UL a una constante entera, el compilador intentará asignarle el primero que sea posible de la lista de tipos siguientes:

unsigned long int
unsigned long long int

(Esto vale para bases decimal, octal, hexadecimal).

   Si anteponemos el sufijo U a una constante más grande que el máximo valor aceptado en un signed long long int, pero que todavía está en el rango del tipo unsigned long long int. ¿Es una constante que el compilador debe aceptar?
   Debo revisar el estándar al respecto. (Revisar) En mi sistema, cuyo long long es de 64 bits, admite sin problemas la constante \( 2^{64}-1 \) seguida de sufijo U, como un unsigned long long int, escribiéndola así:
18446744073709551615U  ::)

(El estándar discute la posibilidad de tipos de datos extendidos, pero yo me restrinjo siempre a lo estrictamente asegurado por el estándar, y estos tipos extendidos no están especificados en el C99 estricto).



7. Constantes numéricas: tres caras de una misma moneda

   Podemos preguntar por las constantes numéricas desde 3 puntos de vista distintos. Ver spoiler:

(Discusión sobre las constantes enteras)
(i)  Constantes numéricas que hemos escrito, y que el compilador es capaz de reconocer como tales.
(ii) Constantes numéricas que el compilador reconoce como tales, y que a su vez fue capaz de asociarles correctamente un tipo numérico en C.
(iii) Valores en el rango admitido para un tipo de datos numérico dado.

   Esos 3 tipos de constantes son esencialmente diferentes.

   En general siempre se cumple (i) pues es fácil reconocer si algo es sintácticamente una constante entera.
   Si una constante cumple (ii), entonces también, siempre, cumple (i) y (iii).
   Si cumple (iii), ¿cumple (i)? En general sí. Si no, sería indicio de un mal compilador.

   Supongamos que nuestro compilador tiene tipos int de 16 bits, long de 32 bits, long long de 64 bits, y además no tiene tipos mayores que estos.
   Consideremos las constantes enteras 10 trillones y 100 trillones, escritas en decimal:

10000000000000000000 (\( 10^{19} \)), 100000000000000000000 (\( 10^{20} \)).

   El número 10 trillones no está en el rango de valores de signed long long int, pero sí de unsigned long long int.
   Mi compilador GCC actual avisa que sólo el estándar C90 acepta ese número como de tipo unsigned long long int (se siguen otras reglas). Pero con las reglas de C99 no se puede asignar un tipo entero.
   Sin embargo, 10 trillones es un valor correcto en el rango de unsigned long long int.
   Hemos encontrado un caso donde vale (iii), pero no (ii).

   El número 100 trillones no cabe en el rango de valores de unsigned long long int.
   En este ejemplo no valen ni (ii) ni (iii).
[cerrar]



Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 9. Enteros en C (parte II)
Publicado por: argentinator en 05 Enero, 2013, 04:35 pm
9. Enteros en C (parte II)

   El estándar C99 nos brinda las librerías <limits.h> y <stdint.h>, que ofrecen información útil de los tipos enteros.

¿Cómo especifica C el dominio de sus tipos enteros?

   Leyendo cuidadosamente el documento del estándar C99 así como el libro de comentarios The New C Standard, de nuestra lista bibliográfica, podemos concluir cosas bastante curiosas. Por ejemplo:

(i) El estándar no especifica el tamaño de los tipos enteros en bytes, ni siquiera en bits. Solamente indica rangos de valores numéricos que cada tipo entero está obligado a cubrir.
(ii) Las implementaciones particulares tienen el derecho de abarcar rangos mayores.
(iii) Hay sistemas, arquitecturas, y demás contextos informáticos, en los cuales se pueden ver ejemplos reales de tipos char de 9 bits, enteros de 36 bits, otro sistema con todos los tipos enteros de 6 bytes, etc., etc.

   El estándar es compatible con todas esas situaciones de apariencia exótica.

   En la práctica corriente, el bit de signo de un tipo signed int, por ejemplo, se aprovecha en un unsigned int para representar valores del doble de tamaño en el rango positivo respecto signed int.
   Sin embargo, esto no es obligatorio, y existen implementaciones en las que el máximo valor positivo en ambos casos se ha tomado como el mismo, y es un comportamiento admitido en el estándar.
   Otra observación:

(iv) El estándar indica valores mínimos y máximos numéricos esperados en cada tipo, pero esto no significa que esos valores puedan o deban expresarse directamente en forma literal en <limits.h>.

   A veces puede resultar enrevesado o incluso imposible escribir en forma directa los valores límites tolerados por ciertos tipos.
   Puede que a veces haga falta dar dichos valores a través de expresiones aritméticas quizá complejas, que involucran constantes.
   Se exige además (a la implementación) que dichas expresiones sean compatibles con el uso de la directiva #if.

[cerrar]

   En el siguiente spoiler hay una tabla con los valores obligatorios para cada constante definida en el archivo limits.h. También explicamos el significado de dichas constantes.

Constantes de limits.h y definición de los rangos de valores de los tipos enteros

CHAR_BIT        \( \geq 8 \)
MB_LEN_MAX      \( \geq 1 \)

SCHAR_MIN       \( \leq -127 \)
SCHAR_MAX       \( \geq 127 \)
UCHAR_MAX       \( \geq 255 \)
CHAR_MIN        0 si char se considera un tipo sin signo,
                ó: el mismo valor de SCHAR_MIN si char se considera con signo.
CHAR_MAX        UCHAR_MAX si char se considera un tipo sin signo,
                el mismo valor de SCHAR_MAX si char se considera con signo.

SHRT_MIN        \( \leq -32767 \)
SHRT_MAX        \( \geq 32767 \)
USHRT_MAX       \( \geq 65535 \)
INT_MIN         \( \leq -32767 \)
INT_MAX         \( \geq 32767 \)
UINT_MAX        \( \geq 65535 \)
LONG_MIN        \( \leq -2147483647 \)
LONG_MAX        \( \geq 2147483647 \)
ULONG_MAX       \( \geq 4294967295 \)
LLONG_MIN       \( \leq -9223372036854775807 \)
LLONG_MAX       \( \geq 9223372036854775807 \)
ULLONG_MAX      \( \geq 18446744073709551615 \)


   Expliquemos ahora como se usan las constates definidas en <limits.h>.

   La macro CHAR_BIT es una constante entera \( \geq 8 \) que indica la longitud en bits que van a tener los 3 tipos char: unsigned char, signed char y char.
   Esto es lo formalmente se llamará 1 byte.
   Como puede apreciarse, la cantidad de bits de un byte puede variar de un sistema a otro, y de hecho lo hace, admitiendo valores de 9, 16, etc. No entraremos en detalles.

   No hemos discutido aún los caracteres multibyte.
   Por ahora sólo diremos que la macro MB_LEN_MAX es una constante entera que indica el número de bytes (en el sentido dado en el párrafo previo) máximo que puede tener un caracter multibyte.

   El rango de valores que pueden tomar los distintos tipos enteros, quedan especificados así:


Tipo de entero ---------- Rango de valores válidos que puede albergar

signed char:              entre SCHAR_MIN y SCHAR_MAX
unsigned char:            entre 0 y UCHAR_MAX
char:                     entre SCHAR_MIN y SCHAR_MAX   (si char se considera "sin" signo)
                          ó: entre 0 y UCHAR_MAX        (si char se considera "con" signo)
signed short int:         entre SHRT_MIN y SHRT_MAX
unsigned short int:       entre USHRT_MIN y USHRT_MAX
signed int:               entre INT_MIN y INT_MAX
unsigned int:             entre 0 y UINT_MAX
signed long int:          entre LONG_MIN y LONG_MAX
unsigned long int:        entre 0 y ULONG_MAX
signed long long int:     entre LLONG_MIN y LONG_MAX
unsigned long long int:   entre 0 y ULLONG_MAX


   Supongamos como ejemplo que tenemos un sistema donde SHRT_MAX se ha elegido como 242143. Esto está permitido, porque SHRT_MAX ha de ser, según la primer tabla, un número \( \geq{}32768 \).
   Pero ahora, el rango de valores de un dato signed short está acotado por arriba, no pudiendo sobrepasar este valor estipulado de 242143.
   Análogas consideraciones valen para los otros tipos y sus rangos respectivos.
[cerrar]

   El estándar C99 obliga además a que se cumplan ciertas condiciones.

En particular, se entromete a fondo con la representación de los enteros a nivel de bits, y da indicaciones sobre el significado de esos bits, etc., etc. Esto no era así en los estándares previos a 1999 del lenguaje C.

   El análisis de las reglas en este Spoiler:

Representación estándar interna de los bits de datos de tipos enteros

   En general, un lenguaje de alto nivel no especifica ni se ocupa de cómo se han de representar sus tipos de datos a nivel de bits, ni dar indicaciones de cuántos bits debe ocupar un cierto tipo, etc.
   Sin embargo en C no es mala idea hacerlo, ya que existen operadores que trabajan bit a bit, y siendo así es mejor que haya reglas claras para mayor portabilidad.
   Desde 1999, muchos detalles a nivel de bits en C tienen un significado uniforme en todos los sistemas informáticos y todos los compiladores.

  • Se acostumbra  ;) ;) estipular que las versiones "con" signo y "sin" signo de un cierto tipo entero general tienen que ocupar la misma cantidad de bits.  Así, si tenemos un unsigned long int de 32 bits, también signed long int ocuparía 32 bits.

       Pero: el estándar no obliga a que esto sea así. Abajo iremos precisando un poco más.

  • Si la versión unsigned de un tipo entero ocupa \( N \) bits, entonces el rango de valores que tiene que representar va de 0 a \( 2^{N-1} \).
       Y más aún, debe representarse internamente en la máquina siguiendo una estricta escritura en base 2, con los bits contiguos respetando las potencias de 2.
       Este nivel tan detallado de especificación se ha establecido en el estándar C99, o sea que este tipo de cosas no eran obligatorias en el estándar anterior a 1999.

  • Para la versión signed de un tipo entero, tiene que haber exactamente 1 y sólo un bit de signo.

  • Para la versión signed de un tipo entero, en el caso de un bit usado para representar un valor (o sea, otro que el bit de signo), representará la misma potencia de 2 que el correspondiente bit en la versión unsigned.
       Esto implica que los mismos bits se usan en ambos tipos para las mismas potencias de 2, pero no quiere decir que se use obligatoriamente 0 ó 1 para indicar presencia o ausencia de un valor específico. Esto tiene que ver conque se permite utilizar distintas representaciones de los números negativos (complemento a 1, complemente a 2, ¿etc.?).
       Más aún, si la versión signed usa \( M \) bits para expresar valores, y la versión unsigned usa \( N \) bits, entonces \( M\leq N \).

  • Si el bit de signo es 0, entonces no afecta al número representado por los bits de valor.
       En consecuencia, en aquellos números no negativos que pertenecen al rango de valores de un tipo, en sus dos versiones signed y unsigned, el valor numérico representado coresponde al mismo valor matemático en ambos casos.

  • Si en un entero signed de \( N \) bits el bit de signo es 1 entonces el valor que representa es exactamente \( -2^N \) si para enteros con signo se usa formato complemento a 2, y \( -(2^N-1) \) si se usa complemento a 1.
       Esta última representación se entiende como una segunda versión del valor 0, cuando se usa complemento a 1.  Es el llamado 0 negativo. Se sigue considerando aritméticamente como el valor 0, pero es otra representación interna del mismo número.
       Además el estándar exige que sólo se produzca un 0 negativo bajo ciertas circunstancias, que aquí no voy a especificar.

    Nota: Desde hace varias décadas todos los sistemas informáticos usan complemento a 2.
    Comentario: Las representaciones complemento a 2 y complemento a 1 serán explicadas en otro momento.

  • No queda especificado el comportamiento de los operadores de bits en presencia de tipos signed con un valor de 0 negativo, aún en el caso que no se use complemento a 1.

       Los resultados de operaciones de bits con datos signed no están definidas por el estándar. Así, para mayor certeza, conviene trabajar con datos unsigned al hacer operaciones de bits.

  • Se denomina precisión de un tipo entero al número de bits usados para representar un valor (sin contar el bit de signo).
       Se denomina longitud (o ancho, en inglés: width) al número de bits usados para representar tanto el valor como el bit de signo.
       La longitud de un signed es siempre igual a su precisión más 1.

  • Cualesquiera sean los métodos empleados para representar valores enteros, las versiones signed y unsigned de un tipo dado tienen que representar el 0 de la misma forma, a saber, con todos los bits iguales a 0, incluido el bit de signo.

  • El estándar admite la posible presencia de unos bits extra, llamados padding bits.

    Nosotros asumiremos que en nuestro sistema no hay padding bits. (Muchos detalles y discusiones se esconden tras esto, y también las evitaremos...)

  • El valor de UCHAR_MAX tiene que ser exactamente igual a \( 2^{CHAR\underline{\ \  }BIT}-1 \).

  • En las reglas del estándar no queda demasiado claro cuánto debiera ser el mínimo valor entero de un tipo dado.
       Por ejemplo, SCHAR_MIN podría ser -128 o -127.
       Sin embargo, esto depende de si los números negativos se implementan como complemento a 2 o como complemento a 1.
       Podemos asegurar entonces, lo siguiente:
       \( \bullet \) Si se usa representación de complemento a 2, entonces: si el máximo valor que toma un tipo signed es \( M \), entonces el mínimo valor que admite es \( -(M+1) \).
       \( \bullet \) Si se usa representación de complemento a 1, entonces: si el máximo valor que toma un tipo signed es \( M \), entonces el mínimo valor que admite es \( -M \).

  • ¿El estándar asegura que el rango de valores admitido por un tipo signed está necesariamente centrado en 0?
       No he sido capaz de hallar esto en las reglas del estándar C99, así que no estoy seguro.
       Sin embargo, en la práctica corriente siempre ocurre que los valores de un signed están centrados en torno al 0.

  • Debido a que el tipo _Bool debe tener una longitud en bits menor o igual que el tipo unsigned char, podemos asegurar que a lo sumo ocupa un byte, sin importar cuántos bits tenga un byte.
       El número de bits de un _Bool es menor o igual que CHAR_BIT.

       A su vez, todos los datos en C tienen que ocupar la unidad mínima de almacenamiento: 1 byte.
       Como CHAR_BIT define el tamaño de 1 byte, resulta que _Bool ha de ocupar exactamente 1 byte.




   ¿Son iguales el número de bits de las versiones signed y unsigned de un tipo entero dado?

   En general, se acostumbra   ;) ;) que la versión signed de un tipo entero tenga la misma longitud que su compañera unsigned, y así la precisión será exactamente 1 menos que de la compañera unsigned.
   Pero el estándar no obliga a esto, y puede ocurrir que los tipos signed sean más pequeños.
   Lo que el estándar sí obliga es a que el rango de valores positivos y los bits que representan valores de la versión signed encajen a nivel de bits con la versión unsigned, de manera que representen el mismo valor matemático.

   Así, podría ocurrir que un signed int tenga 1 bit más que su compañero unsigned int, si ambos comparten los mismos valores positivos, ya que el signed int además debe contener el bit de signo. (Incluso hay ejemplo de un sistema de la vida real que trabaja así).
   Pero en todo otro caso, el número de bits de un signed será menor o igual :) que la de su compañero unsigned. Inclusive podría ocurrir que la versión unsigned tenga varios bits más.
 
Precaución: A nivel de bits, se puede dar el caso que los bits de valor sean de cierta forma, pero que su representación como objeto en memoria sea algo diferente, por más que en ambos casos se hable de bits bien ordenaditos y todo.
   Nosotros estamos suponiendo que ambas cosas son lo mismo, para no marearnos. Pero en algún momento tendríamos que estudiar la diferencia entre estos dos aspectos.
[cerrar]

   En el spoiler anterior no hemos indicado una sutileza importante: Cuando se definen las macros CHAR_BIT, SHRT_MIN, etc., del archivo limits.h, se debe hacer con expresiones numéricas constantes. Esto acarrea consecuencias internas que es bueno mencionar en este lugar. Como es un tema muy técnico, va escondido en un nuevo Spoiler:

Tipos de datos de las constantes de limits.h
   Estas expresiones no están obligadas a ser números escritos en decimal o hexadecimal, ni tener uno u otro formato.
   A su vez, el estándar exige que esas expresiones tengan como tipo resultante el mismo tipo de datos del cual habrán de definir el rango de valores.
   Por ejemplo, SHRT_MIN y SHRT_MAX tienen que ser constantes de tipo signed short int.
   Esta exigencia puede cumplirse en algunos casos, pero en otros es imposible, como podría ocurrir con un tipo short int, debido a que no hay sufijos adecuados que fuercen a que una constante decimal sea short, y además las reglas de conversión de tipos promueven directamente a int u otros.
   En este caso, el estándar permitirá que los valores SHRT_MIN, SHRT_MAX promuevan automáticamente a int, y que USHRT_MAX promueva a unsigned int.
   En principio no habría peligro de errores con esto, debido a que el rango de valores de un short int está contenido en el de un int, y análogamente ocurre con unsigned short int respecto unsigned int.
   Esta promoción automática a tipos int podría suponer un problema en contextos donde se espera que aparezca un tipo short. Sin embargo, si el valor es int pero está dentro del rango de valores de un short, y el contexto esperado es short, la conversión a short procedería automáticamente, y seguramente sin errores.

   ¿Qué ocurre con las constantes CHAR_BIT y MB_LEN_MAX?
   Son demasiado pequeñas como para traer inconvenientes. No analizaremos esto aquí.
[cerrar]

   Si ahora miramos el contenido del archivo limits.h seguro se entenderá.



Especificaciones para los tipos de datos enteros adicionales: librería <stdint.h>

   En el post anterior ya hemos estado estudiando los nuevos tipos enteros definidos en stdint.h. Detalles en el Spoiler.

Tipos de enteros definidos en stdint.h
   Ya hemos visto la mayoría de los tipos enteros definidos en stdint.h. Tenemos:
   
  • Tipos enteros de longitud prefijada (ya los hemos estudiamos):

      int8_t   int16_t   int32_t   int64_t 
      uint8_t  uint16_t  uint32_t  uint64_t
     

  • Tipos enteros de longitud mínima prefijada:

      int_least8_t   int_least16_t   int_least32_t   int_least64_t
      uint_least8_t  uint_least16_t  uint_least32_t  uint_least64_t.

  • Tipos enteros "rápidos" de longitud mínima prefijada:

      int_fast8_t    int_fast16_t    int_fast32_t    int_fast64_t
      uint_fast8_t   uint_fast16_t   uint_fast32_t   uint_fast64_t


   Tenemos también los tipos:

   intmax_t y uintmax_t: representan los tipos enteros de longitud máxima (en bits) que el compilador admite, y tienen que abarcar a todos los tipos enteros admitidos o definidos en el sistema. Por ejemplo, deben ser tan grandes como un long long.
   intptr_t y uintptr_t: se refieren a tipos de enteros compatibles con la operación de conversión de punteros a enteros.

   El estándar no obliga a una implementación a definir los tipos intptr_t y uintptr_t. Esos nombres existen para facilitar la portabilidad de los programas. (C99 exige que estos tipos se definan mediante una declaración que use typedef. No explicaremos ahora qué es esto.)  :'(
   Se puede inferir que los tipos intmax_t y uintmax_t, por ser los máximos tipos enteros, tienen que poder abarcar o contener a los tipos intptr_t y uintptr_t.
[cerrar]

   Estudiemos ahora los rangos de valores para los tipos enteros declarados en <stdint.h>. Allí se especifican macros con valores constantes indicando esos rangos. Detalles en el Spoiler:

Macros y rangos para los tipos enteros declarados en stdint.h

   En la siguiente tabla vemos que los valores mínimo y máximo de cada tipo se especifican con macros, las que están definidas en <stdint.h>, y deben cumplir unas condiciones que veremos después.


Tipo de entero --- Mínimo ---------- Máximo

int8_t             INT8_MIN          INT8_MAX
int16_t            INT16_MIN         INT16_MAX
int32_t            INT32_MIN         INT32_MAX
int64_t            INT64_MIN         INT64_MAX
uint8_t            0                 UINT8_MAX
uint16_t           0                 UINT16_MAX
uint32_t           0                 UINT32_MAX
uint64_t           0                 UINT64_MAX

int_least8_t       INT_LEAST8_MIN    INT_LEAST8_MAX
int_least16_t      INT_LEAST16_MIN   INT_LEAST16_MAX
int_least32_t      INT_LEAST32_MIN   INT_LEAST32_MAX
int_least64_t      INT_LEAST64_MIN   INT_LEAST64_MAX
uint_least8_t      0                 UINT_LEAST8_MAX
uint_least16_t     0                 UINT_LEAST16_MAX
uint_least32_t     0                 UINT_LEAST32_MAX
uint_least64_t     0                 UINT_LEAST64_MAX

int_fast8_t        INT_FAST8_MIN     INT_FAST8_MAX
int_fast16_t       INT_FAST16_MIN    INT_FAST16_MAX
int_fast32_t       INT_FAST32_MIN    INT_FAST32_MAX
int_fast64_t       INT_FAST64_MIN    INT_FAST64_MAX
uint_fast8_t       0                 UINT_FAST8_MAX
uint_fast16_t      0                 UINT_FAST16_MAX
uint_fast32_t      0                 UINT_FAST32_MAX
uint_fast64_t      0                 UINT_FAST64_MAX

intmax_t           INTMAX_MIN        INTMAX_MAX
uintmax_t          0                 UINTMAX_MAX

intptr_t           INTPTR_MIN        INTPTR_MAX
uintptr_t          0                 UINTPTR_MAX


Ahora especificamos las condiciones que deben cumplir las macros de la tabla anterior.

INT8_MIN          \( =-2^7 \)
INT8_MAX          \( =2^{7}-1 \)
UINT8_MAX         \( =2^{8}-1 \)
INT16_MIN         \( =-2^{15} \)
INT16_MAX         \( =2^{15}-1 \)
UINT16_MAX        \( =2^{16}-1 \)
INT32_MIN         \( =-2^{31} \)
INT32_MAX         \( =2^{31}-1 \)
UINT32_MAX        \( =-2^{32}-1 \)
INT64_MIN         \( =-2^{63} \)
INT64_MAX         \( =2^{63}-1 \)
UINT64_MAX        \( =2^{63}-1 \)

INT_LEAST8_MIN    \( \leq-2^7 \)
INT_LEAST8_MAX    \( \geq2^{7}-1 \)
UINT_LEAST8_MAX   \( \geq2^{8}-1 \)
INT_LEAST16_MIN   \( \leq-2^{15} \)
INT_LEAST16_MAX   \( \geq2^{15}-1 \)
UINT_LEAST16_MAX  \( \geq2^{16}-1 \)
INT_LEAST32_MIN   \( \leq-2^{31} \)
INT_LEAST32_MAX   \( \geq2^{31}-1 \)
UINT_LEAST32_MAX  \( \geq-2^{32}-1 \)
INT_LEAST64_MIN   \( \leq-2^{63} \)
INT_LEAST64_MAX   \( \geq2^{63}-1 \)
UINT_LEAST64_MAX  \( \geq2^{63}-1 \)

INT_FAST8_MIN     \( \leq-2^7 \)
INT_FAST8_MAX     \( \geq2^{7}-1 \)
UINT_FAST8_MAX    \( \geq2^{8}-1 \)
INT_FAST16_MIN    \( \leq-2^{15} \)
INT_FAST16_MAX    \( \geq2^{15}-1 \)
UINT_FAST16_MAX   \( \geq2^{16}-1 \)
INT_FAST32_MIN    \( \leq-2^{31} \)
INT_FAST32_MAX    \( \geq2^{31}-1 \)
UINT_FAST32_MAX   \( \geq-2^{32}-1 \)
INT_FAST64_MIN    \( \leq-2^{63} \)
INT_FAST64_MAX    \( \geq2^{63}-1 \)
UINT_FAST64_MAX   \( \geq2^{63}-1 \)

INTMAX_MIN        \( \leq-2^{63} \)
INTMAX_MAX        \( \geq2^{63}-1 \)
UINTMAX_MAX       \( \geq2^{63}-1 \)

INTPTR_MIN        \( \leq-2^{15} \)
INTPTR_MAX        \( \geq2^{15}-1 \)
UINTPTR_MAX       \( \geq2^{16}-1 \)

[/tt][/size]
[cerrar]

   Además, en <stdint.h> se definen rangos de valores para tipos enteros definidos en otras librerías.
   Vemos cuáles son en el spoiler:

(Otros tipos enteros)

   En <stddef.h> se declaran los siguientes tipos (también se declaran en otras librerías):

   size_t: Es un entero sin signo. Se utiliza para representar el tamaño en bits que ocupa un tipo de datos determinado. Esto se hace con el operador sizeof(), que explicaremos en otro momento.
   ptrdiff_t: Es un entero con signo. Se usa para representar la diferencia entre las posiciones de memoria de dos punteros de un mismo tipo.
   wchar_t: Se usa para albergar caracteres multibyte.

   En la librería <wchar.h> está definido el siguiente tipo entero:

   wint_t: Se usa para representar enteros que abarcan los caracteres multibyte representables en wchar_t, y al menos un valor adicional fuera de ese conjunto de valores.

   En la librería <signal.h> está definido el siguiente tipo entero:

sig_atomic_t: Se utiliza en el manejo de señales.  ??? ??? ??? Algún día les explicaré esto...

   Un detalle interesente es que un dato de tipo wint_t no se convierte automáticamente a otro tipo entero.
   El tipo wchar_t puede convertirse en wint_t si el contexto requiere un valor más grande... Pero estos detalles serán explicados en otro momento.

   Nota técnica: El estándar C99 no especifica si los tipos wchar_t, wint_t, sig_atomic_t, deben considerarse enteros con o sin signo. Cada compilador, pues, puede tener una representación distinta (es una situación análoga a char).
   En cualquier caso, en <stdint.h> deben figurar los límites para los rangos de valores de estos tipos de enteros. Los detalles en el Spoiler:

Límites para size_t, ptrdiff_t, wchar_t, wint_t, sig_atomic_t


Tipo de entero  ---------  Mínimo  ---------  Máximo

size_t              0                  SIZE_MAX
ptrdiff_t           PTRDIFF_MIN        PTRDIFF_MAX
wchar_t             WCHAR_MIN          WCHAR_MAX
wint_t              WINT_MIN           WINT_MAX
sig_atomic_t        SIG_ATOMIC_MIN     SIG_ATOMIC_MAX


Ahora estudiamos las condiciones que deben cumplir las macros correspondientes.

SIZE_MAX        debe ser \( \geq 2^{16}-1=65535 \).

PTRDIFF_MIN     debe ser \( \leq -(2^{16}-1)=-65535 \).
PTRDIFF_MAX     debe ser \( \geq 2^{16}-1=65535 \).

Si wchar_t se define como un entero con signo, entonces:
WCHAR_MIN       debe ser \( \leq -(2^{7}-1)=-127 \).
WCHAR_MAX       debe ser \( \geq  2^{7}-1 =127 \).

Si wchar_t se define como un entero sin signo, entonces:
WCHAR_MIN       debe ser \( =0 \).
WCHAR_MAX       debe ser \( \geq  2^{8}-1 =255 \).

Si wint_t se define como un entero con signo, entonces:
WINT_MIN        debe ser \( \leq -(2^{15}-1) \).
WINT_MAX        debe ser \( \geq  2^{15}-1  \).

Si wint_t se define como un entero sin signo, entonces:
WINT_MIN        debe ser \( =0 \).
WINT_MAX        debe ser \( \geq  2^{16}-1  \).

Si sig_atomic_t se define como un entero con signo, entonces:
SIG_ATOMIC_MIN  debe ser \( \leq -(2^{7}-1)=-127 \).
SIG_ATOMIC_MAX  debe ser \( \geq  2^{7}-1 =127 \).

Si sig_atomic_t se define como un entero sin signo, entonces:
SIG_ATOMIC_MIN  debe ser \( =0 \).
SIG_ATOMIC_MAX  debe ser \( \geq  2^{8}-1=255 \).


[cerrar]



Cómo forzar hacia los tipos de <stdint.h>

   Si queremos una constante entera de alguno de los tipos enteros definidos en <stdint.h>, no podemos forzar a mano mediante sufijos, como hacíamos con los tipos básicos.
   El C99 especifica que en <stdint.h> hay macros que permiten definir constantes adecuadas para estos tipos de enteros. Sin embargo estas macros no hacen magia, sino que "retocan" un poco las constantes numéricas para que encajen lo mejor posible en los tipos de <stdint.h>.

   Por ejemplo, la macro INT8_C(X) permitiría definir una constante numérica X como de tipo int8_t. Por ahora nos conformarmos con listarlas:

Macros para definir constantes de los tipos declarados en stdint.h

INT8_C(X)
INT16_C(X)
INT32_C(X)
INT64_C(X)
UINT8_C(X)
UINT16_C(X)
UINT32_C(X)
UINT64_C(X)

INT_LEAST8_C(X)
INT_LEAST16_C(X)
INT_LEAST32_C(X)
INT_LEAST64_C(X)
UINT_LEAST8_C(X)
UINT_LEAST16_C(X)
UINT_LEAST32_C(X)
UINT_LEAST64_C(X)

INT_FAST8_C(X)
INT_FAST16_C(X)
INT_FAST32_C(X)
INT_FAST64_C(X)
UINT_FAST8_C(X)
UINT_FAST16_C(X)
UINT_FAST32_C(X)
UINT_FAST64_C(X)

INTMAX_C(X)
UINTMAX_C(X)

[cerrar]



Más sobre tipos booleanos.

   Si bien el lenguaje C tiene definido un tipo booleano, el _Bool (para indicar valores VERDADERO o FALSO), también provee la librería stdbool.h, la cual vuelve a definir un tipo booleano, aunque con otro nombre.

En stdbool.h se definen las macros:

bool
true
false
__bool_true_false_are_defined


   La macro bool equivale a _Bool.
   No es un nuevo tipo de datos, sino otra forma de decir: _Bool.
   La macro true es igual a 1, y false igual a 0.
   La macro __bool_true_false_are_defined vale 1, y se usa para avisar que las 3 macros anteriores están definidas.

   Estas macros permiten programas más legibles. El tipo booleano tiene el feo nombre _Bool en vez de bool, para no colisionar con programas ya existentes, que definen su versión de bool.



Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 10. Pare de sufrir: Alternativas a la consola CMD
Publicado por: argentinator en 05 Enero, 2013, 04:36 pm
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.


   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:


   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. ;)

(https://foro.rinconmatematico.com/index.php?action=dlattach;topic=64835.0;attach=12514)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 11. Configuración de opciones del Compilador. Compatibilidad
Publicado por: argentinator en 08 Enero, 2013, 01:07 pm
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.

(https://foro.rinconmatematico.com/index.php?action=dlattach;topic=64835.0;attach=12519)

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 (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 12. Uso básico de la función printf()
Publicado por: argentinator en 08 Enero, 2013, 07:26 pm
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 (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 13. Testeando tipos de datos enteros de C (parte I)
Publicado por: argentinator en 09 Enero, 2013, 09:13 am
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 (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 14. Testeando tipos de datos enteros de C (parte II)
Publicado por: argentinator en 10 Enero, 2013, 09:30 pm
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 (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 15. Testeando tipos de datos enteros de C (parte III)
Publicado por: argentinator en 10 Enero, 2013, 10:37 pm
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 (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 16. Testeando tipos de datos enteros de C (parte IV)
Publicado por: argentinator en 10 Enero, 2013, 11:31 pm
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:


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 (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 17. Números de punto flotante. Generalidades
Publicado por: argentinator en 15 Enero, 2013, 04:56 am
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/IEEE_754-2008)
http://en.wikipedia.org/wiki/Half_precision_floating-point_format (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/Single_precision_floating-point_format)
http://en.wikipedia.org/wiki/Double_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/Quadruple_precision_floating-point_format)
http://en.wikipedia.org/wiki/Decimal32_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/Decimal64_floating-point_format)
http://en.wikipedia.org/wiki/Decimal128_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 (http://gcc.gnu.org/c99status.html)


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC



Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 18. Números de punto flotante. Estándar IEEE 754
Publicado por: argentinator en 15 Enero, 2013, 06:32 am
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 (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 19. Números de punto flotante. Estándar C99. Constantes
Publicado por: argentinator en 17 Enero, 2013, 12:31 am
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 (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 20. Números de punto flotante. Redondeos y cambios de base
Publicado por: argentinator en 18 Enero, 2013, 05:34 am
20. Números de punto flotante. Redondeos y cambio de base

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

1. Redondeo de constantes decimales.

   ¿Qué ocurre si queremos especificar la siguiente constante?

1.0000000001F

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

Y el ganador es...  :-*

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

-1    indeterminable: no se puede determinar cuál es el método de redondeo empleado.
 0    hacia 0.
 1    to nearest: hacia el más cercano (esto quiere decir, hacia "par" o hacia "impar", como lo definimos arriba).
 2    hacia \( +\infty \).
 3    hacia \( -\infty \).


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

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

   Pero vayamos un poco más profundo.

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

3. Redondeo para constantes hexadecimales.

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

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

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

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

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

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

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

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

4-El estándar IEEE 754 establece:

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

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

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

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

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

1.000000'12

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

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

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

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

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

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

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

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

   En binario tiene una representación periódica:

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

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

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

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

1.099999'90
1.100000'02

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

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 21. Números de punto flotante. Valores fuera de rango
Publicado por: argentinator en 18 Enero, 2013, 10:56 pm
21. Números de punto flotante. Valores fuera de rango

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

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

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

1.FFFFFEp127f  ;) ;)

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

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

(que es del orden de los 340 sextillones).

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

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

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

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

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

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

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

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

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

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

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


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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

Convertir entre binario, decimal y hexadecimal (http://www.mathsisfun.com/flash.php?path=/numbers/images/binary-dec-hex.swf&w=798&h=198&col=%23FFFFFF&title=Binary%2FDecimal%2FHexadecimal+Converter)

Convertir bits de un Single Precision de IEEE 754 (http://www.h-schmidt.net/FloatConverter/IEEE754.html)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 22. Números de punto flotante. Estándar C99. FLOAT.H
Publicado por: argentinator en 19 Enero, 2013, 11:22 pm
22. Números de punto flotante. Estándar C99. FLOAT.H

   Para saber detalles sobre los tipos de punto flotante en nuestra implementación local de C, debemos estudiar el archivo estándar de biblioteca float.h.
   El estándar C99, en las secciones 5.2.4.2.2, y apéndices E y F de su documento oficial, establece las normativas relativas a los tipos de punto flotante, así como el contenido de <float.h> y su significado.

Tengamos en cuenta que, para los parámetros que se indican a continuación, el C99 no presupone un sistema ajustado al estándar ISO/IEC/IEEE 60559 (IEEE 754).

   El archivo <float.h> contiene sólo macros que son constantes numéricas.
   Algunas son enteras y otras de punto flotante.
   La implementación local debe especificar valores constantes para las siguientes macros:

FLT_EVAL_METHOD
FLT_ROUNDS

   Así que podemos estar seguros de que esas macros están definidas para nuestra versión del compilador.
   La macro FLT_EVAL_METHOD se refiere a los métodos de evaluación de las operaciones aritméticas en relación a los tipos de punto flotante que hay en el sistema.
   No daremos muchos detalles de esto aquí ya que aún no hemos visto nada sobre las operaciones aritméticas.
   Digamos tan sólo que al realizar operaciones aritméticas con tipos en punto flotante, lo común es perder exactitud, debido a los redondeos automáticos necesarios para que el resultado quepa en el tipo de punto flotante elegido.
   No obstante, el resultado final, con redondeo y todo, puede diferir según la precisión utilizada en los cálculos intermedios.
   Los métodos de evaluación se refieren a esto, y los valores posibles para FLT_EVAL_METHOD son:

-1    indeterminable (no se puede determinar el método de evaluación implementado).

0    evaluar todas las operaciones y constantes tan sólo según el rango de valores y la precisión del tipo (de los operandos).

1    evaluar operaciones y constantes de tipos float y double al rango de valores y precisión del tipo double, evaluar operaciones y constantes de tipo long double al rango de valores y precisión de long double

2    evaluar todas las operaciones y constantes al rango de valores y precisión del tipo long double.


   Otros valores negativos deben ser especificados claramente por la implementación local.
   La macro FLT_ROUNDS indica el método de redondeo, como ya explicamos en posts anteriores, y que aquí repetimos:

-1     indeterminable: no se puede determinar cuál es el método de redondeo empleado.
 0     hacia 0.
 1     to nearest: hacia el más cercano.
 2     hacia \( +\infty \).
 3     hacia \( -\infty \).


   Cuando se está bajo la normativa del estándar IEEE 754 (por ejemplo, poniendo la macro __STDC_IEC_559__ igual a 1), el método to nearest es hacia par (en caso de ambigüedad, el redondeo se resuelve hacia el valor más cercano en el tipo de punto flotante usado, tal que la mantisa es par, que en la representación binaria significa un 0 en el último dígito significativo).
   Son posibles otros valores para FLT_ROUNDS, pero su significado depende de la implementación local, que debe documentar apropiadamente al respecto.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Los documentos de los estándares IEEE 754 y C99 no usan la palabra mantisa, por considerarla imprecisa.
   Utilizan en su lugar el término significando.
   Ni pienso explicar este detalle pedante...  >:(

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   La implementación local debe indicar valores para las siguientes macros, algunas de las cuales tienen exigencias indicadas por el estándar C99, y cuyo significado explicamos.

FLT_RADIX

   Debe ser un número entero \( \geq 2 \).
   Indica la base \( b \) en la cual se representarán los números de punto flotante.
   Lo típico es, por supuesto, \( b = 2 \).
   Pero hay casos en que \( b \) puede ser, a veces, otra potencia de 2 (lo más típico sería \( b = 16 \)).
   También puede que algún sistema en el futuro prefiera base decimal (b = 10).
   Formalmente, cualquier entero \( \geq 2 \) está admitido.

FLT_MANT_DIG

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

DBL_MANT_DIG

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

LDBL_MANT_DIG

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

   El estándar no establece restricciones para esos números.
   Es muy posible que se obtengan como resultado de operaciones con otras constantes que definen el formato a usar en los tipos float, double, long double, las que vienen a continuación.

DECIMAL_DIG

   Cantidad \( n \) de dígitos tal que: cualquier número \( x \) de punto flotante en base \( b \) (o sea, FLT_RADIX, que casi siempre es 2) en el tipo de punto flotante más ancho que haya en la implementación local  (o sea, de mantisa con la mayor cantidad \( p=p_{max} \) de dígitos de precisión), redondeado a \( n \) dígitos en base decimal, y luego convertido otra vez a base \( b \), da como resultado el mismo valor \( x \) original.
   El estándar establece que DECIMAL_DIG debe ser \( \geq 10 \).
   Para entender esto, debemos recordar que en el sistema pueden existir tipos de punto flotante con mayor precisión y rango de valores que long double.
   Esto quedará especificado por la implementación local.
   Si esto no ocurre así, entonces este tipo flotante máximo coincide con long double, como es lógico.

   Matemáticamente, esta cantidad \( n \) de dígitos (óptima) se calcula por:

\( \displaystyle n=
\begin{cases}
   p_{max}\log_{10} b,&\text{si $b$ es potencia de 10}\\
   \lceil 1+p_{max}\log_{10} b\rceil,\qquad&\text{en otro caso}.
\end{cases}
 \)

   Si hacemos el cálculo para el formato Single Precision, con \( p = 24, b = 2 \), nos da \( n = 9 \).  :o
   Así que, el hecho de que el mínimo exigido por el estándar sea \( n = 10 \), obliga a que en la implementación local exista al menos un tipo de punto flotante que sea estrictamente mayor (en precisión de la mantisa al menos) que el formato Single Precision.  8^)
   (Para ser exactos, aplicando la fórmula de arriba, debemos tener \( p_{max} \geq 27 \)  :-* ).

FLT_DIG

   Número \( q \) de dígitos tal que cualquier número \( x \) de punto flotante con \( q \) dígitos en base decimal (10) se puede redondear a un número en base \( b \) (o sea, FLT_RADIX, que casi siempre es 2)  de tipo float, y luego redondearlo otra vez a \( q \) dígitos en base decimal, de tal manera que el número obtenido quede igual que el \( x \) original.
   El estándar establece que FLT_DIG debe ser \( \geq 6 \).

DBL_DIG

   Número \( q \) de dígitos tal que cualquier número \( x \) de punto flotante con \( q \) dígitos en base decimal (10) se puede redondear a un número en base \( b \) (o sea, FLT_RADIX, que casi siempre es 2) de tipo double, y luego redondearlo otra vez a \( q \) dígitos en base decimal, de tal manera que el número obtenido quede igual que el \( x \) original.
   El estándar establece que DBL_DIG debe ser \( \geq 10 \).

LDBL_DIG

   Número \( q \) de dígitos tal que cualquier número \( x \) de punto flotante con \( q \) dígitos en base decimal (10) se puede redondear a un número en base \( b \) (o sea, FLT_RADIX, que casi siempre es 2) de tipo long double, y luego redondearlo otra vez a \( q \) dígitos en base decimal, de tal manera que el número obtenido quede igual que el \( x \) original.
   El estándar establece que LDBL_DIG debe ser \( \geq 10 \).
   Matemáticamente, si tomamos un tipo flotante con \( p \) dígitos de precisión (en base \( b \) dada por FLT_RADIX), la cantidad \( q \) de dígitos que permiten ir de base decimal a base \( b \), y luego volver, y recuperando el valor exacto original, está dado por:

\( \displaystyle q=
\begin{cases}
   p\log_{10} b,&\text{si $b$ es potencia de 10}\\
   \lfloor(p-1)\log_{10} b\rfloor,\qquad&\text{en otro caso}.
\end{cases}.
 \)

   Por ejemplo, para Single Precision (\( p = 24 \)) y base \( b = 2 \), tenemos \( q = \lfloor(24-1)\log_{10} 2\rfloor =\lfloor 6.92\rfloor = 6 \).
   En realidad, si se usa un valor de \( q \) más pequeño que 6, la conversión de base 10 a base \( b \) y luego de nuevo a base 10, sigue dando resultados exactos hasta \( q \) dígitos decimales significativos.

FLT_MIN_EXP

   Representa el mínimo exponente entero negativo \( e \), tal que \( b^{e-1} \) es todavía un número normalizado en el tipo float, en donde \( b \) es la base para los dígitos de mantisa (especificada en FLT_RADIX, típicamente \( b = 2 \)) .

DBL_MIN_EXP

   Representa el mínimo exponente entero negativo \( e \), tal que \( b^{e-1} \) es todavía un número normalizado en el tipo double,  en donde \( b \) es la base para los dígitos de mantisa (especificada en FLT_RADIX, típicamente \( b = 2 \)).

LDBL_MIN_EXP

   Representa el mínimo exponente entero negativo \( e \), tal que \( b^{e-1} \) es todavía un número normalizado en el tipo long double, en donde \( b \) es la base para los dígitos de mantisa (especificada en FLT_RADIX, típicamente \( b = 2 \)) .
   Pongamos el ejemplo de un float en formato de Single Precision. Tendríamos \( e = -125 \), con base \( b = 2 \), ya que \( 2^{-126} \) es el mínimo número normalizado en Single Precision.
   (Intencionalmente no he usado la notación \( e_{min} \) porque ambos estándares le dan una connotación diferente, por más que los valores normalizados obtenidos son los mismos para ambos).

FLT_MAX_EXP

   Representa el máximo exponente entero positivo \( e \), tal que \( b^{e-1} \) es todavía un número representable en el tipo float, en donde \( b \) es la base para los dígitos de mantisa (especificada en FLT_RADIX, típicamente \( b = 2 \)) .

DBL_MAX_EXP

   Representa el máximo exponente entero positivo \( e \), tal que \( b^{e-1} \) es todavía un número representable en el tipo double, en donde \( b \) es la base para los dígitos de mantisa (especificada en FLT_RADIX, típicamente \( b = 2 \)) .

LDBL_MAX_EXP

   Representa el máximo exponente entero positivo \( e \), tal que \( b^{e-1} \) es todavía un número representable en el tipo long double, en donde \( b \) es la base para los dígitos de mantisa (especificada en FLT_RADIX, típicamente \( b = 2 \)) .
   Pongamos el ejemplo de un float en formato de Single Precision. Tendríamos \( e = +128 \), con base \( b = 2 \), ya que \( 2^{128-1} \) es la máxima potencia de 2 representable en Single Precision (potencias mayores ya darían overflow).
   (Intencionalmente no he usado la notación \( e_{max} \) porque ambos estándares le dan una connotación diferente, por más que los valores normalizados obtenidos son los mismos para ambos).

   El estándar no especifica un valor máximo para estos últimos seis exponentes.
   Sólo indica que los primeros tres deben ser enteros negativos y los últimos tres enteros positivos.
   Sin embargo, pueden quedar restringidos o implicados por las seis constantes que siguen a continuación.

FLT_MIN_10_EXP

   Denota el mínimo entero negativo \( d \) tal que \( 10^d \) aún está en el rango de los valores normalizados del tipo float.
   El estándar estipula que esta constante debe ser \( \leq -37 \).

DBL_MIN_10_EXP

   Denota el mínimo entero negativo \( d \) tal que \( 10^d \) aún está en el rango de los valores normalizados del tipo double.
   El estándar estipula que esta constante debe ser \( \leq -37 \).

LDBL_MIN_10_EXP

   Denota el mínimo entero negativo \( d \) tal que \( 10^d \) aún está en el rango de los valores normalizados del tipo long double.
   El estándar estipula que esta constante debe ser \( \leq -37 \).

FLT_MAX_10_EXP

   Denota el máximo entero negativo \( d \) tal que \( 10^d \) aún está en el rango de los valores representables del tipo float.
   El estándar estipula que esta constante debe ser \( \geq +37 \).

DBL_MAX_10_EXP

   Denota el máximo entero negativo \( d \) tal que \( 10^d \) aún está en el rango de los valores representables del tipo float.
   El estándar estipula que esta constante debe ser \( \geq +37 \).

LDBL_MAX_10_EXP

   Denota el máximo entero negativo \( d \) tal que \( 10^d \) aún está en el rango de los valores representables del tipo float.
   El estándar estipula que esta constante debe ser \( \geq +37 \).


FLT_MIN

   Representa el mínimo número normalizado de punto flotante del tipo float. El estándar exige que este valor sea \( \leq 10^{-37}. \)

DBL_MIN

   Representa el mínimo número normalizado de punto flotante del tipo double.
   El estándar exige que este valor sea \( \leq 10^{-37}. \)

LDBL_MIN

   Representa el mínimo número normalizado de punto flotante del tipo long double.
   El estándar exige que este valor sea \( \leq 10^{-37}. \)

   Estas tres últimas constantes han de coincidir exactamente con el número \( b^{e-1} \), donde \( e \) es el exponente negativo mínimo que se define en FLT_MIN_EXP, DBL_MIN_EXP ó LDBL_MIN_EXP, según corresponda.

FLT_MAX

   Representa el máximo número de punto flotante que aún es representable en el tipo float.
   El estándar exige que este valor sea \( \geq 10^{+37}. \)

DBL_MAX

   Representa el máximo número de punto flotante que aún es representable en el tipo double.
   El estándar exige que este valor sea \( \geq 10^{+37}. \)

LDBL_MAX

   Representa el máximo número de punto flotante que aún es representable en el tipo long double.
   El estándar exige que este valor sea \( \geq 10^{+37}. \)
   Estas tres últimas constantes han de coincidir exactamente con el número \( (1-b^{-p})b^{e} \), donde \( p \) es la precisión indicada por FLT_MANT_DIG, DBL_MANT_DIG, LDBL_MANT_DIG, \( e \) es el exponente positivo máximo que se define en FLT_MAX_EXP, DBL_MAX_EXP ó LDBL_MAX_EXP, según corresponda en cada caso.

FLT_EPSILON

   Indica la diferencia exacta entre el número 1.0 y el mínimo número de tipo punto flotante mayor que 1, representable en el tipo float.
   El estándar exige que este valor sea \( \leq 10^{-5} \).

DBL_EPSILON

   Indica la diferencia exacta entre el número 1.0 y el mínimo número de tipo punto flotante mayor que 1, representable en el tipo double.
   El estándar exige que este valor sea \( \leq 10^{-9} \).

LDBL_EPSILON

   Indica la diferencia exacta entre el número 1.0 y el mínimo número de tipo punto flotante mayor que 1, representable en el tipo long double.
   El estándar exige que este valor sea \( \leq 10^{-9} \).
   Para estas tres últimas constantes, el valor coincide exactamente con \( \epsilon = b^{1-p} \), donde \( p \) es la cantidad de dígitos en la mantisa, indicada según cada caso por FLT_MANT_DIG, DBL_MANT_DIG, LDBL_MANT_DIG.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Veamos una tabla resumida de las macros de float.h, con sus restricciones:

FLT_ROUNDS       (un número entero)
FLT_EVAL_METHOD  (un número entero)
FLT_RADIX        \( \geq \)  2    (un número entero)

FLT_MANT_DIG     (un número entero positivo)
DBL_MANT_DIG     (un número entero positivo)
LDBL_MANT_DIG    (un número entero positivo)

DECIMAL_DIG      \( \geq \) 10    (entero)

FLT_DIG          \( \geq \) 6     (entero)
DBL_DIG          \( \geq \) 10    (entero)
LDBL_DIG         \( \geq \) 10    (entero)

FLT_MIN_EXP     (un número entero negativo)
DBL_MIN_EXP     (un número entero negativo)
LDBL_MIN_EXP    (un número entero negativo)
FLT_MAX_EXP     (un número entero positivo)
DBL_MAX_EXP     (un número entero positivo)
LDBL_MAX_EXP    (un número entero positivo)

FLT_MIN_10_EXP   \( \leq \) -37        (entero)
DBL_MIN_10_EXP   \( \leq \) -37        (entero)
LDBL_MIN_10_EXP  \( \leq \) -37        (entero)
FLT_MAX_10_EXP   \( \geq \) +37        (entero)
DBL_MAX_10_EXP   \( \geq \) +37        (entero)
LDBL_MAX_10_EXP  \( \geq \) +37        (entero)

FLT_MIN          \( \leq 10^{-37} \)       (número positivo de punto flotante)
DBL_MIN          \( \leq 10^{-37} \)       (número positivo de punto flotante)
DBL_MIN          \( \leq 10^{-37} \)       (número positivo de punto flotante)
FLT_MAX          \( \geq 10^{+37} \)       (número de punto flotante)
DBL_MAX          \( \geq 10^{+37} \)       (número de punto flotante)
LDBL_MAX         \( \geq 10^{+37} \)       (número de punto flotante)

FLT_EPSILON      \( \leq 10^{-5} \)        (número positivo de punto flotante)
DBL_EPSILON      \( \leq 10^{-9} \)        (número positivo de punto flotante)
LDBL_EPSILON     \( \leq 10^{-9} \)        (número positivo de punto flotante)


   Esas constantes deben además cumplir algunas relaciones aritméticas entre sí:
   Denotemos:

\( b = \) FLT_RADIX,
\( n = \) DECIMAL_DIG,
\( p_1 = \) FLT_MANT, \( p_2 = \) DBL_MANT, \( p_3 = \) LDBL_MANT,
\( q_1 = \) FLT_DIG, \( q_2 = \) DBL_DIG, \( q_3 = \) LDBL_DIG,
\( e_1 \) = FLT_MIN_EXP, \( e_2 = \) DBL_MIN_EXP, \( e_3 = \) LDBL_MIN_EXP,
\( e_1' = \) FLT_MAX_EXP, \( e_2' = \) DBL_MAX_EXP, \( e_3' = \) LDBL_MAX_EXP,
\( d_1 = \) FLT_MIN_10_EXP, \( d_2 = \) DBL_MIN_10_EXP, \( d_3 = \) LDBL_MIN_10_EXP,
\( d_1' = \) FLT_MAX_10_EXP, \( d_2' = \) DBL_MAX_10_EXP, \( d_3' = \) LDBL_MAX_10_EXP,
\( m_1 \) = FLT_MIN, \( m_2 = \) DBL_MIN, \( m_3 = \) LDBL_MIN,
\( M_1 = \) FLT_MAX, \( M_2 = \) DBL_MAX, \( M_3 = \) LDBL_MAX,
\( \epsilon_1 = \) FLT_EPSILON, \( \epsilon_2 = \) DBL_EPSILON, \( \epsilon_3 = \) LDBL_EPSILON.

Para \( i=1,2,3 \), podemos escribir:

\( \displaystyle
\begin{align*}
      b&\geq 2\\
  m_i &= b^{e_i-1}\\
  b^{e_i'}\leq M_i &= b^{e_i'}(1-b^{-p_i})\\
\epsilon_i &= b^{1-p_i}\\
  m_i &\leq 10^{d_i}\\
   10^{d_i'}&\leq M_i\\
  0< q_i & \leq\begin{cases}
   p_i\log_{10} b,&\text{si $b$ es potencia de 10}\\
   \lfloor(p_i-1)\log_{10} b\rfloor,\qquad&\text{en otro caso};
\end{cases}\\
\end{align*}
 \)             

   Además, el estándar establece que el rango de valores de float es subconjunto del de double, y a su vez éste es subconjunto del de long double.
   En particular, la precisión de long double tiene que ser al menos tan buena como la de double, y éste a su vez tan buena como la de float.
   Por lo tanto:  \( m_3\leq m_2\leq m_1 < M_1\leq M_2\leq M_3 \), y \( p_1\leq p_2\leq p_3 \).
   En particular, esto implica \( e_3\leq e_2\leq e_1 <0 \), \( e_1'\leq e_2'\leq e_3' \), \( d_3\leq d_2\leq d_1 <0 \) y \( d_1'\leq d_2'\leq d_3' \).
   Si bien las constantes \( p_i, e_i, e_i' \), no se especifican directamente, se pueden calcular.
   Veamos cómo hacerlo.
      \( e_i  \) se puede obtener a partir de \( d_i \), definiéndolo como el máximo entero \( e \) tal que \( b^{e-1}\leq 10^{d_i} \).
   Luego obtenemos fácilmente la precisión:

\( p_i = 1-\log_b e_i. \)

      \( e_i' \) se puede obtener a partir de \( d_i' \), definiéndolo como el máximo entero \( e \) tal que \(  b^{e}(1-b^{-p_i})\leq M_i \).
   Si observamos minuciosamente, vemos que también es posible definir \( e_i \) como el mínimo entero \(  e  \) tal que \( 10^{d_i}\leq b^{e}(1-b^{-p}) \).
   Esas dos caracterizaciones debieran coincidir...

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Notemos que algunas constantes en float.h se exigen como enteras, y otras como punto flotante.
   El estándar no especifica de qué tipos específicos tienen que ser esas constantes.
   Sin embargo, se exige que los valores de punto flotante especificados sean finitos, en el tipo que estén especificadas.
   De las definiciones de los números \( q_i \) parece apropiado exigir que \( 0< q_1\leq q_2\leq q_3 \), pero no parece que esto sea exigido directa o indirectamente por el estándar.
   Si los números \( q_i \) tomaran sus valores máximos, acorde a la precisión  \( p_i  \) correspondiente, entonces sí ocurriría obligadamente que \( q_1\leq q_2\leq q_3 \).
   De otro modo, esto no estaría asegurado.

   Sin embargo, si de verdad fuera así, lo considero una inconsistencia, y cabe esperar que esa desigualdad de los números \( q_i \) valga en toda implementación basada en el estándar.
   (En realidad, en el estándar aparece la fórmula que hemos mostrado arriba para calcular \( q \) respecto de \( p \), como una igualdad, y esto podría interpretarse como un valor obligado de \( q \), sin opción a que la implementación elija un número menor).
   Usando fórmulas exactas, tendríamos siempre que \( n> q_3 \).
   Otros detalles del estándar se muestran en el último apartado de nuestro post:

Números de punto flotante. Estándar C99. Constantes (http://rinconmatematico.com/foros/index.php?topic=64835.msg261637#msg261637)

titulado: Normativa ISO/IEC/IEEE 60559 (ó IEEE 754).

   Se observa que los 37s que andan dando vueltas por ahí, así como otras constantes mínimas exigidas, obligan a que el tipo float sea al menos tan grande como un formato Single Precision, tanto en precisión como en exponentes.
   Obsérvese además que un valor de \( \epsilon \leq 10^{-9}  <10^{-5} \) para double obliga indirectamente a la implementación local a proveer al menos un tipo de punto flotante que tenga estrictamente más precisión que un formato Single Precision (aunque no necesariamente exponentes más amplios).
   Finalmente, el estándar recomienda aunque no obliga, a que un número de tipo double, en base \( b \), convertido a decimal considerando allí sólo \( n = \) DECIMAL_DIG dígitos decimales de exactitud, y convirtiendo esto de nuevo a base \( b \), se obtenga exactamente el número original.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Hasta aquí las especificaciones del C99 y del IEEE 754.

   La gran pregunta es: ¿nuestro compilador se ajusta a estos estándares de punto flotante?  ???  :'( ???  :'( ???  :'( ???
   Analizaremos esto en el siguiente post.

 :-* :-*

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 23. Números de punto flotante. Compilador GCC 4.7
Publicado por: argentinator en 21 Enero, 2013, 10:29 pm
23. Números de punto flotante. Compilador GCC

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

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

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

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

Primero que nada, la documentación necesaria:

[1] Estado de GCC respecto el estándar C99 (http://gcc.gnu.org/c99status.html)

[2] Estándares de C y su grado de implementación por GCC (http://gcc.gnu.org/onlinedocs/gcc/Standards.html#Standards)

[3] Implementación del lenguaje C que hace GCC (http://gcc.gnu.org/onlinedocs/gcc/C-Implementation.html#C-Implementation)

[4] Implementación de GCC de números de punto flotante (http://gcc.gnu.org/onlinedocs/gcc/Floating-point-implementation.html#Floating-point-implementation)

[5] Tipos de punto flotante adicionales (http://gcc.gnu.org/onlinedocs/gcc/Floating-Types.html#Floating-Types)

[6] Tipos flotante de Semi-Precición (http://gcc.gnu.org/onlinedocs/gcc/Half_002dPrecision.html#Half_002dPrecision)

[7] Tipos flotantes decimales (http://gcc.gnu.org/onlinedocs/gcc/Decimal-Float.html#Decimal-Float)

[8] Constantes de punto flotante en hexadecimal (http://gcc.gnu.org/onlinedocs/gcc/Hex-Floats.html#Hex-Floats)

[9] Tipos de punto flotante de punto fijo (http://gcc.gnu.org/onlinedocs/gcc/Fixed_002dPoint.html#Fixed_002dPoint)

[10] Extensiones que el GCC hace por su cuenta al lenguaje C (http://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html#C-Extensions)



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

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

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

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

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

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



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

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

-std=c90   -pedantic

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

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

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

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



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

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

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

Hay otras especificaciones, pero por ahora no nos conciernen.



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

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



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

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

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



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

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

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



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



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



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

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




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




Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)
Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 24. Números de punto flotante. Programas de Testeo (parte I)
Publicado por: argentinator en 25 Enero, 2013, 12:06 am
24. Números de punto flotante. Programas de Testeo (parte I)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

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

LD(FLT_MAX)

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

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

system("CHCP 28591>NUL");

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

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

#define ACENTOS_LineaComandosOk

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

[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   El programa va colocado a continuación en spoiler:

Spoiler
TestingFloatMacros.c


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

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

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

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

#define ACENTOS_LineaComandosOk

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

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

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


[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC


Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)
Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 25. Números de punto flotante. Programas de Testeo (parte II)
Publicado por: argentinator en 27 Enero, 2013, 08:47 am
25. Números de punto flotante. Programas de Testeo (parte II)

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

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

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

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

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

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

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

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

FP_INFINITE
FP_NAN
FP_NORMAL
FP_SUBNORMAL
FP_ZERO

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

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

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

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

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

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

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

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

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

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

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

   Finalmente, nombremos la siguiente macro:

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

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

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

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

Spoiler


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

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

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

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

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

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


int main(void) {

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




[cerrar]



CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)
Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 26. Números de punto flotante. Programas de Testeo (III)
Publicado por: argentinator en 27 Enero, 2013, 11:53 pm
26. Números de punto flotante. Programas de Testeo (parte III)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

El programa está en el spoiler:

Spoiler

TestingFloatIII.c



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

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

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

int main(void) {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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




[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)
Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 27. Números complejos en C. Preliminares
Publicado por: argentinator en 28 Enero, 2013, 03:52 am
27. Números complejos en C. Preliminares

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

   Todos estos detalles los estudiaremos en el post siguiente.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)
Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Program. en C: 28. Números complejos en C. Estándar C99. Conversiones de tipos
Publicado por: argentinator en 29 Enero, 2013, 06:58 am
28. Números complejos en C. Estándar C99. Conversión de tipos

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

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

float _Complex, double _Complex, long double _Complex.

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

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

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

#undef I

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

7.34 + 147.22 * _Complex_I

o bien esto otro, que es equivalente:

7.34 + 147.22 * I

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

float _Imaginary, double _Imaginary, long double _Imaginary

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

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

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

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

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

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


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

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

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


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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



CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Tratamiento de valores infinitos y NaN en el caso complejo.

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

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

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

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

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

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

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

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


   Con eso, si ponemos la instrucción:

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

   Nos mostrará este resultado:

1.140000e+000 + 2.430000e-001 i

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

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

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

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

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


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)
Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 29. Números complejos en C. Programa de testeo.
Publicado por: argentinator en 18 Febrero, 2013, 10:50 pm
29. Números complejos en C. Programa de testeo

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

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

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

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

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

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

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

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

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

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

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

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

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

   El programa completo es como sigue:

(Abrir para ver programa)

TestingComplex.c



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

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

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

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

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

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

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

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

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

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


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

    getchar();

}



[cerrar]

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

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



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

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

Hay soporte parcial para C99(ap.G)

La macro 'complex' está definida.

La macro 'imaginary' NO está definida.

La macro '_Complex_I' está definida.

La macro '_Imaginary_I' NO está definida.

La macro 'I' está definida.

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

Definición interna de las macros:

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

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


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

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

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

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

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

HUGE_VALL * I + 1.0
         == nan + inf i

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




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

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)
Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 30. ¿Cómo lograr que todo funcione? (Parte I)
Publicado por: argentinator en 20 Febrero, 2013, 06:14 pm
30. ¿Cómo lograr que todo funcione? (Parte I)

Este post tiene un contenido netamente espiritual.  ;D

(Lectura opcional)
Primero que nada, ¿para qué diablos aprendemos un lenguaje de programación?
Obviamente, es que nos gusta resolver cositas con la computadora, y disfrutamos ver que una máquina hace automáticamente algo que a nosotros nos llevaría mucho esfuerzo, sudor y lágrimas.

En el fondo, las computadoras son máquinas que nos alivian una buena parte del trabajo intelectual.
No sólo eso, sino que lo hacen con rapidez, a una velocidad de cálculo que los seres humanos no podemos alcanzar.
Esto se vuelve importante porque no sólo ahorramos esfuerzo, sino también tiempo.

En cualquier caso, se ahorra dinero.
Pensemos que el esfuerzo físico o mental nos obliga a ingerir nutrientes, que nos insumen un cierto costo.
Si además podemos producir más resultados en menos tiempo, esto se traduce en un aumento en las ganancias que provienen de nuestro trabajo.

Una ventaja adicional que se gana con las máquinas es que ellas pueden, además, intercambiar información entre sí, de un modo eficiente, y también de bajo costo. Y también es cómodo.
No podemos pasarle a un amigo una fotografía por teléfono, sólo podemos describírsela, y más o menos.
Un conjunto complejo de datos tampoco es fácil de pasar por teléfono o telégrafo.
Hay millones de transacciones y actos burocráticos que tienen la oportunidad, hoy en día, de automatizarse y terminarse rápidamente gracias a las computadoras.

Luego de toda esta propaganda pro-computadoras, es hora de que hagamos algo de publicidad en contra.
Las computadoras no son mágicas. Son máquinas pasivas que requieren que un ser con voluntad le dé órdenes para realizar una tarea. También insumen un costo energético: electricidad (aunque el consumo es bajo comparado con otros electrodomésticos).

Para lograr que la máquina haga algo, tenemos que aceptar el diseño dado por el fabricante, entender cómo trabaja la máquina internamente, y "hacerle tragar" por algún lado una lista de instrucciones a realizar, a fin de que dé el resultado de un cálculo o una búsqueda de información, o imprima un gráfico, etc.

En la prehistoria, esto se hacía con "tarjetas perforadas", que eran unos cartones rectangulares en los que se practicaban agujeros, donde se ponían instrucciones de máquina en código binario. Había un dispositivo de lectura de tarjetas que le pasaba los datos a la máquina, la máquina hacía sus pases mágicos, y después el resultado se obtenía, quién sabe dónde (lamparitas iluminadas, otra tarjeta, datos impresos, una cinta de audio  ??? ).

A medida que las cosas fueron evolucionando, se logró que una persona tenga en su escritorio un dispositivo con una pantalla cómoda de visualizar, un teclado fácil de usar al estilo de una máquina de escribir, y toda una circuitería miniaturizada, de última tecnología, escondida dentro de una carcaza de la que nadie tiene que preocuparse.

Lo único que hay que hacer ahora es escribir un programa en un lenguaje (elijan el C), traducirlo a lenguaje de máquina con un compilador (como el GCC), generando un programa ejecutable (un archivo de extensión .EXE bajo Windows), que hace lo que queremos que haga: resolver un problemita computacional de algún tipo.

Ahora, para lograr que la máquina haga algo, tenemos que "estudiar".  :'(
Estudiamos las reglas de un lenguaje de programación (el C), para ver cómo se escribe un programa (de alto nivel),
y de paso nos damos cuenta de las herramientas que ese programa nos provee (tipos de datos, manejo de archivos, acceso a memoria, control de flujo de la ejecución, soporte multihilo, librerías gráficas, etc.).
En la medida que ellenguaje nos ofrece más herramientas de control sobre nuestra máquina, más y mejores cosas podemos hacer.

Entonces para lograr que nuestros programas hagan lo que deseamos, tenemos que estudiar bien el lenguaje y sus librerías.
Si no existe una librería que haga lo que necesitamos, no queda más remedio que "inventarla".
Somos nosotros los que tendremos que diseñar una librería que resuelva una serie de tareas comunes.

Pero eso no siempre es necesario.
Es muy posible que si queremos desarrollar tareas "lindas y divertidas", alguien más ya ha diseñado librerías para resolver ese tipo de cosas.
Así que hay que averiguar quién hizo esas librerías.

Uno podría buscar un catálogo de programadores de todo el mundo, e ir preguntando de a uno quién de ellos hizo una librería que, por ejemplo, permita hacer figuras geométricas en la pantalla.
Más ágil sería la búsqueda si a la gente del mundillo de la programación le preguntamos si alguno de ellos "conoce a alguien" que haya hecho o tenga a mano la librería que buscamos.
Si hallamos una persona que tenga esa librería, puede que nos regale una copia, o puede que nos la venda.
A veces quien la vende es el mismo autor (y está en su derecho hacerlo), o alguien autorizado por el autor, pero a veces no, y quien nos la vende es un tipo que no tiene relación comercial con el autor. Eso viola los derechos de propiedad intelectual.

También viola los derechos de autor el conseguir una copia gratuita, de una librería que no tiene autorización de uso comercial, y luego utilizarla para generar un programa con fines comerciales.

O sea que, a pesar de que exista una librería que nos soluciona el problema, resulta que nuestro problema no se puede solucionar, porque la librería no puede usarse, por cuestiones legales.

Estos impedimentos no son ni de hardware ni de software, obviamente.
Ya se trata de cuestiones de tipo "social", y así vemos como el factor "social" juega un papel, en este caso negativo o represor, en el desarrollo de una aplicación para la computadora.

Por otra parte, si conseguirmos una copia pirata de una librería, y la usamos en forma privada sólo con fines de aprendizaje o experimentación, entonces es muy probable que no tengamos problemas de tipo legal.
Así, en ese caso, estaríamos resolviendo nuestro problema computacional, aunque no podríamos hacer público nuestro programa así realizado.  :(
Es como resolver la conjetura de Fermat, y no poder decírselo a nadie.
Sirve para decir que la solución existe, pero no se puede compartir dicha solución.

___________________________

Todo esto nos hace plantear de nuevo la dimensión "social" de la informática.
¿Para quién realizamos el programa?
Por lo general, si sólo lo realizamos para nosotros mismos, tenemos más libertades, porque en nuestra computadora mandamos nosotros, y podemos hacer y deshacer a nuestro antojo.

Cuando uno hace un programa para "alguien más", ya interesa el factor "social" del asunto, y entonces hay que "lidiar con las estupideces del género humano", como por ejemplo, las leyes de derecho de autor.

Así que no es fácil decir si un problema informático está o no resuelto, según cuál haya sido la motivación social que generó el problema.

Ahora bien. Con las mismas restricciones sociales que antes, podemos entrar en ellas al menos en forma más eficiente.
No vamos a preguntarle al amigo del amigo de un amigo, a ver si nos facilita una librería que nos falta.

Mejor, ya que estamos en la era de Internet, la buscamos en la red.
Sin embargo, esta opción no reemplaza a la opción de preguntarle al amigo del amigo de un amigo.
Son posibilidades complementarias.

Así, ponemos la dirección de un buscador (Google) en nuestro navegador (Firefox), y allí ponemos "librería para hacer figuras geométricas en C", clic en "Buscar".

Nos saldrá una lista enorme de enlaces con ese tema.
Lo que hay que hacer luego es entrar en varios de esos enlaces, y ver si realmente tienen lo que buscamos.
Si no, habrá que volver a la página de búsqueda, y probar con otro enlace, y así hasta hallar lo que queremos.

Ahora vemos que, si bien no tenemos que perder precioso tiempo diseñando nosotros mismos programando una "librería para hacer figuras geométricas en C",
no obstante igual perdemos tiempo realizando una búsqueda, que nos puede llevar horas si lo que buscamos está medio "escondido" en Internet.

incluso si encontramos una librería con los requisitos pedidos, tenemos que renegar con la página de descarga, que a veces tiene virus, troyanos, o bien opciones de descarga inentendibles o confusas, esquivar la publicidad, y prestar atención a que hay muchos botones que dicen "Descargar", bien grandotes, ¡pero que no descargan el software que queremos! No, esos son botones engañosos que esconden publicidad o directamente virus.

Hasta que por fin damos con el "verdadero" botón de descarga, que probablemente sea "pequeño" y "escondido" quién sabe en qué parte de la página, y la descarga comienza.

Tras descargar la librería, tenemos que investigar un poco cómo se instala, y cómo se hace para que encaje con nuestras librerías (del lenguaje C) que tenemos previamente instaladas.
Esto puede dar un terrible dolor de cabeza porque, digamos la verdad, nunca se entiende qué diablos hay que hacer ni para qué.  :banghead:

Hasta que finalmente uno termina adivinando, si tiene suerte, cómo se hace para que la descarga encaje con nuestro sistema en forma armoniosa.  :aplauso:

Luego hay que aprender a usar la librería, y también hay que hacer pruebas, hasta que finalmente todo funcione más o menos bien.
Pero puede ocurrir que los resultados obtenidos con esa librería que tanto nos costó bajar, nos sean decepcionantes.

Entonces tenemos que desinstalarla, con mucho cuidado para no hacer daño a la configuración del sistema que teníamos antes.
Si hacemos esto mal, que es lo más probable  ;) , no habrá más remedio que desinstalar todo, y volver a instalar todo desde cero.  :banghead:

Todo esto es una pérdida de tiempo, obviamente.
Pero a veces también es inevitable.

Y luego se repite el ciclo de búsqueda de una "librería para hacer figuras geométricas en C", y así probamos, instalamos y desinstalamos cosas extrañas, hasta que hallamos algo que nos conforme.

Puede pasar que no encontremos ninguna librería que nos satisfaga del todo, o que nos guste pero que ande mal, o que ande bien, pero que no sabemos configurarla, etc.

Todas estas molestias son el costado negativo del desarrollo informático de estos tiempos, y a los que a nadie parece importarle.  >:(

Algunas razones para no estar satisfechos con una librería pueden ser que necesiten características que consideremos indeseables.
Por ejemplo, hay programas o librerías que tienen una licencia de "prueba", o sea que son gratuitas por un tiempo, y luego hay que pagar para usarlas.
Si no tenemos intención de gastar dinero en estas cosas, es una pérdida de tiempo descargarlas para experimentar con ellas.

Puede darse el caso de programas o librerías que sean específicos de una plataforma, por ejemplo Windows.
Esto puede importarnos o no, pero si esto supone además que tenemos que aprendernos toda una filosofía de trabajo "bajo Windows" (las famosas API), eso ya es otra historia. Debemos tomar la decisión de invertir o no tiempo en eso.

Significaría aprender algo nuevo, para un solo sistema operativo, y es algo que cuesta tiempo.

Si la librería que nos descargamos es gratuita, hermosa, satisface nuestros deseos, es genérica (sirve para casi cualquier sistema operativo: Windows, Linux, etc.), y es más o menos fácil de aprender, estaríamos dispuestos a descargarla y utilizarla.

Aquí surgen otros inconvenientes.
Puede que la librería esté hecha con tanto amor, que se volvió demasiado completa y sofisticada.
Entonces nos será costoso aprenderla, e innecesario, ya que si sólo queremos hacer cosas sencillas, no sirve de nada aprender toda una filosofía de trabajo asociada a esa librería.

Además, puede ser toda una odisea encontrar manuales adecuados o claros, o completos, de una librería, incluso de una famosa.

_________________________________________

Otro obstáculo es el idioma.
Puede que en castellano encontremos muy poco material asociado a nuestra búsqueda.
Lo recomendable es poner frases en inglés en el buscador (Google), tales como "geometrical figures library in C".
A veces una tal búsqueda puede ser infructuosa si nuestra traducción no es afortunada.
Cambiando la palabra "figures" por "shapes", puede que tengamos muchas más posibilidades de éxito.

A continuación, habrá que renegar con las instrucciones en inglés, un idioma que cada vez entiendo menos, porque me gusta más el idioma que me enseñó mi mamá.

Por razones absolutamente misteriosas de "frikilandia", las librerías que uno necesita bajar no son tan simples de instalar. No basta con apretar un botón que diga "instalar" y listo.
A veces hay "muchos paquetitos" repartidos por ahí, distintos, independientes, pero todos necesarios y relacionados entre sí.
Es muy posible que uno no entienda para qué diablos sirve cada bendito paquetito, ni si le va a hacer falta alguna vez en la vida o no.
Las instrucciones de instalación no son naturales, intuitivas, sino que hay que navegar por la página web, leyendo y releyendo instrucciones, para ver cómo se instala algo, y que, tras instalarlo, lo más probable es que esté todo mal, no funcione nada, y hay que formatear el disco rígido debido al desastre que hicimos instalando y probando cosas nuevas.  :banghead:

Es por esa razón que yo, en este curso de C, hago lo posible para que no haya que instalar prácticamente nada, salvo al principio del curso, y nada más. Y en lo posible también, no cambiar las opciones originales de configuración. Porque cambiar algo en un sistema que usamos para compilar programas en C, es siempre candidato al desastre.

La consecuencia de todo esto, no es ya que nuestros programas no compilan, no funcionan, o que no podemos hacer exactamente lo que deseamos, sino que ni siquiera nos funciona el entorno de programación, y a veces el descalabro llega hasta las entrañas del sistema operativo (según qué cosa hayamos instalado, o bien si se nos metieros virus, gusanos, troyanos, etc.).

El resultado es siempre el mismo: NO FUNCIONA.

Y el objetivo es que las cosas: FUNCIONEN. Todo tiene que funcionar, y bien.

En la prehistoria la dificultad era lo primitivo de la tecnología de las computadoras,
luego la dificultad estaba en nuestros conocimientos adquiridos de programación,
después estaban en la disponibilidad o no de ciertas librerías o programas,
y ahora la dificultad está en lo intrincado que resulta instalar armoniosamente un componente a nuestro entorno de programación.

En cualquier caso, se invierte tiempo y esfuerzo.
LAS DIFICULTADES NO DESAPARECEN, SÓLO SE TRANSFORMAN.

________________________________

Sin embargo, no todo está perdido, y es ahora cuando recuperamos el factor "social" de la informática.
Si vemos que nosotros solos no podemos con un problema, para eso está el resto del mundo para que nos ayude.

Aunque la estructura del mundo es capitalista y mercantilista, es muy común encontrar personas que ofrecen ayuda gratuitamente, o que regalan soluciones.
Esto se puede hallar en Organizaciones Sin Fines de Lucro (ONGs), Institutos de Enseñanza (gratuitos), países donde el ingreso a la Universidad es gratuito, y comunidades virtuales en Internet.

En el caso de Internet, existieron en un principio las "listas de correo", que aún hoy están muy activas, pero que casi nadie sabe que existen. Son famosas en el mundillo de los informáticos, o de alguna especialidad técnica en particular.
En las listas de correo la gente escribe usando un conjunto reducido de caracteres: ASCII, y hasta es posible que ni siquiera se permitan subrayados ni resaltados en negrita o cursiva, como tanto me gusta usar a mí.
Esto es lógico ya que los sistemas que gestionan correos electrónicos puede que den resultados anómalos o desagradables, si es que hay algún error o incompatibilidad. Para evitar toda clase de posibles problemas, directamente te prohíben usar características especiales (HTML) en el correo, y sólo te dejan enviar texto plano (sin formato).

A veces puede pasar que enviaste un correo sin formato, pero te lo rebotan igual.
¿Por qué ocurre esto? Ocurre porque en realidad los servicios de email modernos ponen de todos modos un "formato básico". No basta conque nosotros no usemos ninguna característica HTML, sino que hay elegir específicamente una opción de correo que diga algo como "texto plano". De esa manera, se envía sólo texto (como un archivo TXT).

En eso hay un contratiempo, porque la tecnología de la información avanza para hacernos la vida más agradable, nos dicen que el formato HTML es maravilloso y podemos enviar correos electrónicos bellamente formateados, con fotos y videos, etc., pero resulta que hay gente que no nos deja utilizarlos: dicen que no, que hay que volver a la prehistoria.  ???

Entonces tenemos que "sintonizar" con esa mentalidad extraña, y tratar de expresarnos en forma escueta y clara, usando sólo texto plano, incluso en el caso que necesitamos explicar un asunto complejo.

Existen listas de correo que discuten temas de matemática, por ejemplo, escribiendo exclusivamente en LaTeX, tal como hacemos nosotros, pero con la diferencia de que ellos nunca pueden ver la formulita en forma agradable como hacemos en este foro.

Y discuten temas complejos de matemática así. Es bastante feo, por no decir otras cosas. Pero es algo que existe.

La necesidad de un formato que sea "universal", que no esté sujeto a interpretaciones erróneas de los navegadores, o que sea compatible con "todos" los navegadores y sistemas operativos del mundo, lleva a que la gente tome decisiones extremas como el de comunicarse con correos en "texto plano".
Me cuesta entender que al día de la fecha haya gente que no tenga un navegador de última generación.
Después de todo, son gratuitos.

En realidad, los correos en texto plano tienen la ventaja de que no saturan los servidores de ciertas páginas web que reciben mucha correspondencia, porque casi no ocupan memoria, como sí lo harían en caso de que aceptaran elementos multimedia avanzados.

__________________________

Una evolución a las listas de correo lo son, por supuesto, los foros de internet, como éste en el que estamos.
La manera de debatir temas en un foro es más cómoda y más organizada.
Si además el foro admite capacidades gráficas o multimedia de diversa índole, la comunicación de ideas e información se hace más agradable, fluida y natural.

Si no nos animamos a meternos en el mundillo de las listas de correo, podemos hacer el intento en los foros de internet.
Aquí podemos tener distintos tipos de experiencias.
Habrá foros donde se contestan las preguntas de un modo amable, pero escueto y seco.
Hay otros que contestan puras estupideces e ironías, y otros donde se responde con información incompleta, y encima con soberbia, como si el culpable fuera uno.

Si yo pregunto por qué no puedo factorizar el polinomio \( x^2+1 \), puede que me expliquen con cordura diciéndome que "eso depende del dominio en que considere definido el polinomio, siendo no factorizable en los reales, pero que sí lo es en los complejos".
Cuando me contestan así, me ponen un poco a prueba para ver qué es realmente lo que yo quiero hacer, o cuál es el grado de profundidad que quiero darle a la pregunta.

Entonces ya no es lo mismo que poner una consulta en Google y esperar la respuesta.
Ahora no queda más remedio que interactuar con las personas que están del otro lado de la pantalla, iniciar una conversación con seres humanos de verdad, y entonces volver a preguntar, hasta que la duda queda resuelta completamente.

Puede haber foros o personas que contesten así: "Acá, el problema es que vos nunca estudiaste números complejos, y por eso no sabés cómo se hace esto".
Con tonos parecidos se puede encontrar uno en algunos foros, sobretodo si son de una temática más general o más mundana.

Una respuesta así puede desanimarnos a usar los foros de internet.
Incluso el maltrato o las discusiones con otras personas son algo que es probable que ocurra alguna vez.
Los malentendidos están a la orden del día, por diversas razones, y la gente y sus defectos también.

El costado social del asunto tiene este aspecto negativo.
Entonces, lo que era un original problema de computación, se convierte ahora en un drama psicosocial, que nos hace perder tiempo, discutiendo con personas algo idiotas por un lado, o tratando de entender a personas demasiado técnicas por el otro lado. Esta interacción también lleva tiempo.

Uno tiene que tener paciencia, y seguir en la búsqueda de foros y personas que sean amables.
No tiene sentido desilusionarse o deprimirse por las malas experiencias con la gente en Internet.
Así como en el mundo hay unas 7000 millones de personas, constituyendo una "fauna" de lo más prolífica y exótica, lo mismo pasa con Internet. El mundo entero está ahí adentro, y hay mucha gente desagradable con la que tendremos que lidiar.

La ventaja de Internet es que la pantalla de la computadora nos protege.
Si tenemos que lidiar con un idiota, podemos insultarlo a nuestro antojo, o bien simplemente podemos elegir ignorarlo.

En general ignorar a la gente problemática o que piensa distinto que nosotros, en Internet, es la alternativa más "eficiente".
No puede uno perder demasiado tiempo en discusiones personales o filosóficas, porque tenemos que recordar que tenemos un problema que resolver, y que todo consume tiempo.

O sea que no sólo tenemos que saber "navegar" en la dimensión "social" de Internet, sino que también tenemos que resistir tentaciones propias de nuestra propia psicología.

Entre esas tentaciones están los videos graciosos de Youtube, las películas gratuitas de SeriesYonkis, las noticias escandalosas de la farándula y el deporte, y así por el estilo.

El entretenimiento es una pérdida de tiempo cuando estamos tratando de que nuestro programa "compile" y dé una solución efectiva a un problema.
Sin embargo, los seres humanos no somos máquinas, y necesitamos unas pequeñas dosis de entretenimiento.

Si no, nos ofuscamos y empezamos a hacer todo mal, y pasaremos horas trabados en algún paso estúpido, en algo que no vemos por mero cansancio.
O sea que ahora hay que recobrar la dimensión del entretimiento, darle su justo valor, y dedicarse un poco a los deportes, los juegos, la farándula, etc. Y esto como parte de todo el proceso de ahorrar tiempo, aunque parezca que no.

Es preferible tener 8 horas de trabajo intercaladas con 2 horas de entretenimiento, que no 12 horas seguidas de trabajo, porque se obtendrán mejores resultados de la 1era forma, y más rápido, a pesar de que los números aparenten lo contrario.

O sea que ahora, para que las cosas FUNCIONEN estamos considerando los factores "biológicos" y "psicológicos" de nosotros mismos.

____________________________________

Si ni en los foros de internet ni en las listas de correo hallamos soluciones, son posibles otros caminos.

Podemos formar grupos de intereses comunes en las redes sociales de Internet, como Facebook o Google+.
Se pueden realizar preguntas en páginas como Yahoo respuestas, aunque es muy probable que allí la gente conteste estupideces. Aún así se puede probar suerte.

Páginas como Facebook o Yahoo respuestas no ofrecen una plataforma adecuada para comunicar ideas técnicas complejas. No se pueden escribir fórmulas en LaTeX, no se puede formatear texto (que yo sepa).
En cambio en Facebook al menos se pueden incrustar imágenes y videos muy fácilmente.

Todo tiene sus pros y sus contras.

La creación o ingreso a una comunidad virtual en Facebook requiere otra predisposición que para un foro.
Es común que en Facebook la gente nos haga perder tiempo con impresiones personales, yéndose por las ramas fuera de tema, haciendo chistes, y demás "cositas del face".  :D

Pero si tenemos suerte de dar con gente bien enfocada, es posible generar una experiencia de intercambio interesante.
Allí puede que haya expertos que contesten preguntas (los Administradores del grupo), pero es muy posible también que eso no sea lo que prevalezca, y que tengamos que mentalizarnos en una "ayuda recíproca" como modo de acción, es decir, compartiendo información entre todos.

Una herramienta muy poderosa de comunicación es el chat, aunque casi siempre sólo se usa para conseguir parejas esporádicas por Internet.
Antiguos servicios de chat como el ÏRC, eran (y son todavía) algo así como 'listas de correo' en tiempo real, en que la gente puede intercambiar opiniones de temas técnicos, como por ejemplo, el diseño de una librería en C que permita hacer figuras geométricas.

Los sistemas de chat más modernos como MSN Messenger o el de Yahoo, disponen de opciones para introducir "emoticones", "fotografías" y "videos".
Aunque esta manera de comunicarse instantánea es algo muy bueno, la fama de Facebook ha diluido un poco el uso del chat.
No obstante, es claro que siempre se seguirán utilizando, y ahora MSN evolucionará a Skype definitivamente, que es chat con video.
Esta es una comunicación directa ideal, el poder hablar y ver a la persona con la que uno conversa, es superior y más barato que el uso de llamadas telefónicas a larga distancia.

Por más moderno que esto sea, a veces puede que nos sintamos tímidos de comunicarnos así con desconocidos, y prefiramos conversar usando meros "caracteres", a la antigua.
Hay, pues diversas opciones, pero lo que quiero remarcar con esto es que pueden haber limitaciones psicológicas en donde la tecnología no muestra impedimento alguno.
Y todo es parte de la misma realidad.

Lo importante de sistemas como Skype es que, si superamos la timidez, podemos conversar en directo con varias personas. Son las "llamadas en conferencia". El ahorro de dinero en esto es muy grande.
También se ahorra tiempo, porque es más fácil explicar lo que queremos si usamos el tono de voz y los gestos adecuados. La conversación con voz y audio es siempre más eficiente.

Entonces puede que tengamos suerte de que consigamos más rápidamente lo que queremos: la dichosa librería que hace figuras goemétricas en C.

En toda conversación entran los egos y temperamentos en juego, y esto producirá retrasos, ya que hay gente que querrá ser el centro de atención, o querrá hablar primero, etc.

En cualquier caso, esto se aleja de nuestro objetivo de realizar un simple programita en C, al punto que dan ganas de ponerse a llorar.  :'( :'(
¿Todo esto para dibujar un triangulito en mi computadora?  ::)

____________________________________________

Hay otros obstáculos a sortear, y tienen que ver la documentación y el conocimiento.
A veces no nos alcanza una mera respuesta, sino que necesitamos que "alguien" nos dé "algo".
Para eso hay que encontrar las personas que dan "ese algo", y hallar la manera más adecuada de "pedirlo".
Supongamos que queremos un libro en formato PDF que no se consigue en ninguna parte, pero que el autor eligió regalarlo a quienes se suscriban a su blog, y se lo pidan amablemente.

Entonces hay que registrar una cuenta en el servidor que aloja el blog,
después afiliarse al blog del tipo, esperar a que te acepta la afiliación,
finalmente dirigirle un mensaje privado, o postear algo en una de sus entradas, haciendo el pedido del bendito PDF.
Después esperar a que el tipo tenga ganas de responder, etc.

En otras ocasiones hay gente que sube información a Wikipedia, en forma voluntaria, pero que se demora un cierto tiempo.
Uno puede hacer el intento de entrar a la zona de debate de Wikipedia, y hacer una pregunta ahí, a ver si alguien responde, y de paso lo agregan a la entrada de Wikipedia del tema que nos interesa.

Les dejo como ejercicio pensar otras situaciones.
__________________

Pero hay una dimensión más de la interacción con gente en Internet.

Supongamos que tenemos todo un entorno de programación perfectamente instalado en nuestra computadora, pero que detectamos que no funciona como esperamos, o nos da resultados "erróneos".

¿Qué hacemos?

Lo primero es revisar nuestro programa, a ver si no hemos metido la pata.
Una vez corregidos los posibles errores de programación, hay que estudiar las especificaciones técnicas del lenguaje, que por lo general estarán escritas en un documento, que contiene "el estándar".

Si este estándar no existe, hay que buscar un sustituto, averiguar por Internet a ver qué es lo más parecido a un "estándar" que se acepta para un cierto lenguaje, o una librería externa asociada a él.

Si usamos una versión específica de un lenguaje, diseñada por un grupo empresarial específico, tendremos que conseguir un manual, tutorial o información específica de esa versión.

Si las especificaciones técnicas del estándar no nos aclaran la fuente del error, y parece estar todo correcto, entonces hay que fijarse en la documentación de nuestro compilador particular (GCC), y ver qué opciones de compilación existen. Puede que haya alguna que nos dé el resultado que deseamos, respecto al comportamiento general esperado para nuestro programa ejecutable.

Lo que hacemos en este curso es configurar el compilador para que funcione según la normativa estándar C99, por ejemplo, y que además descarte aquellos aspectos de GCC que no son estrictamente soportados por el estándar.

Si todavía las cosas no funcionan bien, quiere decir que, muy posiblemente, exista un error de diseño en el compilador.

En este caso, podríamos cruzarnos de brazos y decir "esto no tiene más remedio".  
Si el compilador funciona mal, pareciera que nada podemos hacer. :'(

Una opción improbable sería ponernos nosotros mismos a reprogramar el compilador, o hacer otro nuevo directamente, y que funcione bien... Pero descartemos este enfoque impráctico.
Otra opción es reclamarle a los diseñadores del compilador que arreglen el error.

Aquí de nuevo tenemos que interactuar con gente.
Si los diseñadores son competentes, tendremos suerte y escucharán nuestro pedido.
Si no, puede que obtengamos respuestas soberbias y equivocadas.

En el caso del compilador GCC las personas involucradas en su desarrollo son bastante amables, aunque están enfocados estrictamente a su tarea, y no conversan demasiado.
Uno va a la página de desarrollo de GCC, reporta un error, y ellos verifican si de verdad es culpa de ellos o no.
Si no, rechazan el pedido, y te dicen básicamente que "te las arregles por tu cuenta" con tu error.

Esto puede ocurrir cuando tenemos un problema en nuestra computadora (el hardware anda mal), o en nuestro sistema operativo, o bien el problema está relacionado con el compilador, pero no es culpa de GCC.

Recordemos que tenemos más o menos unas 3 capas de herramientas de compilación:

* El compilador (GCC).
* La biblioteca o librerías (MinGW).
* Un IDE o entorno integrado de desarrollo (wxDev-C++).

Estas 3 cosas son bien distintas, las desarrollan grupos de personas diferentes, incluso de páginas webs separadas, y además se pretende que las 3 funcionen juntas, armoniosamente.

Si todo eso funciona en nuestra computadora sin problemas, es casi como un milagro.
Pero cuando algo no funciona, se nos complica la vida, pues tenemos que discernir a quién echarle la culpa, y a quién reclamar.

Una vez que descartamos que los idiotas no somos nosotros mismos, ni Bill Gates,
entonces podemos intentar ver si el error está en otra parte.

Los diseñadores del compilador se ocupan sólo de aspectos sintácticos, traducción del código fuente a ejecutable, y de los mensajes de error o advertencias que arroja el compilador.
Consultas o reclamos de eso, se hacen a la gente de GCC.

Cuando lo que no funciona tiene que ver funciones o datos definidos en las librerías, u otras cuestiones del manejo de los archivos proveídos por la implementación, esto es culpa del equipo de MinGW.

Cuando en línea de comandos el compilador funciona correctamente, pero en el IDE tenemos problemas, las cosas no funcionan, se nos cierran ventanas sin explicación, las opciones de configuración que elegimos no tienen efecto, etc., esto empieza a ser culpa de la gente de wxDev-C++.

Y la única manera de que nuestro programa compile correctamente es que estos tres grupos de personas se dediquen, cada uno de ellos, a arreglar lo que ellos mismos hicieron mal.  :-\

No podemos esperar que mágicamente lo arreglen, porque a pesar de que hay mucha gente en el mundo, que se encuentra con problemas similares que los nuestros, es muy probable que seamos nosotros los primeros en tener un determinado problema con el compilador, las librerías o el IDE.
No habrá más remedio que perder la timidez, registrarse en las páginas webs de esos extraños seres de la informática, prestar atención al modo en que se deben escribir los formularios de informe de errores (llamados bugs), y decir lo que tenemos que decir: "tal o cual cosa NO FUNCIONA".

En estos casos el modo adecuado de hacerlo es en idioma inglés, y además hay que ser lo más conciso y claro posible. Si vamos a reportar un error que obtuvimos en un programa, el ejemplo que pongamos tiene que ser bien sencillo, de ser posible, para que sea bien clara la fuente del error.

Si nos ponemos muy verborrágicos, lo más probable es que no se entienda, y luego les saltará a algunos el "gusanito del orgullo informático", e intentarán hacernos creer que la culpa es nuestra, con una condescendencia un poco disimulada.

En estos casos hay que buscar la manera de insistir, pero al mismo tiempo sin molestar demasiado, porque se enojan enseguida con la gente que se vuelve repetitiva e insistente.
Hay que tener un tacto especial para opinar en estos sitios webs, sobretodo recordando el contexto: la mayoría de los que están allí son expertos, y no les sobra al tiempo. Hay que ir al grano, sobretodo porque los problemas de diseño con los que ellos tienen que lidiar son muy complejos.

Si nosotros vemos que nuestro compilador nos arroja un mensaje equivocado y de apariencia tonta, puede que para los ingenieros que deban arreglarlo sea un dolor de cabeza.
El motivo es que un compilador genérico como GCC, está orientado a funcionar en cientos de máquinas de arquitectura diferente, y también en la mayoría de los sistemas operativos conocidos.
Arreglar un "bug" implica arreglarlo para todas esas configuraciones posibles.
Requiere un diseño cuidadoso.

Además, es un trabajo comunitario, con lo cual varias personas contribuyen a un proyecto global.

[cerrar]



Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)
Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 31. ¿Cómo lograr que todo funcione? (Parte II)
Publicado por: argentinator en 20 Febrero, 2013, 06:16 pm
31. ¿Cómo lograr que todo funcione? (Parte II)

Voy a terminar la exposición anterior contándoles una breve experiencia personal.

(Lectura opcional)

Como ustedes sabrán, estuve luchando varias semanas con los números de punto flotante, a fin de poder explicarlos correcta y completamente en este curso.

(1) Lo primero que tuve que hacer fue estudiar todos los detalles de los datos en "punto flotante" que aparecen en el documento del estándar C99.

(2) Además, tuve que tener en cuenta la enorme experiencia que Derek Jones demuestra en su texto "The New C Standard", accesible en el primer post de este curso.

(3) Tras conjugar esos dos factores, advertí que tenía que estudiar otro estándar aparte: el ISO/IEC/IEEE 60559, relativo a la representación de números de punto flotante.
Hay allí varias cosas que surgen de la experiencia de los ingenieros e informáticos a lo largo de las décadas, de las cuales yo no estoy del todo enterado.

(4) Tuve que elegir qué cosas explicar, y qué cosas no, con sumo cuidado, para mantenerme en un esquema concreto, y todavía entendible.

(5) Entre las cosas que tuve que descartar está un documento importante, un artículo que contiene la teoría de "todo lo que un informático debe saber sobre los números de punto flotante", que contiene resultados matemáticos de mucho interés. Este tema lo estudiaremos más adelante.

(6) A fin de hacer experimentos con los bits de un dato de punto flotante, ingresé a un par de páginas webs, que permitían jugar con las representaciones binarias de dichos números.

(7) Tuve que tener en cuenta las especificaciones de GCC en lo concerniente a si adhieren o no a la normativa IEEE para los números de punto flotante. También, ver con cuidado qué es lo que supuestamente hace el compilador con esta cuestión, y qué es lo que aún no está resuelto o está incompleto.

(8) Al final pude diseñar finalmente los programas de testeo, y obtuve algunos comportamientos que no eran los que yo esperaba, según lo que interpreto de las normativas del estándar.

(9) Habiéndome asegurado de que yo no cometí errores de programación, y tras vacilar un poco, hice el intento de comunicarme con el señor Schmidt, creador de la página http://www.h-schmidt.net/FloatConverter/IEEE754.html (http://www.h-schmidt.net/FloatConverter/IEEE754.html), que tiene el programita para jugar online con números punto flotante en binario.  
Si bien este señor pone su email a disposición para que todo el mundo le pueda preguntar cualquier cosa,
y si bien estoy tratando de convencerlos a ustedes de que no sean tímidos y dialoguen con la gente en Intenet, aún así vacilé un poco, porque se trata de una persona que no me conoce, es alguien serio y que se ve que tiene cierto grado de experto en lo suyo.
Si yo le pregunto una estupidez tras otra, no me va a responder nunca más cosa alguna.

No es lo mismo enviar un email, que postear una pregunta en un foro, pues el email es algo más personal.
En ese caso, tomé todas las precauciones: primero me aseguré de que no podía resolver la duda por mí mismo, ni con libros o información en Internet; luego busqué en Google estilos de redacción de correspondencias formales en inglés, hice una breve presentación de mi persona, intenté explicar el problema brevemente pero con claridad, y en un lenguaje simple, pero correcto (siempre en inglés).
Además, me convencí a mí mismo de que la pregunta que le estaba haciendo tenía un nivel técnico que realmente valía la pena hacérsela a un experto, en vez de, por ejemplo, a mi vecina.

Este señor me respondió en forma amable y escueta, y dijo que él no podía ayudarme con eso, porque no sabía sobre el tema.

O sea que la comunicación salió bien, pero no obtuve soluciones.

(10) Intenté luego informar en la lista de correos de GCC, destinada a informar errores del compilador (bugs). En principio no entendía muy bien cómo venía la mano. Hasta que entendí que el método consistía en enviar un correo a una cierta dirección de email.
El correo que envié lo rebotaron un par de veces hasta que logré encontrar la opción de la página de outlook que envía emails sólo "sólo texto plano".
Al poco tiempo tuve una respuesta, pero mi pregunta fue derivada a la sección de consultas sobre GCC, y no fue considerada como el "reporte de un bug del compilador".  :(
Básicamente me culparon a mí de que tenía mal configurado el compilador.  :-\

El señor que me respondió fue muy amable en indicarme todos los detalles de lo que tenía que hacer, y qué es lo que estaba pasando.
Esto me demostró que yo, por ejemplo, no estaba a la altura de los desarrolladores de GCC. Ellos saben muy al detalle las especificaciones del estándar, y tienen mucha experiencia con compiladores y máquinas.
Yo, en cambio, tengo dudas del alcance de la normativa en casos específicos, y entonces no sabía si estaba frente a un error de concepto mío, o ante un bug del compilador.  :-[

En el primer caso, el error es mio, y en el segundo caso, es de ellos.
Pero el caso es que nunca lo hubiera comprendido del todo si no hubiera intentado comunicarme, hacer el experimento de "meterme por ahí" y ver qué pasa.

A pesar de que esa lista de correo está abierta a todo el público,
igual tomé muchos recaudos para tratar de no preguntar estupideces.
En la parte de "ayuda de GCC" quizá no sea necesario ser tan tímido,
pero en cambio en la parte de "reportar bugs" creo que hay que estar más a tono con el nivel técnico de la gente que está ahí.

Me refiero a que es muy posible que si creemos encontrar un "bug" del compilador, en realidad no lo sea,
ya que el problema puede ser sencillamente que no conocemos el lenguaje y/o nuestro compilador a fondo.
Y es en ese sentido que hay que ser precavido, y estudiar el caso con cuidado por cuenta propia, hasta que se vuelva imperioso plantear el tema directamente a los expertos.

(11) Otra cosa que hice fue meterme en un foro de informática, que es parecido a éste, en el sentido de que uno puede preguntar cosas sin temor de quedar como ignorante, porque hay distintos niveles de conocimiento en los usuarios, y siempre hay otros que saben más que uno. Tuve respuesta amable de un informático. Se notaba que él sabía más que yo sobre el funcionamiento interno de las computadoras y otras cuestiones, pero sobre el tema específico que yo estaba discutiendo, no estaba tan informado como yo.
Así que no obtuve una solución satisfactoria por este camino.

(12) Finalmente hice el intento de ingresar al sitio de GCC, para reportar "bugs", pero ahora de un modo más eficaz que el de la lista de correo.
Creé una cuenta en GCC para esto (tuve que hacerlo), y reporté un bug muy específico del compilador, tal que era posible verlo escribiendo una sola línea de código, sin librerías ni nada.

Expuse el caso, y la primer respuesta que obtuve fue la de un tipo al que "todo le funcionaba maravillosamente bien" en todos sus sistemas y computadoras (que se ve que tiene más de una).  >:(
Me dio a entender que yo tenía una versión anticuada del compilador...

Sin perder tiempo, busqué inmediatamente en la página de MinGW una versión actualizada del compilador.
¿Por qué en MinGW y no directamente en GCC?
Porque a los paquetes de GCC no los entiendo.
En cambio MinGW tiene todo integrado: compilador + librerías, y es lo que necesitamos para que todo funcione correctamente.

Descargué la última versión de GCC, la 4.7.2, que viene con la última distribución actualizada de MinGW.

Para eso tuve que invertir un cierto tiempo en aprender qué y cómo tenía que descargar, dónde ponerlo en mi computadora, y luego cómo reconfigurar el IDE de wxDev-C++ para que todo encaje armoniosamente, hacer algunas pruebas, y ver que FUNCIONA. Uufff, menos mal.

Entonces volví a compilar mi programita, y obtuve el mismo mensaje equivocado del compilador.
Yo estaba seguro que iba a obtener ese mismo comportamiento, pero necesitaba opinar en la web de GCC con "las pruebas en la mano" para que me hagan caso.
Además, advertí que el usuario que me respondió, había pasado por alto las opciones de compilación que yo indiqué.
Tras presentar estos datos, y diciendo con más claridad las opciones que yo usé, este usuario se rectificó y comprobó que él tenía el mismo problema.
Otros usuarios intervinieron, y estuvieron de acuerdo que es un bug del compilador.

Una vez que esto se ha consensuado, el "bug" queda en una lista de espera hasta que aparezca algún voluntario en la comunidad para resolverlo.

Ese alguien podría ser yo mismo, pero al día de hoy no me siento capacitado para inmiscuirme en el desarrollo del compilador GCC.

Entonces lo que debo hacer ahora es esperar a que en una próxima versión alguien haya corregido ese "bug", y así el problema no se presente.

(13) Dado que había tenido respuesta favorable a este reporte del "bug", subí la apuesta ;)
y en ese mismo lugar aproveché para relacionarlo con otro problema que yo consideraba de la misma índole u origen.
Pero entonces me retaron y me dijeron que ahí no iba esa pregunta.  :-[
Me sentí desconcertado, porque no sabía en qué sección, o en qué página de internet, o en qué edificio de la ONU, es que tenía que ir a hacer el reclamo o pregunta.  ??? ??? ???

(14) Sólo por persistencia, ingresé en otra sección de GCC, que parecía más acorde a la pregunta que yo había hecho, a ver cómo me iba. Me disculpé de antemano por si la pregunta no iba en ese lugar (normas de etiqueta), y expliqué el problema lo más clara y escuetamente posible. Un usuario muy amablemente me respondió que a la gente de GCC eso no le concierne, y que tenía que dirigirme al grupo de desarrollo de las librerías de MinGW.
En tal caso, por cortesía, me disculpé y le agradecí.

La cortesía y la diplomacia son importantes para no ser expulsados de esos lugares, sólo por un tonto malentendido, o por estar medio desubicado.
Tras estos intentos, medio acertados y medio equivocados, fui aprendiendo a qué lugar tenía que dirigirme para resolver tal o cual cuestión.

En ocasiones, no sólo hay que consolarse con eso de que "del error se aprende", sino más aún "a veces el error es el único camino posible para avanzar".

(15) Ahora ya tuve más claro adónde tenía que ir. Busqué el equipo de trabajo de MinGW, ya que mi reporte era ahora sobre una cuestión de las librerías de punto flotante de GCC.
El proyecto MinGW está alojado en SourceForge.net, y tuve que registrar una cuenta en ese sito web, lo cual es necesario para reportar "bugs".

A continuación hay que fijarse con cuidado cuál es la metodología adecuada para reportar un "bug". Hay que hacerlo de una manera apropiada, sobretodo breve, y con los formularios específicos.
Tras estos cuidados, volví a hacer la misma pregunta que nadie me respondió en GCC, a ver si ahora tenía más suerte.

Se originaron problemas en el mensaje que escribí, los cuales yo no entiendo porque es la primera vez que escribía en esa web.
Un usuario, quizá un administrador, muy amablemente corrigió mi mensaje.
Luego tuve que esperar a que alguien me responda, a ver si se trata realmente de un "bug" de las librerías de GCC que implementa MinGW, o si yo tengo un error de concepto o interpretación del estándar C99, y tengo la suerte de que alguien me lo explique  ;D con amabilidad.

Una forma astuta de hacer una pregunta teórica es pues, reportar un "bug" del compilador, jejeje.  >:D

Pero para hacer una "jugada" como esa, tuve que estar muy seguro antes de que realmente el problema estaba en el límite de ambigüedad entre lo conceptual y los bugs de implementación.
Se trata de un detalle muy quisquilloso de los números de punto flotante, a saber, las macros de clasificación.
De hecho, casi que me convencí de que era un "bug" cuando investigué internamente cómo estaba hecha la macro (con las famosas DEB que les enseñe a hacer), y parecía que intentaba lograr algo que en el programa compilado no aparece. Lo mismo parecen sugerir los textos técnicos.

Seguramente la respuesta obtenida será breve, pensé.
Existía la posibilidad de que me manden al diablo, diciendo que eso no va ahí, y entonces tendría yo que seguir buceando y preguntando en Internet, a ver quién me da la solución...

Por suerte eso no pasó, y tuve una respuesta favorable.
En este mundo de cuestiones técnicas, "respuesta favorable" sólo significa que alguien puso una lista de especificaciones técnicas que en resumen significa esto: "Hay que investigar qué pasa con tal o cual función, queda pendiente de revisión, la tarea se asigna a tal o cual experto, se intentará solucionar para tal o cual número de versión".

No hubo mucha más discusión ni comentarios que eso, casi como un idioma de robots.


(16) En un momento dado pensé en escribirle un email a Derek Jones, que seguramente es capaz de aclararme las dudas respecto al lenguaje C y los tipos de punto flotante. Pero no lo hice, pues quise resolver las cosas de otro modo, y quizá hacerle una consulta más interesante sobre el tema, un poco más adelante.

______________________________

Al final de todo este proceso, no sólo fui aprendiendo a interactuar con la gente en internet,
sino que además ahora existe la posibilidad de que el problema del compilador se solucione,
gracias a que me animé a reportar estos problemas, incluso haciéndolo de forma algo equivocada al principio.  ;)

Aprovechando que existen estas comunidades de programadores que invierten mucho esfuerzo en dejar las cosas a punto, puede ser que en unos pocos meses el problema esté solucionado.

A mí sólo me queda "esperar".  :D

Visto así, pareciera que actúo en forma egoísta, usando a la gente, pero creo yo que tenemos que acostumbrarnos al modo moderno de hacer las cosas.
Siempre hay gente que sabe más que uno, y hay que delegarles los problemas o las responsabilidades.

En algún momento, cuando uno ya se ha vuelto experto, será quien le solucione la vida a otros.

O bien, en lo inmediato, que ellos vengan acá a preguntarme algo de matemática, y les retribuyo de esa manera el esfuerzo que ellos hacen por mí en el costado informático del mundo.

_________________________________

Lo que quiero mostrar es que no hay que tener prejuicios a la hora de lograr que un programa funcione.
No siempre la solución está "en la máquina" o "en el lenguaje C".
Hay que aprender otras habilidades, buscar información de diversas fuentes, insertarse en círculos sociales que nos resultan extraños, intentar comunicarse, aprender inglés, etc.

La única condición para salir a abordar terrenos y personas desconocidas es tomar antes algunas precauciones, informarse bien de las normas de etiqueta esperadas, a fin de que los demás tomen en serio nuestra búsqueda o reclamo. Una vez superado ese escollo, hay que meter el dedito en la tecla, y animarse.

Siendo así, para dentro de unos meses seguramente tendremos al menos una leve mejora en el manejo de números de punto flotante, y les informaré debidamente de cómo instalar la nueva versión, tanto del compilador GCC como con el entorno MinGW, en armonía con nuestro IDE wxDev-C++.

 :)
[cerrar]



Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)
Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: ...
Publicado por: argentinator en 28 Agosto, 2013, 09:43 pm
...
Título: Programación en C: 33. Caracteres en el lenguaje C (I)
Publicado por: argentinator en 30 Agosto, 2013, 07:03 am
33. Caracteres en el lenguaje C (I)

   He tardado mucho desde el último post. No crean que es porque he olvidado este proyecto, sino todo lo contrario:
   El tema que nos ocupa ahora, el de los caracteres en C, es tan vasto y complejo, que no he tenido más remedio que invertir un largo tiempo investigando hasta al menos llegar a la orilla de la real comprensión del asunto.

   El lenguaje C en su versión estándar C99 tiene la obligación de considerarse en medio de una etapa histórica de transición en el mundo informático, en la cual los sistemas de codificación de caracteres tienen muchas variantes y estándares distintos, todos conviviendo juntos, y con la esperanza de poder migrar todos ellos a un único y uniforme estándar: el Unicode.
   Si bien todos los lenguajes de programación y todos los sistemas informáticos están orientados en esa dirección migratoria, el cambio definitivo a Unicode tardará todavía varios años más, debido a que está involucrado un gran costo económico, ya sea en el cambio de los equipos, la reescritura de antiguas bibliotecas de software, documentos antiguos guardados en una codificación previa a Unicode, páginas web y protocolos anticuados que todavía son útiles por diversas razones, etc.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   El estándar C99 tiene en cuenta todas las variantes posibles, y más aún, tiene presente la distinción entre los caracteres de un programa fuente y los caracteres que perviven en el entorno donde el programa compilado habrá de ejecutarse.
   Es fácil imaginar situaciones en que una biblioteca de funciones se escriben bajo un determinado entorno, con caracteres codificados de cierto modo, y que luego el programa compilado tenga que llevarse o correrse en un entorno de computación distinto.
   Ya podemos vislumbrar en esto una primer fuente de sufrimiento, pues en general los programadores tienden a creer que los mismos caracteres a los que pueden acceder mientras están escribiendo un programa en C, son los mismos caracteres que podrán utilizarse luego mientras el programa esté compilado y ejecutándose.

Malas noticias: eso no es cierto.    :-\

   Las especificaciones del estándar C99 son tan amplias y abiertas a tantas posibilidades que sólo pueden producir un enorme desconcierto.
   En particular, es muy difícil que una implementación específica (compilador + librerías) sea capaz de seguir al pie de la letra todas las consideraciones del estándar.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Para llegar a comprender las consideraciones y peculiaridades del estándar en torno a los caracteres, es necesario tener un cierto conocimiento de la historia y detalles de las diversas codificaciones de caracteres que ha habido en el mundillo informático.
   Daré aquí una rapidísima reseña de esa historia:

(1844) En los albores de la telegrafía surgió el código Morse.
(1870) Luego la telegrafía utilizó el código de Baudot, estandarizado como ITA1: codifica caracteres mediante 5 bits.
(1930) El código de Baudot evolucionó gracias a la empresa Western Union a una nueva versión, estandarizada como ITA2.
(1956) La milicia estadounidense inventó el código FIELDATA, codificando caracteres en un sistema de 7 bits.

(1960) IBM lanza EBCDIC, un código de 8 bits basado en su antiguo sistema de tarjetas perforadas.
(1963) Apareció la primera versión de ASCII: caracteres para escritura en inglés de Estados Unidos, en formato de 7 bits.
(1967) Nueva versión de ASCII, ahora conteniendo letras minúsculas, y cambios en caracteres de control.
(1968) Estandarización internacional de ASCII mediante norma ANSI.
(1972) Creación del estándar ISO 646, que es como ASCII excepto por unos pocos signos de puntuación, que se dejan sin especificar, para ser aprovechados por las necesidades locales de cada nación.
(1973) Aparición de la norma ISO/IEC 2022, un sistema de codificación multibyte que permite usar ASCII junto con caracteres de control que dan acceso a subtablas de caracteres, permitiendo expresar símbolos de idiomas complejos como el Chino o el Japonés. Cada caracter puede estar expresado como 1 o más bytes (de 8 bits).
(1987) Aparición de las normas ISO/IEC 8859-X, de caracteres ASCII extendidos, cuyo formato es de 8 bits.
(1991) Nacimiento del consorcio Unicode, con el fin de representar todo caracter posible de todo alfabeto del mundo. Inicialmente se codificaba en 2 bytes, o 16 bits. Actualmente esto ha sido sobrepasado, por no ser suficiente.
(1993) Caracteres UCS, bajo la norma ISO/IEC 10646. En la actualidad equivale a Unicode.

   En la actualidad tenemos las codificaciones Unicode multibyte UTF8 (8 bits), UTF16 (16 bits) y la de ancho fijo UTF32 (32 bits).
   Se presume que esta última será suficiente para toda la historia futura de la humanidad.

   Conviven aún todos los sistemas desde EBCDIC en adelante, junto con multitud de variantes introducidas por fabricantes de equipos, sistemas operativos, etc.

   Para más detalles sobre esta historia, y sobre cómo funcionan los distintos métodos de cofidificación,
   los enlazo a mi artículo en la revista del foro sobre el respectivo tema:

Codificación de Caracteres (http://rinconmatematico.com/foros/index.php?topic=65991.msg264941#msg264941)

   En esencia, ASCII es la base de los sistemas de codificación más utilizados, y eso incluye a Unicode que es una extensión de ASCII.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

¿Qué son los caracteres multibyte?

   Por un lado tenemos los códigos, que son números binarios de 8 bits, y que ocupan por lo tanto 1 byte.
   Cada byte representa un cierto caracter de cierto conjunto de caracteres.
   Este conjunto puede ir variando a medida que vamos leyendo datos de algún archivo de texto.
   Se presume que el primer caracter leído está en un estado inicial, que corresponde al estándar ASCII.
   Luego, mediante secuencias de ESCAPE se indican cambios de estado (SHIFT), que permiten especificar grupos de caracteres para la parte baja de la tabla (GL) y para la parte alta (GR).
   En tal sentido, el "estado" es una propiedad del sistema que permite saber a qué caracter nos estamos refiriendo en determinado momento.

   En cierto modo, la computadora tiene que "llevar la cuenta" o "memorizar" cuál es el último estado seleccionado, para saber dónde estamos ubicados.
   Hay sin embargo algunos caracteres que no son afectados por el "estado": son los caracteres de control, y muy especialmente el caracter nulo (el que tiene todos los bits igual a 0).

   A veces, un estado determinado indica que el próximo caracter ha de especificarse ya sea con 1 byte o con 2 o más bytes consecutivos.
   En resumen, los sistemas multibyte trabajan mediante cambios de estado indicados mediante secuencias de ESCAPE, y puede que haya caracteres representados con 1 byte o 2 bytes. En la práctica no suelen aparecer caracteres de 3 bytes o más, aunque no hay restricciones teóricas en este sentido.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Mientras las codificaciones multibyte aprovechan un sistema de 1 byte para representar muchos más caracteres que los 256 de máximo que estarían normalmente disponibles, esto no deja de ser un mecanismo, un artificio que funciona con determinadas reglas.
   Por otra parte, una codificación de caracteres anchos es aquella que tiene una cantidad fija de bytes para cada caracter, y cuyo tamaño en memoria es normalmente mayor que 1 byte por caracter. Lo normal son 2 bytes, pero no hay restricciones a anchos mayores. De hecho, para el juego completo de caracteres Unicode, son necesarios 4 bytes.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 34. Caracteres en el lenguaje C (II)
Publicado por: argentinator en 07 Septiembre, 2013, 11:10 pm
34. Caracteres en el lenguaje C (II)

Entorno de traducción y entorno de ejecución.

   A partir de ahora tenemos que abrir nuestro cerebro partiéndolo en dos mitades.
   Cuando trabajamos con el lenguaje C tenemos que estar pensando siempre en dos sistemas de computación distintos: el 1er sistema es aquel en que escribimos nuestro programa y lo compilamos para crear un archivo ejecutable, mientras que el 2do sistema es aquel en donde dicho programa efectivamente correrá y hará de las suyas.
   Se llama entorno de traducción a aquel sistema en donde la edición y compilación del programa se realizan, y se llama entorno de ejecución a aquel sistema en donde el programa ejecutará realmente.
   Un ejemplo claro y sencillo: un amigo nuestro tiene una modernísima computadora Intel Core Duo, con sistema operativo Linux, y nos ha prometido hacernos un programa en C con un salido por el día del amigo que estará compilado de manera que no funcionará en su sistema, pero que sí funcionará en el nuestro: un Intel Celeron antiguo, con un desactualizado sistema Windows XP.
   Esto separa las herramientas de diseño de un programa, de las capacidades del sistema en que se ejecuta. Otros ejemplos más modernos serían estos: diseñar un jueguito que ejecutará en un teléfono con sistema operativo Android, pero que ha sido diseñado en una PC corriente con procesador Intel y sistema Windows 7.
   Las combinaciones son muchas, y cada vez más a medida que la informática moderna se diversifica.

   Se denomina archivo fuente al archivo de texto en el que escribimos nuestro programa en C.
   La versión compilada y lista para andar se llama archivo ejecutable.
   Los archivos fuente se diseñan o escriben en un determinado entorno de traducción, mientras que el ejecutable corre y realiza sus tareas en un determinado entorno de ejecución.

   Esta distinción nos puede parecer extraña, ya que toda la vida hemos escrito, compilado y corrido nuestros programas en una sola computadora: la que tenemos en casa, y punto.
   Pero a los fines teóricos, debemos tener siempre en cuenta este doble contexto para un programa en C.

   Cuando ambos entornos son diferentes en la práctica, se le suele llamar compilación cruzada (cross compiling).  :-* :-*

   ¿Por qué insistimos con todo este asunto? Bueno, es necesario ya que para definir exactamente cómo se entienden los caracteres en C, tenemos que distinguir en qué entorno estamos pensando: si el de traducción o si el de ejecución.
   Los conjuntos de caracteres tienen, en cada uno de esos casos, un nombre distinto, así como elementos potencialmente distintos.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Un caracter (abstracto) es, según el estándar C un miembro de un conjunto de elementos usado para la organización, control o representación de datos.

(Todos estos términos nos llevaría meses definirlos, así que nos conformamos con la versión intuitiva del asunto.)

Un caracter (byte solitario) es una representación en bits que cabe exactamente en un byte.

Una collating sequence (¿sucesión de clasificación?) es una relación definida entre objetos de un mismo conjunto con propósitos de ordenación. Es decir, se dice qué elementos preceden a cuáles otros.

\( \bullet \)   Se definen dos conjuntos de caracteres y sus sucesiones de clasificación/ordenación (collating sequences): el conjunto de los caracteres fuente (del entorno donde los archivos fuente se escriben), y el conjunto de los caracteres de ejecución (del entorno donde el programa se ejecuta).

   Nunca se termina de aclarar en el estándar cuáles son todos los caracteres que hay en cada uno de esos conjuntos. Pero sí se aclara: cuáles son los caracteres que sí o sí tienen que estar en cada uno de ellos, y cuáles son los caracteres que ambos conjuntos sí o sí tienen que compartir.

   Cada uno de ambos conjuntos viene con una relación de orden previamente definida, que sirve para saber cuál va primero y cuál después. Esto se usa, claro está, cuando hace falta ordenar datos.

   Los caracteres usados en la definición del lenguaje C existirán al mismo tiempo en ambos conjuntos de caracteres.
(Estos son unos pocos caracteres, que luego listaremos, y forman apenas un subconjunto de los caracteres ASCII. ¡Y no se requiere que haya más caracteres que estos en ninguno de los dos tipos de entornos (fuente y ejecución)!).

   Cada conjunto (de caracteres) se divide en:

\( \bullet \)    Conjunto de caracteres básicos
\( \bullet \)    Conjunto de caracteres extendidos.

   A la unión de ambos conjuntos se le llama conjunto extendido de caracteres. Esta terminología es totalmente confusa, por supuesto.  :banghead: :banghead:

   Los caracteres básicos siempre han de estar disponibles, tanto en el conjunto fuente (la traduccón exacta sería: conjunto de caracteres para los archivos fuente) como en el conjunto de ejecución.
   Mientras que caracteres extendidos, que son siempre distintos y adicionales respecto los básicos, puede haber varios o ninguno. (Se sobreentiende que son finitos, aunque el estándar no aclara este punto, seguramente porque sería pedante una tal aclaración).

   El estándar no especifica los valores binarios a los cuales corresponde cada caracter, ya sea del entorno fuente o del entorno de ejecución. Aunque se especifican algunas otras propiedades.
   Estos valores son dependientes del compilador utilizado.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Los caracteres (abstractos) que existen siempre tanto en los caracteres fuente como en los caracteres de ejecución, son los siguientes:

\( \bullet \)   Alfabéticos (mayúsculas): A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
\( \bullet \)   Alfabéticos (minúsculas): a b c d e f g h i j k l m n o p q r s t u v w x y z
\( \bullet \)   Dígitos decimales:     0 1 2 3 4 5 6 7 8 9
\( \bullet \)   Caracteres gráficos: ! " # % & ’ ( ) * + , - . / : ; < = > ? [ \ ] ^ _ { | } ~
\( \bullet \)   Blancos y de control: Espacio, Tab Horizontal, Tab Vertical, Avance de Formulario.

   En total estos son los 95 caracteres básicos en el entorno de traducción.
   En total son 96, pues se agrega el siguiente:

   Se supone que en el entorno de traducción (archivos fuente) hay alguna manera de indicar el final de una línea de texto. El estándar considera a "esto" como si fuese un único caracter fin de línea.

(Se podría decir que es un caracter básico más en el entorno de traducción ??? ).

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   El entorno de ejecución contiene a todos esos caracteres, y además los siguientes caracteres de control:

\( \bullet \)   Alerta, Retroceso, Retorno de Carro, Nueva Línea.
\( \bullet \)   También contiene el Caracter Nulo (ver abajo).

   En total hay 100 caracteres fijos en el entorno de ejecución.

   Estos nuevos caracteres no están, necesariamente, en el entorno de traducción.
   Por lo tanto, tienen que indicarse con secuencias de escape. (Ver luego).

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Trigrafos.

Los trigrafos son secuencias de exactamente 3 caracteres que han de reemplazarse por exactamente 1 caracter. Son los siguientes:

??=   #
??(   [
??/   \
??)   ]
??’   ^
??<   {
??!   |
??<   }
??-   ~


   Los trigrafos aparecen exclusivamente en el entorno de traducción, y así:

   El compilador analiza el archivo fuente (o sea, nuestro programa en C), y busca todas los trigrafos. Los reemplaza automáticamente por el caracter que se indica a la derecha.
   Después de esta operación comienzan las etapas de compilación propiamente dichas.
   Se observa que:

\( \bullet \)   Los trigrafos son caracteres del conjunto básico del entorno de traducción.
\( \bullet \)   Los trigrafos se reemplazan luego por otros caracteres que también son del básicos del entorno de traducción.
\( \bullet \)   El reemplazo se realiza sólo en el entorno de traducción, y nunca en el entorno de ejecución.
\( \bullet \)   El reemplazo no es opcional. Se realiza SIEMPRE. (En realidad hay sistemas donde los trigrafos se han desactivado por conveniencia, tal como en los compiladores para Apple Macintosh).
\( \bullet \)   Para evitar confusiones, debemos saber que el compilador primero reemplaza los trigrafos, y después analiza el código fuente resultante.
\( \bullet \)   Los trigrafos se utilizan para representar algunos caracteres de la norma ASCII (de 7 bits) que no están contemplados en la norma ISO/IEC 646.

   Así, si escribimos int float w??(20??); se transforma en int float w[20];.

   Los trigrafos se han inventado por la siguiente razón: El código ASCII no fue adoptado por todos los países con total exactitud, sino que hay posiciones de la tabla que han sido utilizados para otras necesidades locales (acentos, tildes, letras especiales, ligaduras, etc.).
   Eso hace que algunos caracteres necesarios en el lenguaje C no estén disponibles.

   Las posiciones en gris de la siguiente tabla (extraída de Wikipedia (http://en.wikipedia.org/wiki/ISO/IEC_646)) muestra en gris las posiciones usadas para variantes locales (sin embargo los trigrafos no cubren todos los casos, sino sólo los que se necesitan en C):

(https://foro.rinconmatematico.com/index.php?action=dlattach;topic=65991.0;attach=12334)

Importante: A algún diseñador de compiladores se le podría ocurrir la idea de seguir jugando con trigrafos o cosas similares. En realidad esto no está permitido por el estándar: los únicos trigrafos admitidos son los 9 ya listados.

   Este tema está relacionado específicamente a la norma ISO/IEC 646, cuyos detalles hemos discutido aquÍ.

ISO/IEC 646 (ECMA 6) (http://rinconmatematico.com/foros/index.php?topic=65991.msg275318#msg275318)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Existencia de caracteres multibyte

   La norma C99 establece que:

\( \bullet \)   El conjunto de caracteres fuente puede (léase: no es obligatorio, pero tiene permitido) contener caracteres multibyte usados para representar miembros del conjunto extendido de caracteres.

Importante: la norma C99 no especifica de qué modo son considerados los caracteres multibyte: puede que se decodifiquen transformándolos de alguna manera a los miembros del conjunto extendido de caracteres, o puede que queden sin transformar, etc. Esto es algo que concierne solamente a la implementación local utilizada.

   El resultado de esta norma tan incierta es que no podemos estar seguros de qué caracteres tenemos a mano o no para escribir un programa en C. ¿Esto es grave o trae algún problema? Respuesta: NO. En el entorno de traducción sólo tenemos los programas fuente, y allí no es estrictamente necesario tener disponibilidad o certeza alguna acerca de los caracteres multibyte.
   Estos caracteres podrían aparecer como parte de un identificador, o como parte de un comentario.
   Pero cuando compilamos el programa, la versión ejecutable no se preocupa en lo más mínimo de los caracteres que usamos para indicar identificadores (no quedan rastros de esto en el ejecutable), ni mucho menos los comentarios.

Los programas fuente son archivos de texto que residen en el entorno de traducción.

   Lo importante entonces es que el compilador sea claro acerca de qué caracteres nos permite usar, o mejor, que explícitamente podamos saber qué codificación de caracteres estamos usando para archivos de texto.

Pero esta información tenemos que buscarla externamente: en la documentación del compilador.

   Concretamente: Puede que en el entorno de traducción sólo estén los caracteres básicos, o puede que haya extendidos. Entre los extendidos, puede que algunos se generan mediante alguna codificación del tipo "multibyte", o puede que simplemente se tenga una codificación sencilla de 1 solo byte (como los formatos de ASCII extendidos), o puede que se use alguna codificación sencilla de 2 o más bytes, aprovechando Unicode.

   En el caso de caracteres multibyte, puede que no se haga traducción alguna a elementos concretos de un conjunto dado de caracteres, o puede que sí.
   Si queremos estos detalles, tenemos que indagar en la documentación del compilador que estemos utilizando.

\( \bullet \)   El conjunto de caracteres de ejecución también puede contener caracteres multibyte para representar miembros de su propio conjunto extendido de caracteres.
\( \bullet \)   En caso de haber caracteres multibyte tanto en el entorno de traducción como el de ejecución, no tienen por qué coincidir sus decodificaciones.

   Más aún: el conjunto de caracteres del entorno de traducción está siempre fijo para una implementación dada, pero en el entorno de ejecución, la decodificación de los caracteres multibyte podría cambiar veces en un mismo programa, dependiendo de si se cambian o no la configuración "local" (o sea, los detalles de un programa que dependen de cambios de idioma o nacionalidad).

   Nota adicional: Aquí existe una discusión de fondo acerca de los caracteres que pueden visualizarse exitosamente o no mientras el programador está escribiendo o leyendo un programa en C. Si un mismo programa se lee en entornos distintos, puede que haya caracteres que no se visualicen correctamente o no, según el entorno circundante.
   El estándar da certezas mínimas para que al menos los caracteres básicos sean visibles en cualquier caso.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Codificación: Se refiere al valor numérico que se asigna a un caracter.

En C99 podemos asegurar lo siguiente (tanto para el entorno de traducción como para el entorno de ejecución):

\( \bullet \)   La codificación de los caracteres básicos, caben siempre en 1 byte.
(Esto evita que haya implementaciones que sean, por ejemplo, totalmente multibyte).

\( \bullet \)   El tamaño (cantidad de bits) que ocupa 1 byte está dado por la macro CHAR_BIT. Se asegura que este valor será siempre \( \geq{8} \).

   La observación sutil que se desprende de esto es que hay caracteres que pueden tener una codificación mayor que \( 255 = 2^8-1 \)... siempre y cuando el sistema subyacente admita bytes con más de 8 bits.
   ¿Existen de verdad sistemas de computación en los que los bytes tienen más de 8 bits?
   . No voy a dar ejemplos por ahora.

   Aunque no se dice explícitamente, un hecho que podemos afirmar es que CHAR_BIT permanece constante a lo largo de todo el programa: un programa dado no ejecutará en una máquina donde los bytes tengan... ¡número de bits variable!
   Esto no lo dice el estándar... pero uno lo deduce a partir de las misteriosas leyes del sentido común.
   No existen en la actualidad sistemas de cómputo en los que la cantidad de bits vaya a variar mientras un programa ejecuta.

Sin embargo, tengo mis dudas. ¿Qué pasa si una aplicación se diseña para funcionar en una red de computadoras cuya arquitectura varía en cada terminal?  ::)
Pero esto no nos ha de preocupar, olviden que lo he mencionado.

* La presencia, significado y representación de los miembros adicionales concierne a la "configuración local" específica que hay en el programa ejecutable.

(Aunque no lo hayamos mencionado, en todo momento en un programa ejecutable hecho en C se supone que está activa una cierta "configuración local". Hay una por defecto, llamada "C", pero puede cambiar a lo largo del programa. Según la configuración existente es que cambia el modo en que se interpretan o manejan los caracteres adicionales.)

Los valores (numéricos binarios) de las codificaciones asociadas a los caracteres básicos (tanto del entorno de traducción como de ejecución) caben siempre en 1 byte, pero sus valores específicos son elegidos por el compilador.
Sin embargo, se puede asegurar que:

\( \bullet \)   Los valores de los caracteres básicos que representan dígitos son siempre consecutivos.
\( \bullet \)   Los valores de los caracteres básicos son enteros no negativos.
\( \bullet \)   Los valores de los caracteres básicos caen dentro del rango de valores del tipo char.

   En el conjunto de caracteres de ejecución existe un caracter cuyo valor es el entero 0, es tal que todos sus bits son 0, y se llama el caracter nulo.
   Como se puede apreciar, el caracter nulo no tiene la obligación de existir en el entorno de traducción (si lo pensamos un poco, tiene perfecto sentido...).

\( \bullet \)   Los caracteres multibyte funcionan acorde lo que se conoce como una máquina de estados finitos.  ??? ??? :o :o :o

   Es demasiada terminología para decir que, simplemente, algunos bytes se usan para codificar "estados" (se llaman SHIFTS), que permiten acceder, temporalmente, a otros caracteres que comparten una misma posición en una tabla dada.

   Una analogía en nuestro teclado sería esta: El estado de las teclas "normal" genera letras minúsculas, pero cuando activamos la tecla CAPS LOCK, las mismas teclas generan ahora otros caracteres: letras mayúsculas, hasta que volvemos a presionar CAPS LOCK, y el "estado" cambia de nuevo a "letras minúsculas".

   El CAPS LOCK es un ejemplo de un "estado" que dura hasta que se indique lo contrario.
   Es un "SHIFT permanente".
   Es posible que haya algún "SHIFT temporal", tal que el cambio de estado se aplica sólo al siguiente caracter. (Esto ocurre más o menos cuando presionamos la tecla de la tilde ' seguida de una vocal, que termina generando una volcal acentuada: á).

   Lenguajes más complicados como el japonés o el chino requieren variantes más intrincadas de "cambios de estado". Ahora bien, supongamos que el caracter de escape usado para indicar CAPS LOCK hubiese sido por ejemplo "@" (todo esto es un ejemplo inventado por razones pedagógicas...):

   La cadena "xyz @abc @huv ~nn" resulta en lo siguiente: "xyz ABC huv ñn".

   Algo similar ocurre con los caracteres multibyte "de verdad", salvo que los SHIFTS se especifican mediante un conjunto de reglas especificado localmente, en cada sistema, y no podemos aquí enumerar y ejemplificar todos los casos posibles.

   Es complicado programar caracteres multibyte, porque dada una secuencia de caracteres, el significado de un caracter depende, en un momento dado, del "estado" en que se encuentre la secuencia, o sea, hay que tener la información concreta de toda la historia de SHIFTs que se han utilizado desde el principio de la secuencia.

   En nuestro ejemplo inventado del CAPS LOCK, si en un momento dado tenemos una secuencia de caracteres tal que aparece una b, ¿qué significa? b ó B. Eso depende de que el estado CAPS LOCK esté activado o no, y dicha información no está en el caracter mismo, que siempre es b, sino que proviene de la historia de todos los caracteres leídos antes.
   Muchas veces esto obliga a leer textos de principio a fin para realizar correctamente operaciones de cómputo que, de otro modo, serían mucho más sencillas.

   Lo que estamos diciendo es que un caracter multibyte se codifica usando a su vez otros caracteres (de 1 solo byte) y SHIFTs. Puede que haga falta usar más de 1 byte para generar un caracter multibyte deseado.
   ¿Cuántos? No se puede saber de entrada.
   Pero el estándar C99 establece que ha de haber un máximo de bytes en el entorno de ejecución que será suficiente para albergar uno de estos caracteres multibyte.
   Podemos obtener la información de dicho máximo consultando el valor de la macro MB_LEN_MAX.
   Más todavía: no importa si se cambia la configuración local varias veces en el programa. El valor máximo alojado en esa macro es el máximo que el compilador soporta para toda configuración local posible.
   Este límite superior ayuda también a poner una cota a los posibles SHIFTs redundantes que pudieran existir o aparecer.

   Para el lector interesado, un caso de estudio importante puede ser la norma ISO/IEC 2022, que estandariza métodos de codificación para caracteres multibyte (aunque las codificaciones propiamente dichas se especifican en otras normas).

http://en.wikipedia.org/wiki/ISO/IEC_2022 (http://en.wikipedia.org/wiki/ISO/IEC_2022)

   Lo más probable es que nunca tengamos que lidiar con estos problemas de los caracteres multibyte.
   De hecho, con el advenimiento de Unicode (un sistema simple, uniforme, universal), las complicadas codificaciones multibyte han de caer en desuso.
   Sin embargo, tenemos que hablar de esto porque aún existe este tema en la programación de hoy en día, y además hay reglas importantes asociadas a los caracteres multibyte, por ejemplo esta:

\( \bullet \)   En una secuencia de caracteres multibyte, aquellos bytes cuyos bits son todos nulos corresponden siempre al caracter nulo, y no interesa aquí en qué estado (SHIFT) se encuentre
\( \bullet \)   En el entorno de ejecución, el caracter nulo no puede formar parte en forma parcial de un caracter multibyte, ni estar afectado a SHIFT alguno. Sólo puede haber caracteres nulos al final de una secuencia completa que conforme un caracter multibyte.

   En un programa en C, podemos contar conque una secuencia multibyte no terminada, al final de una cadena de caracteres, no tendrá efecto alguno. (Revisar).

En realidad, siempre se supone que hay un SHIFT activado: es el denomiando "estado inicial".

   Se supone que un archivo fuente siempre empieza y termina en su estado inicial.
   Y que una secuencia de caracteres de un programa ejecutable siempre comienza en dicho "estado inicial".
   El caracter nulo vive, pues, en este "estado inicial".

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Soporte para caracteres multibyte en distintos compiladores:

   No puede asegurarse que todos los compiladores manejen por igual los caracteres no básicos.
   En particular puede que algunos de ellos indiquen errores en donde otros compiladores consideren que se trata de código válido.

\( \bullet \)   Se exige que todo identificador, comentario, constante literal, en un programa fuente, comience y termine en "estado inicial" como una secuencia de caracteres multibyte válida.  ::)

   Lo triste aquí es que cada compilador puede elegir lo que considera una secuencia válida de caracteres multibyte.

   Como consecuencia de esto, un mismo programa puede que no compile en distintos entornos de traducción.
   Esto obviamente trae problemas de compatibilidad/portabilidad.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 35. Caracteres en el lenguaje C (III)
Publicado por: argentinator en 08 Septiembre, 2013, 07:58 am
35. Caracteres en el lenguaje C (III): Constantes de caracter

(To foreign readers: if you experiment difficulties in reading this post, change the character encoding of your browser to Unicode)

   Una constante (o un literal) de caracter es un modo de indicar en un programa fuente el valor asociado a un caracter dado en el entorno de ejecución. Por ejemplo, la constante de caracter 'x' representa el valor numérico asociado al caracter x, que es 120.
   De esta manera dejamos en claro desde un principio que las constantes de caracter representan números. Esos números tienen relación con caracteres, pero un "caracter" no es un valor ni un tipo de datos en C.
   El valor numérico depende de la codificación que utilice el entorno de ejecución, y esto no puede predecirse (o sea, el estándar C99 no dice nada al respecto).

   Hay dos tipos de constantes de caracter:

\( \bullet \) Las que van encerradas entre comillas simples: 'x', '1', '@',
\( \bullet \) las que anteponen además una L mayúscula: L'x', L'1', L'@'.

   Una constante de caracter (de alguno de los dos tipos) es válida si entre las dos comillas simples se coloca una de las siguientes:

\( \bullet \) Un caracter cualquiera del conjunto fuente, siempre y cuando no sea alguno de estos: '  \  {nueva-línea}
\( \bullet \) Una secuencia de escape simple, que es un par de caracteres formado por una contrabarra \ y una letra, así:

\'   \"   \?   \\   \a   \b   \f   \n   \r   \t   \v

\( \bullet \) Una secuencia de escape octal, lo cual consiste de una contrabarra \ seguida de uno, dos o tres caracteres octales. Por ejemplo: '\05', '\34', '\177'.
\( \bullet \) Una secuencia de escape hexadecimal, lo cual consiste en el par de caracteres \x seguida de 1 o más dígitos hexadecimales (los dígitos mayores que 9 pueden indicarse con letras tanto minúsculas como mayúsculas). Ejemplos:
'\x890C4', '\x074bb218faab', '\xff'.

   Se observa que no hay límites en el número de dígitos hexadecimales que se aceptan como una secuencia hexadecimal válida. Pero esto no significa que se acepten números enteros arbitrariamente grandes.
   Cada constante de caracter representa un número entero, concretamente, es una constante de tipo int.

   Si una contrabarra \ está seguida por algún otro caracter que los especificados, el comportamiento es indeterminado (o depende de la implementación local).
   Importante: Si bien las constantes de caracter se especifican mediante el conjunto fuente, sus valores efectivos serán los que correspondan al entorno de ejecución.
   Las secuencias de escape se usan para indicar caracteres del conjunto de ejecución que, de otro modo, no se podrían indicar con el conjunto de caracteres fuente.
   Aquí, pues, tenemos que tener en cuenta los dos conjuntos de caracteres, y las relaciones entre ellos.

   Hay dos maneras de representar comillas dobles: '\"' o simlemente '"'.
   Hay dos maneras de representar signos de pregunta: '\?' o simlemente '?'.
   Hay una sola manera de representar una comilla simple: '\''.
   Hay una sola manera de representar una contrabarra: '\\'.
   Una barra solitaria, como en '\', da un error de sintexis, porque \' se interpreta como el código de escape de la comilla simple.

   ¿Para qué sirve la secuencia de escape del signo de pregunta '\?' ?
   Su misión es defendernos de los trigrafos. Si quisiéramos tener literalmente una cadena de caracteres como "??[", podríamos indicarla con "\?\?[". (Ver abajo: constantes de cadena).



   Cada secuencia de escape antes listada representa exactamente un único caracter del entorno de ejecución.
   Los valores octales y hexadecimales representan un número entero no negativo, correspondiente, supuestamente, al caracter que uno desea. Sin embargo, dado que lo que se especifica del caracter es sólo su valor numérico asociado, es lo mismo '\xA3' que la constante hexadecimal numérica 0xA3, por ejemplo. Representan un mismo valor numérico.
   Los valores octales que pueden representarse con una constante de caracter varían entre 0 y 511. En particular, es común ver en los programas el uso de '\0', una constante de caracter octal que se usa para denotar el caracter nulo (cuyo valor siempre es 0).
   En general tenemos caracteres representables en bytes de 8 bits, lo cual da un valor máximo posible de 255. Esto nos dice que las constantes de caracter octales en general son suficientes para cubrir todas las necesidades al respecto. ¿Qué ocurre en este caso con valores, digamos, más allá de 255 en este caso? El compilador reconoce un tal valor, pero se considera un número entero fuera de rango (ver debajo).

   Una constante de caracter es un número entero. Si se escriban como "caracteres" es más bien por conveniencia intuitiva para el programador, y el significado que tendrá en el programa en ejecución. Por otra parte, el valor numérico concreto de una tal constante no está previamente especificado: 'A' no significa necesariamente el número 65 (código ASCII de la letra A). Si bien eso será cierto en casi todos los casos, no podemos darlo por garantizado, y no conviene programar bajo la creencia de que 'A' "es" el número 65.



Las secuencias de escape simples \a \b \f \n \r \t \v
denotan caracteres de control, no (necesariamente) imprimibles en el entorno de ejecución:

\a Caracter BELL (pitido).
\b Caracter BACKSPACE (retroceso).
\f Caracter FORMFEED (avance de formulario).
\n Caracter NEWLINE (nueva línea).
\r Caracter CARRIAGE RETURN (retorno de carro).
\t Caracter TAB (tabulación).
\v Caracter VERTICAL TAB (tabulación vertical).


Daremos más detalles de estos caracteres en otro post...



Rangos de valores numéricos para las constantes de caracter:

   Las constantes de caracter entrecomilladas se denominan constantes de caracter enteras.
   Las constantes de caracter entrecomilladas precedidas por L se denominan constantes de caracter ancho.
   (Nota: sólo se admite la L mayúscula para caracteres anchos, y nunca la l minúscula).

\( \bullet \) Una constante de caracter tiene tipo int.

\( \bullet \) El rango de valores de una constante de caracter octal o hexadecimal siempre "DEBE SER REPRESENTABLE" en el del tipo unsigned char.
\( \bullet \) El tipo de datos char permite (es suficiente para) especificar el valor de la codificación de cualquier caracter del entorno de ejecución.
\( \bullet \) Además, un miembro del conjunto básico de caracteres (del entorno de ejecución) tiene asociado siempre un valor no negativo.
\( \bullet \) Los valores de los caracteres que representan dígitos: '0' a '9' siempre son contiguos.
\( \bullet \) El valor numérico de una constante caracter de 1 byte, es el que corresponde a dicho caracter en el entorno de ejecución. (Aclaración: si la letra A tiene un código en el entorno de traducción, y tiene otro código numérico en el de ejecución, entonces la constante 'A' se refiere al valor numérico del entorno de ejecución).
\( \bullet \) Es posible indicar constantes de caracter con más de 1 caracter fuente (como 'ab') o secuencias de escape. Pero el significado de esto no está definido en el estándar C99, y depende de la implementación.
(Nosotros intentaremos, pues, no usar este tipo de cosas).
\( \bullet \) El valor de una constante de caracter que contiene un único caracter o una secuencia de escape, es el que tendría si se lo considera como un dato de tipo char que promociona (o convierte) a tipo int.

   ¿Qué quiere decir todo esto?

   El tipo de datos char es, recordemos, uno más de los tipos de datos aritméticos enteros.
   Asociados a él están los tipos signed char y unsigned char.
   Recordemos también que en algunas implementaciones puede darse el caso de que char equivale a signed char, y en otros casos char equivale a unsigned char.

   Los rangos de valores de estos tipos de datos son los siguientes intervalos de números enteros:

         char: [CHAR_MIN, CHAR_MAX]
unsigned char: [0,          UCHAR_MAX]
  signed char: [SCHAR_MIN, SCHAR_MAX]


   Por otro lado, tenemos el tipo int, cuyo rango de valores es:

int: [INT_MIN, INT_MAX]

   En cualquier caso, el rango de valores de signed char "cabe" en el rango de valores de un int.
   Además, todos los valores no negativos que están en el rango de valores admitido para signed char, también están en el rango de valores admitido para unsigned char.

   ¿Podría ocurrir que signed char e int tengan el mismo rango de valores? Respuesta: en teoría .
   Esto quiere decir que no podemos programar suponiendo que un int siempre tiene un rango de valores mayor, por más que esto es lo que sucede en la mayoría de los casos.
   (Las excepciones suelen darse en máquinas donde 1 byte ocupa más de 8 bits).

Volviendo a los caracteres:

   Una constante de caracter de tipo octal o hexadecimal determina un valor numérico entero y no negativo. En el caso octal, hasta 511, en el caso hexadecimal, no hay límites.
   Pero este valor DEBE poder representarse como un unsigned char.
   Si esto no ocurre, el estándar C99 indica que se trata de una violación de restricción (constraing violation), o sea, una situación anómala en el lenguaje, que requiere un aviso del compilador.

   En este caso podría ocurrir que el programa no compile, o que compile pero con un mensaje de advertencia (warning) informando que la constante está fuera del rango (de un unsigned char).
   Si el compilador acepta de todos modos la constante, no podemos asegurar qué camino va a tomar.
   Sólo podemos estar seguros de que elegirá un valor en el rango de unsigned char para esta constante.

   (El compilador GCC 4.8, con la opción -std=c99, lleva a cabo el procedimiento típico que el estándar exigiría a cualquier constante no negativa fuera de rango: trunca el número, quedándose con la porción menos significativa. En concreto, si la constante que hemos definido tiene valor X, el valor se cambia a R, donde R es el resto de la división entre X y UCHAR_MAX.)

   Luego de que el tamaño de la constante ha sido "resuelto" de alguna manera, el compilador intenta convertir el valor de la constante a tipo char.
   Mientras el valor de la constante se mantenga no negativo y no sobrepase a CHAR_MAX, no habrá cambios en el numerito que veníamos arrastrando.
   Pero si el valor fuese mayor que CHAR_MAX, el número resultante dependerá de los caprichos del compilador utilizado. Ya el estándar no asegura nada.

   Esta situación incómoda puede darse si char coincide con signed char,
   Supongamos el caso típico en que CHAR_BIT es 8. Tendríamos que '\xA0' es una constante de caracter válida (pues sería representable en un unsigned char, ya que 0xA0 es igual a 160, menor que UCHAR_MAX, que es 255).
   Al convertir eso a signed char, obtendríamos un número negativo (no voy a decir cuál, pues esto depende de la implementación).
   En un último "paso" (que virtualmente lleva a cabo el compilador), ese valor char es promocionado a int.

   En nuestro ejemplo de char igual signed char, con 8 bits en 1 byte, la situación es que toda constante de caracter (válida) tiene un valor que oscila entre -128 y +127, y se considera de tipo int.

   En el caso de que tuviésemos que char igual a unsigned char, y tal que el rango de valores de unsigned char cabe en int, tendríamos que toda constante de caracter (válida) tiene un valor que oscila entre 0 y 255.

   ¿Y qué pasa si hay valores en unsigned char que no están en int?
   Supongamos el caso en que int coincidiese con signed char.
   Siendo así, el rango de valores de unsigned char iría de 0 a 255, mientras que el de int iría de -128 a +127.
   El resultado sería que las constantes se convierten de nuevo a int, dando un comportamiento no claramente definido en el caso de valores mayores a +127.

   En el caso de constantes de caracter válidas o admitidas por el compilador, tenemos (internamente) esta cadena de conversiones:

constante original --> (unsigned char) --> (char) --> (int)

   Puede resultar sorprendente que para algunas implementaciones la constante '\xFF', que a propósito se refiere a un valor hexadecimal igual al decimal 255, termine teniendo un valor ¡negativo! (típicamente -1).

Moraleja: Una constante de caracter, si bien representa un número entero, no está destinada a representar información con significado numérico, sino que es una herramienta para manejar caracteres en un programa dado.

Luego: No podemos confiar en que siempre '\x(hexa)' será lo mismo que 0x(hexa).
   Pero: Sí que podemos estar seguros de que son el mismo número, cuando el valor hexadecimal sea inferior a SCHAR_MAX.



Valores para caracteres anchos:

   Repitamos la misma experiencia de antes, ahora para caracteres anchos.

   Primero aclaremos que en el archivo de cabecera <stddef.h> está definido el tipo de datos entero wchar_t. (También aparece definido en <stdlib.h> y en <wchar.h>)

\( \bullet \) Una constante de caracter ancho tiene tipo wchar_t.
\( \bullet \) El rango de valores de una constante de caracter octal o hexadecimal siempre "DEBE SER REPRESENTABLE" en el del tipo sin-signo (unsigned) que corresponda a wchar_t.
\( \bullet \) El tipo de datos wchar_t permite (es suficiente para) especificar el valor de la codificación de cualquier caracter del entorno de ejecución.
\( \bullet \) Todo caracter ancho especificado por medio de 1 solo caracter multibyte (que tiene correspondencia con algún caracter del entorno de ejecución) se asigna en una forma bien definida al valor wchar_t (de un caracter ancho) que le corresponde, mediante las reglas especificadas para la función de biblioteca estándar llamada mbtowc() (la cual no analizaremos aquí).
\( \bullet \) Si se indica más de 1 caracter multibyte en la constante, o el caracter multibyte dado no se corresponde con caracter alguno del entorno de ejecución, el estándar C99 no es capaz de definir o decidir qué comportamiento adoptará el compilador en ese caso.

   El tratamiento dado a los caracteres anchos dependerá en todo momento, en la ejecución del programa, de la "configuración local" que esté activa en un instante dado.



¿Es cierto que los caracteres anchos son más anchos que los caracteres "normales"?

   Muchas veces encontraremos compiladores donde no hay soporte adecuado para los caracteres anchos. La idea es que los caracteres anchos sirvan para representar todos los caracteres del conjunto especificado en ISO/IEC 10646 (que equivale a Unicode).
   Pero esto no necesariamente tiene que ser así, y bien podríamos tener wchar_t siendo de 1 byte.



¿Es cierto que para los caracteres del conjunto básico, las constantes de caracter y las constantes de caracter ancho tienen el mismo valor numérico asociado?

   Por ejemplo, ¿es cierto que 'x' da un número entero igual que L'x'?

Respuesta: ¡En principio no se puede asegurar!  :o :o

   ¡Oh, por Dios!  :banghead: :banghead: :banghead:
   Lo lógico es pensar que, en la mayoría de los casos, sería saludable que el caracter de la letra x tuviera el mismo código numérico ya sea que se considere como un caracter normal que cuando se le considere un caracter ancho.
   El programador puede verificar si éste es el caso, consultando la macro:

__STDC_MB_MIGHT_NEQ_WC__

   Si el valor de esa macro es 1, significa que los valores numéricos de las versiones "normal" y "ancha" de una misma constante de caracter podrían diferir.
   Si dicha macro tiene otro valor, o no está definida, entonces podemos asegurar que los caracteres básicos tienen constantes asociadas con el mismo valor numérico en sus versiones "normal" y "ancha". Así, por ejemplo: 'x' tiene el mismo valor numérico que L'x'.
   El caracter ancho nulo corresponde al valor 0 (de tipo wchar_t).



   Sobre wchar_t:

\( \bullet \) Los valores mínimo y máximo tolerados por wchar_t son: WCHAR_MIN y WCHAR_MAX.
\( \bullet \) Si wchar_t se define como un tipo entero con signo (signed), entonces contiene como mínimo al rango de valores [-127, +127]. Si se define como entero sin signo (unsigned), entonces contiene como mínimo al rango de valores [0, 255].
\( \bullet \) El tipo de datos wint_t se relaciona "de algún modo" con wchar_t (del mismo modo que int se relaciona con char en funciones de biblioteca). Sus valores mínimo y máximo son WINT_MIN y WINT_MAX.
\( \bullet \) El rango de valores de wint_t contiene en cualquier caso, como mínimo, al rango de valores de wchar_t.
\( \bullet \) Si wint_t se define como un tipo entero con signo (signed), entonces contiene como mínimo al rango de valores [-32767, +32767]. Si se define como entero sin signo (unsigned), entonces contiene como mínimo al rango de valores [0, 65535].
\( \bullet \) wchar_t y wint_t pueden ser el mismo tipo entero.  ::)

   Lo importante respecto este último punto es que:
\( \bullet \) Todo valor asociado a un caracter ancho puede representarse tanto en el rango de valores de wchar_t como en el de wint_t. (Si sobran valores numéricos, no asignados a caracter alguno, no importa  ;) ).
\( \bullet \) Sin embargo, el tipo wint_t es capaz de albergar al menos un valor numérico más que no corresponde a los valores enteros de los caracteres anchos.

   ¿Pero y entonces? ¿Si wint_t tiene espacio para al menos 1 valor más que wchar_t, no amerita tener un tipo de datos mayor? De hecho no, porque si a wchar_t le quedaron valores sin asignar a caracteres anchos, cualquiera de esos valores puede usarse como el adicional requerido por wint_t, y así ambos tipos enteros pueden tener el mismo tamaño en memoria.



Un poco de Unicode.

   La norma ISO/IEC 10646 equivale a Unicode. Contiene un conjunto de caracteres mucho mayor que ASCII, cuya virtud es contener juegos completos de caracteres para todos los idiomas de la humanidad, hasta la fecha. También contiene símbolos matemáticos, científicos, técnicos, etc.
   El conjunto resultante necesita 32 bits para tolerar todas las posibilidades. Su codificación se denota UTF-32. Sin embargo, de los \( 2^{32}(\approx 4000\textsf{\ millones}) \) códigos posibles, se usan apenas 110000 (año 2013). Para la mayoría de los casos es suficiente la versión original de Unicode que "encajaba todo" en 16 bits, dando un máximo de 65535 caracteres. (Esto es lo que hacen por ejemplo las fuentes típicas de Windows).

   A fin de ahorrar espacio de almacenamiento, dado que no siempre se requieren todos los caracteres de Unicode, se suelen usar otras dos codificaciones: UTF-16 y UTF-8. Son codificaciones multibyte de longitud variable, es decir: dan reglas que permiten especificar todos los caracteres Unicode mediante SHITFs adecuados, que indican qué parte de la tabla Unicode ha de utilizarse. Los caracteres se representan en UTF-16 con 1 o 2 bytes, mientras que en UTF-8 se utilizan de 1 a 4 bytes.
   No vamos a entrar en detalles de cómo es la codificación multibyte utilizada, pero diremos que está diseñada de forma ventajosa respecto a otras estrategias multibyte. Por ejemplo, no es necesario llevar la cuenta de todos los SHIFTs que hubieren desde el principio del Universo, y además, si un programa observa 1 byte individual, es capaz de darse cuenta en qué estado de la codificación multibyte está.

   En UTF-8, los caracteres ASCII funcionan normalmente con los mismos valores codificados de siempre (del 32 hasta el 127), ocupando 1 byte.

¿Hay soporte en C para caracteres Unicode? ¿Cómo?

   En C99 se declara que:

\( \bullet \) El conjunto requerido Unicode consiste en todos los caracteres definidos por la norma ISO/IEC 10646, junto con todos las enmiendas y correcciones que correspondan a los sucesivos años o versiones.

\( \bullet \) Para saber la fecha de la versión de Unicode que se está usando, se debe consultar el valor de la macro: __STDC_ISO_10646__. Su valor tiene el formato yyyymmL, es decir, un número entero de tipo long int de 6 dígitos decimales, tal que los primeros 4 corresponden al año, y los 2 que siguen al mes.

Caracteres Unicode en el Entorno de Traducción

   En el código fuente (o sea, en el entorno de traducción) pueden usarse los nombres de caracter universales (abreviado: UCN):

\( \bullet \) Un UCN tiene alguno de los formatos

\uhhhh: La letra u siempre es minúscula, y hhhh denota exactamente 4 dígitos hexadecimales (ni más ni menos).

\UHHHHhhhh: La letra U siempre es minúscula, y HHHHhhhh denota exactamente 8 dígitos hexadecimales (ni más ni menos).

   Es una manera de escribir (en el entorno de traducción) un caracter Unicode a través de su código en hexadecimal.
   La forma "larga" \UHHHHhhhh permite especificar cualquier caracter Unicode.
   La forma "corta" \uhhhh[/i][/color] se usa para indicar los caracteres Unicode del primer plano de 65535 elementos de la tabla Unicode, y así el caracter \uhhhh[/i][/color] equivale a la versión "larga" con 0's delante: \U0000hhhh.

   Esta notación puede usarse en el entorno de traducción para escribir un programa en C.
   Cada UCN representa un caracter Unicode a través del código hexadecimal correspondiente.
   (Así como los trigrafos se reemplazan luego por caracteres específicos, también los nombres de caracter universales hacen algo similar).

   Esto otorga un soporte portable para caracteres Unicode, en el costado del entorno de traducción.

\( \bullet \) Los nombres de caracter universales no pueden representan tal que su identificador corto (o sea, sus últimos 4 dígitos hexadecimales) tiene valores por debajo del hexadecimal 00A0, exceptuando los que corresponden a 0x0024 ($), 0x0040 (@), 0x0060 (').
   (Los caracteres por debajo de 0x00A0 contienen los típicos caracteres de control de ASCII, así como los caracteres básicos del estándar C).

\( \bullet \) Tampoco están permitidos los valores cuya parte "corta" está en el rango que va de hexadecimal D800 a hexadecimal DFFF. (Es un rango de valores reservados por Unicode).

   Por otro lado, tenemos que:

\( \bullet \) Los $UCN$s pueden aparecer en identificadores, constantes de caracter y constantes de cadena, para especificar caracteres que no están en el conjunto básico de caracteres.

¿Qué diferencia hay entre \x00A0 y \u00A0?  ???

   La diferencia es que \u00A0 es una forma de introducir caracteres en cualquier parte de nuestro programa, con código Unicode hexadecimal 00A0, mientras que \x00A0 sólo puede usarse como código de escape dentro de una constante caracter o de cadena: '\x00A0'  "Hello: \x00A0 !!!".

   En particular, dentro de una constante de caracter o de cadena, puede aparecer también la forma \uhhh: '\u00A0'.

   Sin embargo, no tenemos que confundirnos con este asunto: al escribir  '\u00A0' es lo mismo que si escribiéramos el caracter Unicode cuyo código es 00A0 y lo ponemos entre comillas. No es esa una manera de indicarle a C un valor hexadecimal de una constante.

Más concretamente:

   Se espera que el programador nunca tenga que escribir nombres universales de caracter como \uhhhh ó \UHHHHhhhh, sino que el editor de texto que utiliza ha de ser capaz de mostrarle los caracteres Unicode correspondiente en forma correcta (visualmente hablando). Internamente dicho editor de texto ha de ser quien maneje la conversión y escriba estos nombres universales de caracter en el formato \u ó \U que corresponda.
   Aún así, si el programador (nosotros) escribe esos nombres universales de caracter, todo tendría  que funcionar bien.

   ¿Se puede asegurar que un caracter dado tiene código hexadecimal idéntico al que tiene asignado en Unicode?
   Para preguntas como esa se tiene la macro __STDC_ISO_10646__. Si está definida, se pueden asegurar algunas cosas:

\( \bullet \) Todo caracter del conjunto requerido de Unicode cuando se guarda en un objeto de tipo wchar_t, tendrá un valor entero igual a la parte "corta" del identificador de dicho caracter.

   (Esto servirá para representar cualquier caracter Unicode con códigos por debajo de 65535).

\( \bullet \) Si se usa alguna otra codificación distinta a Unicode, la macro __STDC_ISO_10646__ no ha de estar presente.

   Como ejemplo, analicemos algunas situaciones (ejemplos extraídos de "The new C standard"):

\( \bullet \) ¿Son iguales L'\u00A3' y L'\x00A3'? La 1er constante tiene código Unicode 0x00A3. Pero el entorno de traducción no "ve" el código, sino el caracter propiamente dicho, y entonces (si no se está usando Unicode allí) puede estar codificado con otro valor numérico. Así, la 2da constante puede indicar un valor distinto. Mas, ambas serán iguales si __STDC_ISO_10646__ está definida.

\( \bullet \) ¿Son iguales L'\u00A3' y L'\( \textsf{\pounds} \)'? Sí, siempre, pues el caracter \( \textsf{\pounds} \) que aparece en la 2da constante... tiene código Unicode 0x00A3, y eso es lo que se ha escrito del lado izquierdo: el caracter cuyo código Unicode es 0x00A3.

\( \bullet \) ¿Son iguales L'\x00A3' y (wchar_t) 0x00A3? Ciertamente sí, ya que ahora en la 1er constante hemos puesto un caracter hexadecimal, cuyo código ha de ser ese mismo número hexadecimal. Dado que se trata de una constante de caracter ancho, su tipo es wchar_t, y entonces todo funciona bien cuando se convierte el número 0x00A3 del lado derecho al tipo wchar_t.

\( \bullet \) ¿Son iguales L'\x00A3' y L'\( \textsf{\pounds} \)' ? Del lado izquierdo ahora tenemos simplemente el valor hexadecimal 0x00A3, mientras que del lado derecho tenemos el número asociado al código del caracter \( \textsf{\pounds} \). Si la codificación no es Unicode, dichos valores podrían diferir. En cambio, son coincidentes si la macro __STDC_ISO_10646__ está definida.

\( \bullet \) ¿Son iguales L'\( \textsf{\pounds} \)' y (wchar_t) 0x00A3? De nuevo, esto depende del valor que le toque al caracter \( \pounds \) en la codificación utilizada. Coincidirán si la macro __STDC_ISO_10646__ está definida.



(En el siguiente post continuaremos con un tema que va directo a continuación de este: Constantes de cadena).



   Sólo una cosa más: ¡me cansé!  :'( ¡Esto de los caracteres en C es durísimo!  :banghead: :banghead: :banghead:



Organización (http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247)

Comentarios y Consultas (http://rinconmatematico.com/foros/index.php?topic=64837.msg260251#msg260251)
Título: Programación en C: 36. Caracteres en el lenguaje C (IV)
Publicado por: argentinator en 08 Septiembre, 2013, 08:59 am
36. Caracteres en el lenguaje C (IV): Constantes de Cadena. Algunas reflexiones

Constantes de cadena.

\( \bullet \)   Una cadena de caracteres literal es una secuencia de cero o más caracteres multibyte encerrados entre comillas dobles "":

"xyz"
"Hello"
"@!=3"


\( \bullet \)   Cada elemento de la secuencia de la cadena es una constante de caracter, y sigue las mismas reglas de formación que antes se explicaron, incluyendo secuencias de escape, octales, hexadecimales, etc:
    "Ejemplo hexa: \xAA \t\t Todo bien?\n".

\( \bullet \)   Sin embargo, ahora el caracter comilla simple (') puede indicarse escribiendo tanto la secuencia de escape \' como la comilla por sí sola: '. Por otro lado, para indicar el caracter comilla doble, es obligatorio usar secuencia de escape: \.

   Por su parte:

\( \bullet \)   Las cadenas de caracteres anchos son secuencias de caracteres anchos, que se indican encerradas entre comillas dobles, y además tienen antepuesto el prefijo L:

L"\( \kappa\alpha\rho\alpha\kappa\tau\epsilon\rho\epsilon\sigma \) anchos"

¿Qué ocurre cuando se intercalan comentarios?

   En C hay dos maneras de intercalar comentarios:

(1) /* Comentario multilínea estilo C de K&R */
(2) // Comentario hasta fin de línea estilo C++

   ¿Qué pasa cuando ponemos esos símbolos en una cadena de caracteres?
   ¿Se confunde con los comentarios, o sale la cadena ilesa?
   En esos casos, es prioritaria la interpretación como cadena de caracteres.
   Así por ejemplo:

"A ver si esto /* funciona o... */ o no funciona"

equivale a la cadena de caracteres tal cual se ve ahí: A ver si esto /* funciona o... */ no funciona.

   En cambio:

A ver si esto /* funciona o... */ no funciona

sería interpretado como: A ver si esto   no funciona.

   Así que los comentarios no se han activado dentro de la constante literal de cadena.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Recapitulación. Reflexiones

   Los 3 posts anteriores han ido creciendo en dureza técnica, y para mí ha sido muy agotador investigar y explicar los detalles pertinentes. Tratemos de hacer un breve resumen de lo que allí desarrollamos, a fin de suavizar el tratamiento sobre este asunto.

\( \bullet \)   En la parte (I) de Caracteres en el lenguaje C hicimos un rápido recorrido histórico sobre los conjuntos de caracteres que hubo en el mundo hasta la fecha, y mencionamos la famosa tabla ASCII (que muchos se saben ya de memoria), las limitaciones que esa tabla tiene para caracteres internacionales, las soluciones que luego se propusieron a través de estrategias o codificaciones multibyte, y finalmente el advenimiento de Unicode, que es la norma ideal que todo sistema o programa informático tiene que adoptar.

\( \bullet \)   En la parte (II) nos introdujimos en los tecnicismos que el estándar C hace sobre los caracteres. Vimos allí que todo el tiempo hay una separación intencional entre el lugar donde los programas se escriben (entorno de traducción) y el lugar donde se lo piensa llevar ya compilado para ejecutarse (entorno de ejecución).

\( \bullet \)   Para cada uno de esos dos entornos se definen respectivos subconjuntos de caracteres: básicos y extendidos. Los básicos del entorno de traducción son los mismos para el entorno de ejecución. Así ambos entornos tienen un subconjunto de caracteres en común. El entorno de ejecución tiene al menos unos cuantos caracteres de control más. En particular, el entorno de ejecución contiene al caracter nulo, que se usa para indicar el fin de las cadenas de caracteres en C, y es por eso tan importante que figure allí.

\( \bullet \)   Se estudiaron los trigrafos: unas 9 secuencias específicas de 3 caracteres básicos que se reemplazan en etapa de compilación por 9 caracteres básicos específicos, que en algunas regiones del mundo puede que no estén debidamente disponibles.

\( \bullet \)   Se discutió el concepto de codificación: un número entero no negativo que se le asocia a cada caracter (siendo un caracter un objeto abstracto, un dibujito que vive en el imaginario colectivo).

\( \bullet \)   Explicamos el funcionamiento de los caracteres multibyte, unas secuencias de varios bytes que permiten representar mediante cambios de estado a muchos otros caracteres que no serían accesibles si sólo se usara 1 byte. Comentamos el hecho de que bien pueden ellos formar parte de un programa fuente, así como del programa ejecutable ya compilado. En cada caso la situación es distinta, como siempre. Sobretodo, en el programa fuente pueden introducirse caracteres multibyte desde el editor de texto utilizado, en el idioma del programador, y el método utilizado es fijo. En cambio, en el entorno de ejecución puede cambiar varias veces la "configuración local" dando lugar a varias posibilidades de codificación multibyte diferentes.

\( \bullet \)   En las partes (III) y (IV) nos zambullimos en las constantes de caracter y de cadena, y en los valores numéricos de las codificaciones asociadas. Aparecieron dos tipos de caracteres ahora: los "normales" (que pueden representarse con 1 solo byte), y los "anchos" (caracteres extendidos del entorno de ejecución que abarcan tanto a los normales como a aquellos que no caben en 1 byte). Los caracteres "anchos" no son multibyte, sino más bien caracteres con una codificación determinada, en un sistema que requiere varios bytes (en cantidad fija) para representarse (por ejemplo, Unicode).

\( \bullet \)   Los caracteres "normales" se representan encerrados entre comillas simples, y los caracteres "anchos" se representan del mismo modo, aunque precedidos por el prefijo L.

\( \bullet \)   Aprendimos que las constantes de caracter representan números enteros, ya sea de tipo int (para los caracteres "normales") o de tipo wchar_t (para los caracteres "anchos").

\( \bullet \)   Sin embargo, no se usan todos los valores de un int para representar constantes de caracteres, sino sólo los que caben en un char.

\( \bullet \)   Por otro lado, se pueden usar constantes de caracter octales (hasta 3 dígitos) y hexadecimales (sin límite en el número de dígitos) para indicar caracteres con códigos que quepan en un unsigned char.

\( \bullet \)   Dado que a veces char no es lo mismo que unsigned char (o sea, char puede ser signed char), un valor octal o hexadecimal muy grande puede volverse negativo.

\( \bullet \)   Los valores de los caracteres "ancho" caben en wchar_t.

\( \bullet \)   Estudiamos todas las secuencias de escape admitidas por el estándar C, que generan caracteres de uso reservado (comillas simples y dobles, contrabarra, interrogación) caracteres de control, códigos octales o hexadecimales.

\( \bullet \)   Investigamos la relación entre las constantes de caracteres "normales" y caracteres "anchos" con ayuda de la macro __STDC_MB_MIGHT_NEQ_WC__.

\( \bullet \)   Introdujimos los nombres universales de caracter (UCN), usados para denotar en hexadecimal los correspondientes caracteres de Unicode. Estos nombres utilizan dos variantes, una forma corta con \u y una forma larga con \U. Debe entenderse que en donde aparece una de estas construcciones sintácticas, en realidad hay un caracter Unicode con el código numérico correspondiente.

\( \bullet \)   Estudiamos entonces el soporte de C99 para caracteres unicode, a través de estos UCN.

\( \bullet \)   Dado que nada obliga a que una implementación dada codifique sus caracteres Unicode con el número de código que le otorgó el consorcio Unicode, nos hicimos la pregunta de si podemos confiar o no en esos números. La respuesta la brinda la macro __STDC_ISO_10646__. Si está presente, es que SÍ, y si no, es que NO.

\( \bullet \)   Por último hemos desarrollado lo poco que faltaba: las constantes literales de cadena, tanto las de caracteres "normales" como de caracteres "anchos".

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Ustedes se preguntarán por qué me he empecinado  :banghead: :banghead: :banghead: en estudiar con tanto detalle este asunto de los caracteres en C. Hay varios motivos para ello:

   (1) Un programa en C tiene varias fases de compilación, las cuales implican transformaciones de caracteres del código fuente en quién sabe qué cosas.

   (2) Un programa en C tiene reglas sintácticas que se dictan en función de la codificación de caracteres empleada en el entorno de traducción. En particular, podremos abordar con total rigor las reglas conque se escribe un programa correcto en C.

   (3) El tema de los caracteres es muy complejo, y es bueno saber que existen cosas como estas: caracteres básicos, extendidos, multibyte, caracteres anchos, nombres universales. Y además tener una idea de las diferencias y relaciones entre esos conceptos.

   (4) En las aplicaciones modernas hay que tener en cuenta Unicode como asunto esencial, y familiarizarse con cosas como wchar_t, <wchar.h>.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Nosotros haremos aplicaciones en la consola, que se conforman con ver los acentos y eñes del idioma castellano.
   Así que los caracteres "normales" serán suficientes, y no habrá que lidiar con Unicode