17/10/2019, 12:13:03 pm *
Bienvenido(a), Visitante. Por favor, ingresa o regístrate.
¿Perdiste tu email de activación?

Ingresar con nombre de usuario, contraseña y duración de la sesión
Noticias: Puedes practicar LATEX con el cómodo editor de Latex online
 
 
Páginas: [1] 2 3 4   Ir Abajo
  Imprimir  
Autor Tema: Proyecto de Curso (Dictado - Notas): Programación en C (2013, por Argentinator)  (Leído 43869 veces)
0 Usuarios y 1 Visitante están viendo este tema.
argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« : 03/01/2013, 01:13:10 pm »

Proyecto de Curso (Dictado - Notas Teóricas): Programación en C

3/Enero/2013

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Bibliografía sugerida

[texx]\bullet[/texx]  C standard documents

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

[texx]\bullet[/texx]  Programando con wxDevC++, archivo electrónico disponible en:

Programando con wxDevC++.zip

Programando con wxDevC++.pdf (archivo adjunto a este post)

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

[texx]\bullet[/texx]  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).

[texx]\bullet[/texx]  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).

[texx]\bullet[/texx]  The C book, por: gbDirect, acceso online en:

The C Book

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

[texx]\bullet[/texx]  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

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

[texx]\bullet[/texx]  The New C Standard, D. M. Jones

The New C Standard

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

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

Manual de C de IBM

(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

2.      Explicando el HolaMundo.c

3.      Un sencillo Hola Mundo en Windows

4.      De la eficacia a la elegancia - Macros del Preprocesador (parte I)

5.      De la eficacia a la elegancia - Macros del Preprocesador (parte II)

6.      Etapas del Preprocesador

10.     Pare de sufrir: Alternativas a la consola CMD

11.     Configuración de opciones del Compilador. Compatibilidad con C99.

12.     Uso básico de la función printf().

30.     ¿Cómo lograr que todo funcione? (Parte I)

31.     ¿Cómo lograr que todo funcione? (Parte II)

Caracteres y strings:

7.      Caracteres (char) y Strings (char*)

33.     Caracteres en el lenguaje C (I)

34.     Caracteres en el lenguaje C (II)

35.     Caracteres en el lenguaje C (III)

36.     Caracteres en el lenguaje C (IV)

37.     Caracteres en el lenguaje C (V)

38.     Caracteres en el lenguaje C (VI)


Números enteros, de punto flotante y complejos:

8.      Enteros en C (parte I)

9.      Enteros en C (parte II)

12.     Uso básico de la función printf().

13.     Testeando tipos de datos enteros de C (parte I)

14.     Testeando tipos de datos enteros de C (parte II)

15.     Testeando tipos de datos enteros de C (parte III)

16.     Testeando tipos de datos enteros de C (parte IV)

17.     Números de punto flotante. Generalidades

18.     Números de punto flotante. Estándar IEEE 754

19.     Números de punto flotante. Estándar C99. Constantes

20.     Números de punto flotante. Redondeos y cambio de base

21.     Números de punto flotante. Valores fuera de rango

22.     Números de punto flotante. Estándar C99. FLOAT.H

23.     Números de punto flotante. Compilador GCC

24.     Números de punto flotante. Programas de Testeo (parte I)

25.     Números de punto flotante. Programas de Testeo (parte II)

26.     Números de punto flotante. Programas de Testeo (parte III)

27.     Números complejos en C. Preliminares

28.     Números complejos en C. Estándar C99. Conversión de tipos

29.     Números complejos en C. Programa de testeo.


Spoiler: (Índice por orden de edición) (click para mostrar u ocultar)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

* Programando_con_wxDev-C.pdf (3147.02 KB - descargado 8118 veces.)
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #1 : 03/01/2013, 02:35:20 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.  :rodando_los_ojos:
   A fines técnicos digamos que:

[texx]\bullet[/texx]  Trabajaremos con el estándar C99.

Spoiler: Elección de un estándar (click para mostrar u ocultar)

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



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

Spoiler: ¿Qué es un IDE y cuál elegir? (click para mostrar u ocultar)

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++

   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:sonrisa_amplia:

   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

   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

Comentarios y Consultas
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #2 : 03/01/2013, 04:53:54 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

Comentarios y Consultas
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #3 : 04/01/2013, 01:19:38 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).


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #4 : 04/01/2013, 10:47:02 am »

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.

Spoiler: (Continuación de la discusión...) (click para mostrar u ocultar)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #5 : 04/01/2013, 03:40:33 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 ,:

Cita

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

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


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #6 : 04/01/2013, 08:12:26 pm »

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

Comentarios y Consultas
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #7 : 05/01/2013, 09:26:51 am »

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

Spoiler: Entendiendo los caracteres (click para mostrar u ocultar)

   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:


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

[texx]\bullet[/texx] \n   Salto de Línea
[texx]\bullet[/texx] \\   Caracter \
[texx]\bullet[/texx] \'   Caracter '
[texx]\bullet[/texx] \"   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!! :lengua_afuera: )
   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:


   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

Spoiler: (programa TestingChars.c) (click para mostrar u ocultar)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   El tipo de datos que se usa  :tranqui: 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: *:rodando_los_ojos:

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

   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: :sorprendido:

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

   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)


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #8 : 05/01/2013, 12:35:46 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.


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:


   ¿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 [texx]2^{64}-1=18'446744'073709'551615[/texx], 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:

[texx]\bullet[/texx]   unsigned char:
       Representa valores al menos 8 bits, con lo cual se asegura al menos el rango de 0 a [texx]2^8-1=255[/texx].
[texx]\bullet[/texx]   unsigned short int         
       Representa valores al menos en el rango de 0 a [texx]2^{16}-1=65535[/texx].
       Además se exige que, en cualquier circunstancia, contenga el rango de unsigned char.
[texx]\bullet[/texx]   unsigned int
       Representa valores al menos en el rango de 0 a [texx]2^{16}-1=65535[/texx].
       Además se exige que, en cualquier circunstancia, contenga el rango de unsigned short int.
[texx]\bullet[/texx]   unsigned long int
       Representa valores al menos en el rango de 0 a [texx]2^{16}-1=65535[/texx].
       Además se exige que, en cualquier circunstancia, contenga el rango de unsigned int.
[texx]\bullet[/texx]   unsigned long long int
       Representa valores al menos en el rango de 0 a [texx]2^{64}-1=18'446744'073709'551615[/texx].
       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:


   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:

[texx]\bullet[/texx]  signed char:
       de [texx]-2^7=-127[/texx] a [texx]2^7-1=127[/texx]
[texx]\bullet[/texx]  signed short:
       al menos de [texx]-2^{15}=-32767[/texx] a [texx]2^{15}-1=32767[/texx].
[texx]\bullet[/texx]  signed int:
       al menos el mismo rango que signed short.
[texx]\bullet[/texx]  signed long:
       al menos el mismo rango que signed int.
[texx]\bullet[/texx]  signed long long:
       al menos de [texx]-2^{63}=-9223372036854775807[/texx] a [texx]2^{63}-1=9223372036854775807[/texx].
       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:enojado:
   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:

[texx]\bullet[/texx]   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:




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:  :sorprendido: :sorprendido: 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.   :beso: 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:

[texx]\bullet[/texx]   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 [texx]2^{63}-1[/texx], 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 [texx]2^{64}-1[/texx] seguida de sufijo U, como un unsigned long long int, escribiéndola así:
18446744073709551615U  :rodando_los_ojos:

(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:




Organización

Comentarios y Consultas
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #9 : 05/01/2013, 12:35:54 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.


   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.


   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:


   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:


   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.


   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:


   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:

Spoiler: (Otros tipos enteros) (click para mostrar u ocultar)



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:




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

Comentarios y Consultas
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #10 : 05/01/2013, 12:36:37 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.

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

  • Nos aparece el enlace de descarga siguiente:

    ConEmuSetup.130827.exe

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

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

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

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

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

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

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

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

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

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

    explorer.exe|devcpp.exe

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

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

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

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

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

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

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

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

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


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

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



CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

* 20130914conemufondo.PNG (35.29 KB - descargado 1026 veces.)
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #11 : 08/01/2013, 09:07:07 am »

11. Configuración de opciones del Compilador. Compatibilidad con C99.

(Actualizado: 20/Diciembre/2013)

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

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

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

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


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

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

-pedantic-errors

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

-fextended-identifiers

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

* 20130916conemufondo.PNG (35.29 KB - descargado 1027 veces.)
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #12 : 08/01/2013, 03:26:50 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:


   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.  :rodando_los_ojos:
   Queda así (abrir Spoiler):

Spoiler: Programita Minitutorial de Música (click para mostrar u ocultar)

   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?  :malvado:

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #13 : 09/01/2013, 05:13:54 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?  :sorprendido: :sorprendido: :sorprendido: :sorprendido:

   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 [texx]r\leq x \leq s[/texx], [texx]r[/texx] es el límite inferior, y [texx]s[/texx] 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.  :labios_sellados:

printf() va a mostrar erróneamente la mayoría de los números.  :enojado:  :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.  :llorando: :llorando:
(2) Habrá quienes piensen que el programa TestingIntegers1.c está hecho por un cavernícola. :BangHead:  :risa:
      ¿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.  :tranqui:


 :sonrisa_amplia:


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

* 20130109TestingIntegers1.zip (1.68 KB - descargado 196 veces.)
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #14 : 10/01/2013, 05:30:40 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:indeciso:
   En particular, esto nos obliga a ser muy cuidadosos  :sorprendido: :sorprendido: :sorprendido: :sorprendido: :sorprendido: 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:

Spoiler: Abrir para leer (click para mostrar u ocultar)

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

En Spoiler:


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

En Spoiler:


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

En Spoiler:



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

En Spoiler:




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

En Spoiler:


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

En Spoiler:



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

En Spoiler:


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:


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


 :guiño: :guiño: :guiño:

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

(Continuamos en el siguiente post...)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #15 : 10/01/2013, 06:37:35 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:


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

En Spoiler:




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

En Spoiler:

Spoiler: Macro PRINTF_INFO_S7 (click para mostrar u ocultar)

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:

Spoiler: Macro PRINTF_INFO_M7 (click para mostrar u ocultar)


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:

Spoiler: Programa TestingIntegerMacros.c (click para mostrar u ocultar)


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #16 : 10/01/2013, 07:31:23 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.  :sorprendido: :sorprendido: :sorprendido: :sorprendido:

   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".  :lengua_afuera:
(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í.  :beso:

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

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

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

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

Spoiler: Programa TestingIntegers2.c (click para mostrar u ocultar)


 :sonrisa_amplia:

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

* 20130110TestingIntegers2.zip (4.79 KB - descargado 153 veces.)
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #17 : 15/01/2013, 12:56:59 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?  :llorando: :llorando: :llorando: ¡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 [texx]N[/texx], entonces no pueden representarse más que [texx]N[/texx] 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 ([texx]\sigma[/texx]), mantisa ([texx]\mu[/texx]) y exponente ([texx]r[/texx]).

   Así, un número de punto flotante tendrá la forma: [texx]\sigma \mu \cdot b^r[/texx], en donde [texx]b[/texx] representa la base de numeración utilizada (para las computadoras suele ser [texx]b=2[/texx], y para los humanos [texx]b=10[/texx]).
   La mantisa tiene esta forma: [texx]n.dddddd...[/texx], en donde queremos expresar que hay un único dígito [texx]n[/texx], 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 [texx]r[/texx] 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 [texx]1.7\cdot 10^{10} [/texx], 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 [texx]9.10952\cdot 10^{-28}[/texx], 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 [texx]10^{300}[/texx]).

   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:

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

   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:

[texx]\bullet[/texx]   Estudiar el estándar IEEE 754 de formatos de punto flotante, y conocer sus mínimos requerimientos obligatorios y sugerencias.
[texx]\bullet[/texx]   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.
[texx]\bullet[/texx]   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:

[texx]\bullet[/texx]  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.

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

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

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

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

[texx]\bullet[/texx]  http://gcc.gnu.org/c99status.html


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC



Organización

Comentarios y Consultas

* ieee754-1985-binary-only.pdf (49.53 KB - descargado 365 veces.)
* Goldberg-ieee_754-appendixD.pdf (699.37 KB - descargado 305 veces.)
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #18 : 15/01/2013, 02:32:30 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. 

[texx]\bullet[/texx]   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.
[texx]\bullet[/texx]   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.
[texx]\bullet[/texx]   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:

[texx]b[/texx]: base
[texx]p[/texx]: precisión
[texx]emin[/texx]: exponente mínimo
[texx]emax[/texx]: 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 [texx]0[/texx] a [texx]b-1[/texx] como dígitos.
   Si la base es [texx]b = 2[/texx] (binaria), estamos en el típico caso de dígitos binarios de las computadoras: 0 y 1.
   Si [texx]b = \textsf{diez}[/texx] (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: [texx]\sigma\cdot m \cdot b^r[/texx],  donde tenemos un signo [texx]\sigma[/texx], una mantisa [texx]m[/texx] y un exponente [texx]r[/texx].

   La precisión [texx]p[/texx] 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 [texx]p[/texx] es la cantidad de dígitos significativos.
   Los coeficientes [texx]e_{min}[/texx] y [texx]e_{max}[/texx] 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 [texx](b,p,emin,emax)[/texx], estará determinado por 3 números enteros [texx](s,m,r)[/texx], de la siguiente manera:

[texx]s[/texx]: signo
[texx]m[/texx]: mantisa (en realidad el estándar le llama significando)
[texx]q[/texx]: exponente

   Son tales que [texx]s=0[/texx] ó [texx]1[/texx], [texx]m[/texx] es un entero no negativo, y [texx]q[/texx] es un entero. Representan el número:

[texx]\bullet[/texx]         [texx](-1)^s\cdot m\cdot b^q[/texx]

   Obviamente, [texx]s=0[/texx] da signo positivo, y [texx]s=1[/texx] da signo negativo: [texx](-1)^0=+1, (-1)^1=-1[/texx].
   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...  :indeciso:

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

[texx]f = .d_1...d_{p-1}.[/texx]

   Observemos que todo número binario escrito en notación científica se puede escribir de forma normalizada, es decir, con [texx]d_0 = 1[/texx].
   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

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

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 [texx]f[/texx]. 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 [texx]p[/texx].

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

   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 [texx]d_1,...,d_{p-1}[/texx] son todos 0 y el exponente es [texx]e_{min}-1[/texx], se conviene que representa el valor matemático 0.
   Sin embargo, aquí hay ambigüedad, porque todavía hay un bit de signo.
   Si [texx]s = 0[/texx], se considera que el valor 0 es positivo, y se expresa [texx]+0[/texx], mientras que si [texx]s = 1[/texx], el valor 0 es negativo, y se expresa [texx]-0[/texx].
   La notación [texx]+0, -0,[/texx] 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 [texx]e \geq e_{min}[/texx], el número representado no es 0, sino [texx]\pm 2^e[/texx] (el signo depende, claro, del bit de signo).

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

   En cambio, [texx]\tilde e = 0[/texx] se usará para guardar los ceros signados y los números subnormales.

   El exponente descentrado [texx]\tilde e = H+1[/texx] 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.

[texx]\bullet[/texx]   En todo formato debe h aber "al menos" un valor [texx]-\infty[/texx] y uno [texx]+\infty[/texx].
[texx]\bullet[/texx]   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 [texx]e = e_{max}+1[/texx], que es lo mismo que decir que usan el exponente descentrado [texx]\tilde e = H+1[/texx].
   Más precisamente, si tenemos la terna [texx](s, m, e)[/texx] donde [texx]m = 0...0[/texx], [texx]e = e_{max}+1[/texx] (o sea, [texx]\tilde e = H+1[/texx]), el valor representado es [texx](-1)^s \infty[/texx].

   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 [texx](s, m, e)[/texx], donde [texx]m \neq 0...0[/texx], y [texx]e = e_{max+1}[/texx] (o sea, [texx]\tilde e =H+1[/texx]).
   El estándar no obliga a que el signo [texx]s[/texx] 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 [texx]m[/texx] (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 [texx]e = e_{max}+1[/texx], 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ó.  :indeciso:

   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 [texx](s, m, e)[/texx] 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) [texx]w[/texx] de un formato básico binario binaryXX es de XX bits, como su nombre indica.
   Se usa 1 bit para el signo, [texx]p[/texx] 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: [texx]p \geq 32, e_{max}\geq +1023, e_{min} \leq -1022, w_e \geq 11, w\geq 43.[/texx]
Extended Double precision: [texx]p \geq 64, e_{max}\geq +16383, e_{min} \leq -16382, w_e \geq 15, w\geq 79.[/texx]


   Hemos indicado con [texx]w_e[/texx] la cantidad de bits que se usa para representar el exponente.
   Con [texx]w[/texx] 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 [texx]p[/texx], el exponente máximo [texx]e_{max}[/texx], el ancho del exponente [texx]w_e[/texx], y valores máximos para el exponente mínimo [texx]e_{min}[/texx].
   El ancho total [texx]w[/texx] del formato será la suma de esos valores más 1 bit de signo: [texx]w = 1+p+w_e[/texx].

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

¿Son obligatorios los formatos básicos y extendidos?

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas
En línea

argentinator
Consultar la FIRMAPEDIA __________________________________________________________________________________________________________________
Administrador
Pleno*
*****

Karma: +0/-0
Desconectado Desconectado

Sexo: Masculino
Argentina Argentina

Mensajes: 7.275

Vean mis posts activos en mi página personal


Ver Perfil WWW
« Respuesta #19 : 16/01/2013, 08:31:45 pm »

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

   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 [texx]s[/texx] ([texx]= +1[/texx] ó [texx]-1[/texx]), una base [texx]b[/texx] (un entero [texx]> 1[/texx]), un exponente [texx]e[/texx] (un entero cuyo valor está entre dos valores [texx]e_{min}, e_{max}[/texx], [texx]e_{min}\leq e  \leq e_{max}[/texx]), una precisión [texx]p[/texx], unos dígitos [texx]f_1, f_2 ..., f_{p}[/texx] en base [texx]b[/texx] (es decir, enteros [texx]0\leq f_k\leq b-1[/texx]), definen un modelo [texx]x[/texx] de números de punto flotante, así:

[texx]x=s\cdot b^e\cdot \sum_{k=1}^{p} f_kb^{-k}.[/texx]

   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"  :guiño: :guiño: 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:

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

[texx]\bullet[/texx]   En n va una secuencia finita de dígitos decimales (del 0 al 9), que denotan la parte entera.
[texx]\bullet[/texx]   El . es el punto decimal (que separa la parte entera de la parte fraccionaria).
[texx]\bullet[/texx]   En d va una secuencia finita de dígitos decimales, que denotan la parte fraccionaria.
[texx]\bullet[/texx]   La letra e es el indicador de exponente, y puede usarse indistantemente e ó E.
[texx]\bullet[/texx]   El signo + es el signo del exponente, y puede ser [texx]+[/texx] ó [texx]-[/texx].
[texx]\bullet[/texx]   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 [texx]+[/texx] 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 [texx]+[/texx]), 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:

[texx]\bullet[/texx]   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.

[texx]\bullet[/texx]   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.

[texx]\bullet[/texx]   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.

[texx]\bullet[/texx]   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.

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

[texx]\bullet[/texx]   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.

[texx]\bullet[/texx]   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:

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

   El prefijo 0x ó 0X indica que la constante es hexadecimal:guiño: :guiño:
   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:  :sorprendido: :sorprendido: :sorprendido: :sorprendido: :sorprendido: 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  :llorando: :llorando: )

   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  :sorprendido: 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:

[texx]1.0p11: \qquad\qquad\qquad\quad = 2^{(11)_{10}}[/texx]                (double)
[texx]0x31FCp-17f: \qquad\qquad = (31FC)_{16}\cdot 2^{-(17)_{10}}[/texx] (float)
[texx]0xEF.CC1400A5p0L: \qquad = (EF.CC1400A5)_{16}[/texx] (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"  :guiño: 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 [texx]e = e_{max}+1[/texx], 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:

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

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

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

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

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

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

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas
En línea

Páginas: [1] 2 3 4   Ir Arriba
  Imprimir  
 
Ir a:  

Impulsado por MySQL Impulsado por PHP Powered by SMF 1.1.4 | SMF © 2006, Simple Machines LLC XHTML 1.0 válido! CSS válido!