Está en la página 1de 55

4 Conversin entre sistemas multibyte y de caracteres anchos Debido a que los caracteres anchos se emplean generalmente para representacin

interna, y los multibyte para la representacin externa, las operaciones de entrada/salida suelen implicar un proceso de conversin entre ambos sistemas de codificacin. Un caso frecuente lo constituyen los procesos de lectura y escritura de ficheros. Estos ltimos contienen generalmente caracteres multibyte, y cuando se lee un fichero, sus caracteres deben ser convertidos en caracteres anchos y situados en un almacenamiento interno donde puedan ser procesados. Evidentemente el sistema de caracteres anchos debe tener anchura suficiente para contener el carcter ms ancho del sistema multibyte. En cambio, todas las secuencias de escape pueden ser eliminadas. La figura adjunta [3] muestra esquemticamente el proceso de convertir un fichero de codificacin JIS al sistema Unicode de caracteres anchos.

Nota: La Librera Estndar C++ dispone de algunas herramientas para realizar este tipo de conversiones ( 5.2.1).

2.2.1a1 El carcter ancho


1 Introduccin Respecto al problema someramente esbozado en el apartado anterior ( 2.2.1a), de representar los caracteres de lenguas distintas del ingls americano. En los compiladores C y C++ se decidi utilizar un nuevo tipo, denominado carcter ancho o DBCS ("Double Byte Character Set"), en atencin a que como veremos a continuacin, la mayora de las veces ocupa dos Bytes. El carcter ancho se designa con la palabra wchar_t. En C se utiliz un typdef ( 3.2.1a, <stddef.h>) para definirlo, pero en C++ tom carta de naturaleza como un nuevo tipo preconstruido en el lenguaje, de forma que de ser un typedef pas a ser palabra clave.

2 wchar_t En C++ esta palabra clave identifica un tipo especial, el carcter ancho. Sintaxis: wchar_t Comentario El Estndar C++ nos dice que su tamao es dependiente de la implementacin, pero que debe ser suficiente para almacenar el juego de caracteres mayor que soporte el compilador. Como actualmente el juego de caracteres internacional es Unicode ( 2.2.1a2) que utiliza 2 bytes, este es el tamao mnimo del wchar_t de la mayora de compiladores. Nota: en el caso de Borland C++ es del mismo tamao, signo y alineacin que el tipo int, es decir 4 bytes (un wchar_t C es de solo 2 bytes), mientras que el wchar_t de MS Visual C++ es de 2 bytes. <identificador>;

Tenga en cuenta, que la librera Estndar C++ utiliza funciones distintas para manejar caracteres anchos y normales. Por ejemplo, la funcinprintf para caracteres tipo char, tiene una versin wprintf cuando se trata de representar caracteres tipo wchar_t; a su vez, strlen ("string length") tiene la versin wcslen ("wide-character string length") para caracteres anchos. wcout es la versin de cout para caracteres anchos. Etc. (existen dos versiones de todas aquellas funciones que manejan cadenas alfanumricas). Ejemplos wchar_t ch; // declaracin de carcter ancho wchar_t* str = L"ABCD"; // puntero a cadena de caracteres anchos wcout << "La cadena es: " << str << endl; // L.3 wcout << L"La cadena es: " << str << endl; // L.4 wchar_t cnulo = L'\0' // carcter nulo ancho Comentario: Las sentencias L.3 y L.4 son equivalentes; la salida en ambos casos es: La cadena es: ABCD

Observe que en L.3 el compilador es suficientemente inteligente para suponer que el literal " La cadena es: " es de caracteres anchos.

2.2.1a2 Codificaciones UCS/Unicode


1 Introduccin

Respecto al problema ya esbozado en el captulo anterior ( 2.2.1a), de representar los caracteres de lenguas distintas del ingls americano, se haban utilizado mltiples soluciones parciales, pero a finales de los 80 se hicieron intentos para definir un sistema de codificacin verdaderamente universal. De un lado el denominado UCS ("Universal Character Set"), preconizado por la organizacin internacional de estandarizacin ISO; de otro el denominado Unicode, desarrollado por la iniciativa privada (Microsoft principalmente). Esta compaa haba incorporado en sus sistemas Windows 9x un juego de caracteres bastante adaptado a la mayora de alfabetos occidentales. Posteriormente implement Unicode en sus sistemas NT y sucesores. Tanto UCS como Unicode pretendan eliminar el problema de una vez por todas, estableciendo un sistema de codificacin que permitiese la incorporacin los caracteres (grafos) de todas las lenguas escritas del mundo, tanto actuales como pasadas, as como los smbolos utilizados en matemticas, tipografa (por ejemplo los utilizados en TeX) y otros, como el juego de caracteres fonticos IPA ("International Phonetic Alphabet"); tipos usados frecuentemente para reconocimiento ptico de caracteres (OCR), etc. El sistema debera ser adems extensible, de forma que pudieran aadirse nuevos tipos en el futuro. Aunque el desarrollo inicial de ambos sistemas fue independiente, los respectivos comits se dieron cuenta enseguida que este camino seguira manteniendo el cisma que tanto dao haba causado a la informtica desde los primeros intentos de extender el US-ASCII, de forma que pronto convergieron, y el sistema Unicode se convirti en un subset del sistema UCS ms general.

2 UCS El sistema universal de caracteres UCS se concreta en el estndar ISO-10646 y se define como un 31 juego de caracteres de 31 bits. Por supuesto sus 2 posibilidades garantizan que pueda soportar con creces las amplsimas expectativas sealadas en el prrafo anterior. Sin embargo, como no es necesario utilizar un potencial tan desmesurado, se han definido subconjuntos. En realidad todos los espacios utilizados por el momento se agrupan en los 16 primeros bits. Este subconjunto es 16 conocido como BMP ("Basic Multilingual Plane") o Plano 0, y sus 2 (65.536) posibilidades son suficientes para los caracteres de todas las lenguas conocidas. Nota: en realidad, lo que se hizo fue copiar el conocidsimo juego de caracteres US-ACII en las 128 primeras posiciones. A continuacin, en las 128 siguientes, hasta completar 256, se colocaron los caracteres de la extensin ANSI/ISO 8859-1 (una variacin de los caracteres mostrados en la tabla 3.2a del captulo anterior). Sucesivamente se fueron reservando zonas para diversos alfabetos, por ejemplo, para el Griego; el Cirlico (utilizado en la Federacin Rusa y otros pases limtrofes); Armenio; Hebreo y los ideogramas Kanji (Japoneses), Hangeul (Coreanos) y del alfabeto Chino.

Los espacios situados fuera de este "Plano" estn reservados para los caracteres utilizados en aplicaciones muy especiales. Por ejemplo, los estudiosos de culturas antiguas o la notacin cientfica. El estndar ISO-10646-1 fue establecido en 1993, y le sigui una versin ISO-10646-2 en el 2001. La teora es que pueden aadirse nuevos caracteres segn las necesidades, pero los ya establecidos no se alterarn nunca ms, de forma que se pretende una plataforma estable. Adems se supone que no existirn nunca caracteres de ms de 21 bits, ya que mil trillones de 21 caracteres (2 ) sern suficientes.

3 Unicode Tcnicamente Unicode es el ISO 10646-1. As pues cada carcter ocupa 16 bits, lo que significa que puede estar representado por un carcter ancho ( wchar_t 2.2.1a1) de cualquier compilador C++. El inconveniente es que evidentemente, los almacenamientos ocupan el doble de espacio que con los caracteres estndar. La ventaja es que no existe necesidad de ninguna conversin ni ambigedad. Cada carcter, representado por un nmero, tiene una significacin inequvoca. Como ejemplo se incluye un trozo de cdigo tomado del fichero winnt.h, donde puede verse como la definicin de los typedef ( 3.2.1a) TCHAR yPTCHAR se realiza de forma distinta segn se est utilizando un juego de caracteres normales o Unicode. #ifdef UNICODE typedef WCHAR TCHAR, *PTCHAR; #else typedef char TCHAR, *PTCHAR; #endif Los programas Windows identifican la lengua mediante una constante manifiesta ( 1.4.1a) que es traducida por el preprocesador a una constante hexadecimal ( 3.2.3b). A su vez, las variaciones o dialectos locales ("sublenguajes" en la terminologa de estos compiladores) se identifican mediante otras constantes que son tambin traducidas a nmeros. Para dar idea de las lenguas soportadas, a continuacin se relacionan las constantes implementadas en el compilador MS VC++ 6.0

Lengua

Variedad

#define #define 0x36 #define #define #define #define #define #define #define 0x23 #define #define 0x02 #define #define #define #define #define

LANG_NEUTRAL 0x00 LANG_AFRIKAANS LANG_ALBANIAN 0x1c LANG_ARABIC 0x01 LANG_ARMENIAN 0x2b LANG_ASSAMESE 0x4d LANG_AZERI 0x2c LANG_BASQUE 0x2d LANG_BELARUSIAN LANG_BENGALI 0x45 LANG_BULGARIAN LANG_CATALAN 0x03 LANG_CHINESE 0x04 LANG_CROATIAN 0x1a LANG_CZECH 0x05 LANG_DANISH 0x06

#define SUBLANG_NEUTRAL 0x00 // language neutral #define SUBLANG_DEFAULT 0x01 // user default #define SUBLANG_SYS_DEFAULT 0x02 // system default #define SUBLANG_ARABIC_SAUDI_ARABIA 0x01 // Arabic (Saudi Arabia) #define SUBLANG_ARABIC_IRAQ 0x02 // Arabic (Iraq) #define SUBLANG_ARABIC_EGYPT 0x03 // Arabic (Egypt) #define SUBLANG_ARABIC_LIBYA 0x04 // Arabic (Libya) #define SUBLANG_ARABIC_ALGERIA 0x05 // Arabic (Algeria) #define SUBLANG_ARABIC_MOROCCO 0x06 // Arabic (Morocco)

#define #define #define #define #define #define #define #define #define #define #define #define #define #define 0x0e #define 0x0f #define 0x21 #define #define #define #define #define #define #define #define #define 0x27 #define 0x2f #define #define 0x4c #define #define #define #define 0x14 #define #define #define 0x16 #define #define #define #define

LANG_DUTCH 0x13 LANG_ENGLISH 0x09 LANG_ESTONIAN 0x25 LANG_FAEROESE 0x38 LANG_FARSI 0x29 LANG_FINNISH 0x0b LANG_FRENCH 0x0c LANG_GEORGIAN 0x37 LANG_GERMAN 0x07 LANG_GREEK 0x08 LANG_GUJARATI 0x47 LANG_HEBREW 0x0d LANG_HINDI 0x39 LANG_HUNGARIAN LANG_ICELANDIC LANG_INDONESIAN LANG_ITALIAN 0x10 LANG_JAPANESE 0x11 LANG_KANNADA 0x4b LANG_KASHMIRI 0x60 LANG_KAZAK 0x3f LANG_KONKANI 0x57 LANG_KOREAN 0x12 LANG_LATVIAN 0x26 LANG_LITHUANIAN LANG_MACEDONIAN LANG_MALAY 0x3e LANG_MALAYALAM LANG_MANIPURI 0x58 LANG_MARATHI 0x4e LANG_NEPALI 0x61 LANG_NORWEGIAN LANG_ORIYA 0x48 LANG_POLISH 0x15 LANG_PORTUGUESE LANG_PUNJABI 0x46 LANG_ROMANIAN 0x18 LANG_RUSSIAN 0x19 LANG_SANSKRIT 0x4f

#define SUBLANG_ARABIC_TUNISIA 0x07 // Arabic (Tunisia) #define SUBLANG_ARABIC_OMAN 0x08 // Arabic (Oman) #define SUBLANG_ARABIC_YEMEN 0x09 // Arabic (Yemen) #define SUBLANG_ARABIC_SYRIA 0x0a // Arabic (Syria) #define SUBLANG_ARABIC_JORDAN 0x0b // Arabic (Jordan) #define SUBLANG_ARABIC_LEBANON 0x0c // Arabic (Lebanon) #define SUBLANG_ARABIC_KUWAIT 0x0d // Arabic (Kuwait) #define SUBLANG_ARABIC_UAE 0x0e // Arabic (U.A.E) #define SUBLANG_ARABIC_BAHRAIN 0x0f // Arabic (Bahrain) #define SUBLANG_ARABIC_QATAR 0x10 // Arabic (Qatar) #define SUBLANG_AZERI_LATIN 0x01 // Azeri (Latin) #define SUBLANG_AZERI_CYRILLIC 0x02 // Azeri (Cyrillic) #define SUBLANG_CHINESE_TRADITIONAL 0x01 // Chinese (Taiwan) #define SUBLANG_CHINESE_SIMPLIFIED 0x02 // Chinese (PR China) #define SUBLANG_CHINESE_HONGKONG 0x03 // Chinese (Hong Kong) #define SUBLANG_CHINESE_SINGAPORE 0x04 // Chinese (Singapore) #define SUBLANG_CHINESE_MACAU 0x05 // Chinese (Macau) #define SUBLANG_DUTCH 0x01 // Dutch #define SUBLANG_DUTCH_BELGIAN 0x02 // Dutch (Belgian) #define SUBLANG_ENGLISH_US 0x01 // English (USA) #define SUBLANG_ENGLISH_UK 0x02 // English (UK) #define SUBLANG_ENGLISH_AUS 0x03 // English (Australian) #define SUBLANG_ENGLISH_CAN 0x04 // English (Canadian)

#define #define #define #define 0x24 #define #define #define #define #define #define #define #define #define 0x22 #define #define #define 0x2a

LANG_SERBIAN 0x1a LANG_SINDHI 0x59 LANG_SLOVAK 0x1b LANG_SLOVENIAN LANG_SPANISH 0x0a LANG_SWAHILI 0x41 LANG_SWEDISH 0x1d LANG_TAMIL 0x49 LANG_TATAR 0x44 LANG_TELUGU 0x4a LANG_THAI 0x1e LANG_TURKISH 0x1f LANG_UKRAINIAN LANG_URDU 0x20 LANG_UZBEK 0x43 LANG_VIETNAMESE

#define SUBLANG_ENGLISH_NZ 0x05 // English (New Zealand) #define SUBLANG_ENGLISH_EIRE 0x06 // English (Irish) #define SUBLANG_ENGLISH_SOUTH_AFRICA 0x07 // English (South Africa) #define SUBLANG_ENGLISH_JAMAICA 0x08 // English (Jamaica) #define SUBLANG_ENGLISH_CARIBBEAN 0x09 // English (Caribbean) #define SUBLANG_ENGLISH_BELIZE 0x0a // English (Belize) #define SUBLANG_ENGLISH_TRINIDAD 0x0b // English (Trinidad) #define SUBLANG_ENGLISH_ZIMBABWE 0x0c // English (Zimbabwe) #define SUBLANG_ENGLISH_PHILIPPINES 0x0d // English (Philippines) #define SUBLANG_FRENCH 0x01 // French #define SUBLANG_FRENCH_BELGIAN 0x02 // French (Belgian) #define SUBLANG_FRENCH_CANADIAN 0x03 // French (Canadian) #define SUBLANG_FRENCH_SWISS 0x04 // French (Swiss) #define SUBLANG_FRENCH_LUXEMBOURG 0x05 // French (Luxembourg) #define SUBLANG_FRENCH_MONACO 0x06 // French (Monaco) #define SUBLANG_GERMAN 0x01 // German #define SUBLANG_GERMAN_SWISS 0x02 // German (Swiss) #define SUBLANG_GERMAN_AUSTRIAN 0x03 // German (Austrian) #define SUBLANG_GERMAN_LUXEMBOURG 0x04 // German (Luxembourg) #define SUBLANG_GERMAN_LIECHTENSTEIN 0x05 // German (Liechtenstein) #define SUBLANG_ITALIAN 0x01 // Italian #define SUBLANG_ITALIAN_SWISS

0x02 // Italian (Swiss) #define SUBLANG_KASHMIRI_INDIA 0x02 // Kashmiri (India) #define SUBLANG_KOREAN 0x01 // Korean (Extended Wansung) #define SUBLANG_LITHUANIAN 0x01 // Lithuanian #define SUBLANG_LITHUANIAN_CLASSIC 0x02 // Lithuanian (Classic) #define SUBLANG_MALAY_MALAYSIA 0x01 // Malay (Malaysia) #define SUBLANG_MALAY_BRUNEI_DARUSSALAM 0x02 // Malay (Brunei Darussalam) #define SUBLANG_NEPALI_INDIA 0x02 // Nepali (India) #define SUBLANG_NORWEGIAN_BOKMAL 0x01 // Norwegian (Bokmal) #define SUBLANG_NORWEGIAN_NYNORSK 0x02 // Norwegian (Nynorsk) #define SUBLANG_PORTUGUESE 0x02 // Portuguese #define SUBLANG_PORTUGUESE_BRAZILIAN 0x01 // Portuguese (Brazilian) #define SUBLANG_SERBIAN_LATIN 0x02 // Serbian (Latin) #define SUBLANG_SERBIAN_CYRILLIC 0x03 // Serbian (Cyrillic) #define SUBLANG_SPANISH 0x01 // Spanish (Castilian) #define SUBLANG_SPANISH_MEXICAN 0x02 // Spanish (Mexican) #define SUBLANG_SPANISH_MODERN 0x03 // Spanish (Modern) #define SUBLANG_SPANISH_GUATEMALA 0x04 // Spanish (Guatemala) #define SUBLANG_SPANISH_COSTA_RICA 0x05 // Spanish (Costa Rica) #define SUBLANG_SPANISH_PANAMA 0x06 // Spanish (Panama) #define SUBLANG_SPANISH_DOMINICAN_REPUBLIC 0x07 // Spanish (Dominican Republic)

#define SUBLANG_SPANISH_VENEZUELA 0x08 // Spanish (Venezuela) #define SUBLANG_SPANISH_COLOMBIA 0x09 // Spanish (Colombia) #define SUBLANG_SPANISH_PERU 0x0a // Spanish (Peru) #define SUBLANG_SPANISH_ARGENTINA 0x0b // Spanish (Argentina) #define SUBLANG_SPANISH_ECUADOR 0x0c // Spanish (Ecuador) #define SUBLANG_SPANISH_CHILE 0x0d // Spanish (Chile) #define SUBLANG_SPANISH_URUGUAY 0x0e // Spanish (Uruguay) #define SUBLANG_SPANISH_PARAGUAY 0x0f // Spanish (Paraguay) #define SUBLANG_SPANISH_BOLIVIA 0x10 // Spanish (Bolivia) #define SUBLANG_SPANISH_EL_SALVADOR 0x11 // Spanish (El Salvador) #define SUBLANG_SPANISH_HONDURAS 0x12 // Spanish (Honduras) #define SUBLANG_SPANISH_NICARAGUA 0x13 // Spanish (Nicaragua) #define SUBLANG_SPANISH_PUERTO_RICO 0x14 // Spanish (Puerto Rico) #define SUBLANG_SWEDISH 0x01 // Swedish #define SUBLANG_SWEDISH_FINLAND 0x02 // Swedish (Finland) #define SUBLANG_URDU_PAKISTAN 0x01 // Urdu (Pakistan) #define SUBLANG_URDU_INDIA 0x02 // Urdu (India) #define SUBLANG_UZBEK_LATIN 0x01 // Uzbek (Latin) #define SUBLANG_UZBEK_CYRILLIC 0x02 // Uzbek (Cyrillic)

3 Webografa UTF-8 and Unicode FAQ for Unix/Linux www.cl.cam.ac.uk

A pesar de su ttulo, esta pgina de Markus Kuhn es una excelente gua acerca de los sistemas de codificacin en general.

Unicode.org

www.unicode.org/unicode/

Sin duda el sitio oficial es la mejor fuente de informacin. En la pgina "Code charts" pueden obtenerse las tablas de caracteres, ideogramas y signos de todas las lenguas del mundo.

2.2.2 Tipos derivados


1 Sinopsis Aparte de los tipos bsicos ( 2.2.1) y sus variantes, C++ soporta otros tipos de naturaleza compleja que derivan de aquellos. Dicho en otras palabras: C++ permite al usuario construir otros tipos a partir de los bsicos. De estos "nuevos" tipos, los de nivel de abstraccin ms alto son lasclases ( 4.11), incluyendo sus formas particulares (estructuras y uniones); tambin pueden crearse campos de bits; matrices y una familia de tipos muy importante: los punteros. Hay punteros a cada uno de los dems tipos: a carcter, a entero, a estructura, a funcin, a puntero, etc. ( 4.2). Los tipos derivados se construyen con palabras clave: class, struct, unin u operadores especiales como los siguientes: * *const & [] () puntero a puntero constante a referencia a array[1] de retorno de funcin 2.2.1), pueden declararse

Ejemplos: suponiendo que tipoX un tipo bsico o variante (no void tipos derivados tal como se muestra: tipoX t; tipoX arr[10]; elementos tipoX tipoX *ptr; tipoX &ref=t; tipoX func(void); acepta parmetros) void func(tipoX t); parmetro t tipoX (no devuelve struct st {tipoX t1; tipoX t2};

// t es un objeto de tipo tipoX // arr es una matriz de diez // ptr es un puntero a tipoX // ref es una referencia a tipoX // func devuelve un valor tipoX (no // func1 acepta un nada) // la estructura st alberga dos tipoX

Nota: Las expresiones: tipo& var, tipo &var y tipo & var son equivalentes.

2.2.3 Modificadores de tipo


1 Sinopsis Hemos sealado ( 2.2.1) que los modificadores opcionales que pueden acompaar a los tipos bsicos son: con signo, sin signo, largo ycorto; y que se aplican con las palabras clave. long, short, signed, unsigned. Por otra parte, la creciente implementacin de procesadores con longitud de palabra de 64 bits, hace suponer que pronto se extendern los tipos actuales incluyendo versiones extra-largas de los enteros ( Tipos extendidos).

2 long Sintaxis: long [int] <identificador> ; [long] double <identificador> ; Descripcin: Cuando se utiliza este modificador sobre un int, crea un tipo que dobla el espacio de almacenamiento utilizado para almacenar un int [1]. Cuando se utiliza para modificar un double, define un tipo de dato de coma flotante, long double, con 80 bits de precisin, no los 128 (2x64) que cabra esperar ( 2.2.4 Representacin interna y precisin). Existen los siguientes tipos de long: long x; long int x; signed long x; signed long int x; unsigned long x; unsigned long int x; // // // // // // x x x x x x es es es es es es signed long int signed long int signed long int signed long int unsigned long int unsigned long int

3 short Sintaxis: short int <identificador> ; Descripcin: El modificador short se utiliza cuando se desea una variable menor que un int. Este modificador solo puede aplicarse al tipo base int (si se omite el tipo base, se asume int por defecto). Existen los siguientes tipos:

short x; short int x; signed short x; signed short int x; unsigned short x; unsigned short int x; Ejemplos: short int i; short i;

// // // // // //

x x x x x x

es es es es es es

signed short int signed short int signed short int signed short int unsigned short int unsigned short int

// equivale a: "short int i;"

4 signed Sintaxis: signed <tipo> <identificador> ; Descripcin: El modificador de tipo signed define que el valor de una variable numrica puede ser positivo o negativo. Este modificador puede ser aplicado a los tipos bsicos int, char, long, short y __int64. Segn se indica en el ejemplo, si no se indica el tipo bsico, el modificador por si solo suponesigned int. El efecto que tiene este modificador, es que parte de la capacidad de almacenamiento del tipo base al que se aplica, se utiliza para especificar el signo, por lo que el mximo valor absoluto que es posible guardar es menor que el correspondiente valor unsigned del mismo tipo base ( 2.2.4Tipos de datos y representacin interna). Ejemplos: signed int i; int i; signed i; unsigned long int l; unsigned long l; signed char ch; unsigned char ch; // // // // // // // signed es valor por defecto (no es preciso) equivalente al anterior equivalente al anterior Ok equivale al anterior Ok Ok

5 unsigned Sintaxis: unsigned <tipo> <identificador> ; Descripcin:

Este modificador se utiliza cuando la variable sea siempre positiva. Puesto que no es necesario almacenar el signo, el valor absoluto puede ser mayor que en las versiones con signo. Puede aplicarse a los tipos base int, char, long, short e __int64. Cuando no se indica tipo base, por defecto se supone que se trata de un int (ver los ejemplos). Ejemplos: unsigned unsigned unsigned unsigned unsigned char i; i; long int l; long l; char ch; ch; int

// // // // //

equivale al anterior Ok Equivale al anterior Ok Equivale al anterior

6 Tipos enteros extendidos La progresiva utilizacin de procesadores de 64 bits, de los que pronto existirn versiones para ordenadores de sobremesa, junto con el crecimiento espectacular de la memoria disponible, tanto en RAM como en disco, hace que sea razonable esperar versiones de C++ que permitan utilizar enteros con ms de 32 bits. Entre otras razones, porque rpidamente sern normales almacenamientos de disco con ms capacidad de la que puede direccionarse directamente con palabras de 32 bits. En la actualidad se est trabajando en un estndar, conocido como C9x que se espera sea adoptado oficialmente en breve (2001). Esta versin incluye nuevos especificadores opcionales long long en versiones con y sin signo [2]. El cuadro adjunto muestra la propuesta existente para los citados modificadores. Como puede verse, siguiendo la tradicin, se supone int si no se indica otro tipo base. Adems por defecto long long se supone con signo. long long x; long long int x; signed long long x; signed long long int x; unsigned long long x; unsigned long long int x; // // // // // // x x x x x x es es es es es es signed long long int signed long long int signed long long int signed long long int unsigned long long int unsigned long long int

7 Extensiones C++Builder Este compilador C++ de Borland permite especificadores opcionales para los enteros, ms all de lo especificado en el estndar (alguno en lnea de los nuevos tipos que se esperan). Estos modificadores opcionales permiten definir el tipo de almacenamiento que se utilizar para el entero. Para utilizarlos es preciso usar tambin los sufijos que se indican en cada caso:

Tipo __int8

Sufijo Ejemplo i8
__int8 c = 127i8;

Almacenamiento 8 bits

__int16 __int32 __int64 unsigned __int64

i16 i32 i64 ui64

__int16 s = 32767i16; __int32 i = 123456789i32; __int64 big = 12345654321i64; unsigned __int64 hInt=1234567887654321ui64;

16 bits 32 bits 64 bits 64 bits

2.2.4 Tipos bsicos: representacin interna, rango


1 Sinopsis El ANSI C reconoce que el tamao y rangos de valor numrico de los tipos bsicos y sus varias permutaciones ( 2.2.1) dependen de la implementacin y generalmente derivan de la arquitectura del ordenador. La tabla adjunta muestra los tamaos y rangos de los tipos numricos de 32-bits de Borland C++ [1]. Nota: precisamente esta circunstancia, que el Estndar C++ impone relativa libertad en cuanto al tamao de los tipos, es la responsable de que an adhirindose estrictamente al Estndar, puedan existir problemas de portabilidad entre diversas plataformas de los programas C++ (algo que no ocurre con otros lenguajes de definicin ms estricta. Por ejemplo Java). Ver en 2.2.4c unas notas adicionales sobre los tipos bsicos.

2 Almacenamiento y rango Las explicaciones siguientes muestran como se representan internamente estos tipos (en negrita los tipos bsicos). Los ficheros de cabecera<climits> y <float.h> contienen definiciones de los rangos de valor de todos los tipos fundamentales.

Tipo unsigned char char (signed) short (signed) unsigned short

bits Rango / Tipo de uso 8 8 16 16

0 <= X <= 255 -128 <= X <= 127 ASCII [5]

Nmeros pequeos y juego caracteres del PC. Nmeros muy pequeos y juego de caracteres

-32,768 <= X <= 32,767 Nmeros muy pequeos, control de bucles pequeos 0 <= X <= 65,535 pequeos Nmeros muy pequeos, control de bucles

unsigned (int) int (signed) unsigned long enum long (int) float double long double

32 32 32 32 32 32 64 80

0 <= X <= 4,294,967,295.

Nmeros grandes

-2,147,483,648 <= X <= 2,147,483,647 Nmeros pequeos, control de bucles 0 <= X <= 4,294,967,295 Distancias astronmicas Conjuntos de valores

-2,147,483,648 <= X <= 2,147,483,647 ordenados

-2,147,483,648 <= X <= 2,147,483,647 Nmeros grandes


1.18e-38 <= |X| <= 3.40e38 Precisin cientfica ( 7-dgitos)

2.23e-308 <= |X| <= 1.79e308 3.37e-4932 <= |X| <= 1.18e4932

Precisin cientfica (15-dgitos) Precisin cientfica (18-dgitos)

Nota: las cuestiones de almacenamiento interno, como se almacenan los datos en memoria y cuanto espacio necesitan, estn influidas por otros factores adems de los sealados. Estas son las que podramos denominar "necesidades mnimas" de almacenamiento. En ocasiones, especialmente en estructuras y uniones, el compilador realiza determinados "ajustes" o "alineaciones" con los datos, de forma que las direcciones de memoria se ajustan a determinadas pautas. El resultado es que en estos casos, el tamao de por ejemplo una estructura, no corresponde con lo que tericamente se deduce de la suma de los miembros ( 4.5.9). Las caractersticas de esta "alineacin" pueden ser controladas mediante opciones del compilador ( 4.5.9a).

3 Enteros En C++ 32-bit, los tipos int y long son equivalentes, ambos usan 32 bits [3]. Las variedades con signo son todas almacenadas en forma de complemento a dos usando el bit ms significativo como bit de signo (0 positivo y 1 negativo), lo que explica los rangos indicados en la tabla. En las n versiones sin signo, se usan todos los bits, con lo que el nmero de posibilidades es 2 , y el rango n de valores est entre 0 y 2 -1, donde n es el nmero de bits de la palabra del procesador, 8, 16 o 32 (uno, dos, o cuatro octetos). El estndar ANSI C no define el tamao de almacenamiento de los diversos tipos, solamente indica que la serie short, int y long no es descendente, es decir: short <= int <= long. De hecho, legalmente los tres tipos pueden ser del mismo tamao. En muchas (pero no todas) las implementaciones de C y C++ un long es mayor que un int. Actualmente, la mayora de las aplicaciones de oficina y personales, con entornos como Windows

o Linux, corren sobre plataformas hardware de 32 bits, de forma que la mayora de los compiladores para estas plataformas utilizan un int de 32 bits (del mismo tamao que el long). En cualquier caso, los rangos vienen indicados por las constantes que se sealan (incluidas en <limits.h>): signed short: SHRT_MIN <= X <= SHRT_MAX.

Siendo: SHRT_MIN <= -32767 y SHRT_MAX >= 32767. Algunas implementaciones hacen SHRT_MIN = -32768 pero no es exigido por el estndar. unsigned short: 0 <= X <= USHRT_MAX.

Siendo: USHRT_MAX >= 65535. Las variedades short deben contener al menos 16 bits para que pueda cubrirse el rango de valores exigidos. En la mayora de los compiladores un short es menor que un int, de forma que algunos programas que deben almacenar grandes matrices de nmeros en memoria o en ficheros pueden economizar espacio utilizando short en lugar de int, pero siempre que se cumplan dos condiciones: 1. En la implementacin un short es realmente menor que un int. 2.- Los valores caben en un short. En algunas arquitecturas el cdigo empleado para manejar los short es ms largo y lento que el correspondiente para los int. Esto es particularmente cierto en los procesadores Intel x86 ejecutando cdigo de 32 bits en programas para Windows (NT/95/98), Linux y otras versiones Unix. En estos cdigos, cada instruccin que referencia a un short es un byte ms larga y generalmente necesita tiempo extra de procesador para ejecutarse. signed int: INT_MIN <= X <= INT_MAX.

Siendo: INT_MIN <= -32767 y INT_MAX >= 32767. Algunas implementaciones utilizan un valor INT_MIN = -32768 pero no es exigido en el estndar. unsigned int: 0 <= X <= UINT_MAX.

Siendo: UINT_MAX >= 65535. Para cubrir esta exigencia, los int deben ser de 16 bits por lo menos. El rango exigido para signed int y unsigned int es idntico que para los signed short y unsigned short. En compiladores para procesadores de 8 y 16 bits (incluyendo los Intel x86 ejecutando cdigo en modo 16 bits, como bajo MS DOS), normalmente un int es de 16 bits, exactamente igual que un short. En los compiladores para procesadores de 32 bit y mayores (incluyendo los Intel x86 ejecutando cdigo de 32 bits como Windows o Linux) generalmente un int es de 32 bits, exactamente igual que un long. signed long: LONG_MIN <= X <= LONG_MAX.

Siendo: LONG_MIN <= -2147483647 y LONG_MAX >= 2147483647. Existen implementaciones que hacen LONG_MIN = -2147483648 pero no es exigido por el estndar. unsigned long: 0 <= X <= ULONG_MAX.

Siendo: ULONG_MAX >= 4294967295. Para poder cubrir este rango, los tipos long deben ser de al menos 32 bits.

4 Nuevos tipos numricos Los rangos previstos para los nuevos tipos ( estndar son: signed long long: 3.2.3d) long long. que se proyectan incluir en el

LLONG_MIN <= X <= LLONG_MAX.

Siendo: LLONG_MIN <= -9223372036854775807 y LLONG_MAX >= 9223372036854775807. Algunas implementaciones hacenLLONG_MIN = -9223372036854775808 pero no es exigido. unsigned long long: 0 <= X <= ULLONG_MAX.

Siendo: ULLONG_MAX >= 18446744073709551615. Las variedades long deben ser de al menos 64 bits para poder cubrir el rango exigido. La diferencia entre enteros con signo y sin signo (signed y unsigned) es que en los primeros el bit ms significativo se usa para guardar el signo (0 positivo, 1 negativo), esto hace que los enteros con signo tengan un rango de valores posibles distinto que los unsigned. Vase al respecto el rango de int y unsigned int. Los enteros sin signo se mantienen en valores 0 positivos, dentro de la aritmtica de numeracin n de mdulo base 2, es decir 2 , donde n es el nmero de bits de almacenamiento del tipo, de forma 32 que, por ejemplo, si un int se almacena en 32 bits, unsigned int tiene un rango entre 0 y 2 -1 = 4,294,967,295 (el valor 0 ocupa una posicin de las 4.294.967.295 posibles).

5 Carcter La cabecera <limits.h> contiene una macro, CHAR_BIT, que se expande a una constante entera que indica el nmero de bits de un tipo carcter (char), que se almacenan en 1 byte, es decir, siempre ocurre que sizeof(char) == 1. Esta es la definicin ANSI de byte en C/C++, es decir, la memoria requerida para almacenar un carcter, pero este byte no corresponde necesariamente con el byte de la mquina. El valor de CHAR_BIT es al menos 8; la mayora de los ordenadores modernos usan bytes de 8 bits (octetos), pero existen algunos con otros tamaos, por ejemplo 9 bits. Adems algunos procesadores, especialmente de seal (Digital Signal Processors), que no pueden acceder de forma eficiente a la memoria en tamaos menores que la palabra del preprocesador, tienen un CHAR_BIT distinto, por ejemplo 24. En estos casos, los tipos char, short e int son todos de 24

bits, y long de 48 bits. Incluso son ms comunes actualmente procesadores de seal donde todos los tipos enteros incluyendo los long son de 32 bits. signed char: Valores entre: SCHAR_MIN <= X <= SCHAR_MAX, Siendo: SCHAR_MIN <= -127 y SCHAR_MAX >= 127. La mayora de los compiladores utilizan un valor SCHAR_MIN de -128, pero no es exigido por el estndar. unsigned char: Valores entre 0 <= x <= UCHAR_MAX.

Se exige que UCHAR_MAX >= 255. si CHAR_BIT es mayor que 8, se exige que UCHAR_MAX = CHAR_BIT 2 - 1. De forma que una implementacin que utilice un carcter de 9 bits puede almacenar valores entre 0 y 511 en un unsigned char. char (valor carcter a secas "plain char"). Valores entre CHAR_MIN <= X <= CHAR_MAX

Si los valores char son considerados signed char por defecto: CHAR_MIN == SCHAR_MIN y CHAR_MAX == SCHAR_MAX. Si los valores char son considerados unsigned char por defecto: CHAR_MIN == 0 y CHAR_MAX == UCHAR_MAX. Por ejemplo, un trozo del fichero limits.h que acompaa al compilador Microsoft Visual C++ 2008, tiene el siguiente aspecto: #define #define #define #define CHAR_BIT 8 SCHAR_MIN (-128) SCHAR_MAX 127 UCHAR_MAX 0xff /* /* /* /* number of bits in a char */ minimum signed char value */ maximum signed char value */ maximum unsigned char value */

6 Fraccionarios La representacin y rango de valores de los nmeros fraccionarios depende del compilador. Es decir, cada implementacin de C++ es libre para definirlos. La mayora utiliza el formato estndar de la IEEE (Institute of Electrical and Electronics Engineers) para este tipo de nmeros ( 2.2.4a). float y double son tipos fraccionarios de 32 y 64 bits respectivamente. El modificador long puede utilizarse con el tipo double, declarando entonces un nmero fraccionario de 80 bits. En C++Builder las constantes fraccionarias, que pueden ser float, double y long double, tienen los rangos que se indican:

Tipo float

bits Rango 32 1.17549e-38 <= |X| <= 3.40282e+38

double

64

2.22507e-308 <= |X| <= 1.79769e+308 3.37e-4932 <= |X| <= 1.18e4932

long double 80

Generalmente los compiladores C++ incluyen de forma automtica la librera matemtica de punto flotante si el programa utiliza valores fraccionarios [4]. Builder utiliza los siguientes lmites, definidos en el fichero <values.h> float. Valores entre: MINFLOAT <= |X| <= MAXFLOAT (valores actuales entre parntesis) MINFLOAT (1.17549e-38)

MAXFLOAT (3.40282e+38) FEXPLEN Nmero de bits en el exponente (8) FMAXEXP Valor mximo permitido para el exponente (38) FMAXPOWTWO Mxima potencia de dos permitida (127) FMINEXP Valor mnimo permitido para el exponente (-37) FSIGNIF Nmero de bits significativos. (24) double. Valores entre MINDOUBLE <= |X| <= MAXDOUB double. Valores entre MINDOUBLE MINDOUBLE <= |X| <= MAXDOUBLE

(2.22507e-308)

MAXDOUBLE (1.79769e+308) DEXPLEN Nmero de bits en el exponente (11) DMAXEXP Valor mximo permitido para el exponente (308) DMAXPOWTWO Mxima potencia de dos permitida (1023) DMINEXP Valor mnimo permitido para el exponente (-307) DSIGNIF Nmero de bits significativos (53)

7 La clase numeric_limits La Librera Estndar C++ contiene una clase numeric_limits que contiene informacin sobre los escalares. Existen subclases para cada tipo fundamental, enteros (incluyendo los booleanos) y fraccionarios. Esta clase engloba la informacin contenida en los ficheros de cabecera < climits> y <cfloat>; adems de incluir informacin que no est contenida en ninguna otra cabecera. A continuacin se incluye un ejemplo que muestra el espacio disponible (bits) para codificar el valor de los tipos fundamentales (mantisa), as como la salida proporcionada en un caso concreto. #include <cstdlib> #include <iostream> #include <limits> int main(int argc, char *argv[]) { std::cout << "Mantisa de un char: "

<< std::numeric_limits<char>::digits << '\n'; std::cout << "Mantisa de un unsigned char: " << std::numeric_limits<unsigned char>::digits << '\n'; std::cout << << std::cout << << std::cout << << std::cout << << std::cout << << std::cout << << std::cout << << std::cout << << std::cout << << std::cout << << std::cout << << return 0; } Salida en una mquina Intel con el compilador GNU g++ 3.4.2 para Windows: Mantisa de un char: 7 Mantisa de un unsigned char: 8 Mantisa de un short: 15 Mantisa de un unsigned short: 16 Mantisa de un int: 31 Mantisa de un unsigned int: 32 Mantisa de un long: 31 Mantisa de un unsigned long: 32 Mantisa de un float: 24 Longitud de un double: 53 Longitud de un long double: 64 Mantisa de un long long: 63 Mantisa de un unsigned long long: 64 "Mantisa de un short: " std::numeric_limits<short>::digits << '\n'; "Mantisa de un unsigned short: " std::numeric_limits<unsigned short>::digits << '\n'; "Mantisa de un int: " std::numeric_limits<int>::digits << '\n'; "Mantisa de un unsigned int: " std::numeric_limits<unsigned int>::digits << '\n'; "Mantisa de un long: " std::numeric_limits<long>::digits << '\n'; "Mantisa de un unsigned long: " std::numeric_limits<unsigned long>::digits << '\n'; "Mantisa de un float: " std::numeric_limits<float>::digits << '\n'; "Longitud de un double: " std::numeric_limits<double>::digits << '\n'; "Longitud de un long double: " std::numeric_limits<long double>::digits << '\n'; "Mantisa de un long long: " std::numeric_limits<long long>::digits << '\n'; "Mantisa de un unsigned long long: " std::numeric_limits<unsigned long long>::digits << '\n';

Temas relacionados: Operador sizeof ( Alineacin interna ( 4.9.13) 4.6.1).

2.2.4a Formas de representacin binaria de las magnitudes numricas


1 Presentacin de un problema Antes de entrar en detalles, haremos un pequeo inciso para sealar el principal problema que entraa la representacin de cantidades numricas en los ordenadores digitales. En el apartado dedicado al Ordenador digital ( 0.1) recordamos que la informacin est representada en forma digitalizada. Es decir, reducida a cantidades discretas representadas por nmeros y estos a su vez, expresados en formato binario. Como la serie de los nmeros reales tiene infinitos nmeros (desde -Infinito a +Infinito [0]), es imposible su representacin completa en cualquier sistema de representacin. Adems, aunque un nmero puede contener una cantidad indefinida de cifras, los bits destinados a almacenarlas son necesariamente limitados [3]. Como consecuencia, en la informtica real solo es posible utilizar un subconjunto finito del conjunto de los nmeros reales. El rango y precisin (nmero de cifras) del subconjunto de nmeros que pueden representarse en una mquina dada, dependen de la arquitectura, y para el lenguaje C++, depende adems del compilador ( 2.2.4). Puesto que existen ocasiones en que las aplicaciones informticas necesitan manejar nmeros muy grandes y muy pequeos, se ha derrochado mucho ingenio para conseguir representaciones binarias con la mxima precisin en el mnimo espacio, y para que estos formatos puedan ser manipulados por implementaciones hardware lo ms simples posible. Tambin ha sido necesario ingeniar artificios para detectar y prevenir situaciones en las que un resultado se sale por arriba o por abajo del rango permitido, al tiempo que se mantiene el mximo de precisin en los clculos. Hay que recordar que, incluso manejando cantidades dentro del rango, pueden presentarse fcilmente situaciones con errores de bulto que seran catastrficas en determinadas circunstancias. Por ejemplo, en clculos de ingeniera. Supongamos una situacin en que el compilador C++ tiene que multiplicar una serie de cantidades definidas en la mxima precisin: long double r = x * y * z; y que el orden de ejecucin x * y * z es en este caso de izquierda a derecha. Si en un momento dado los valores de x e y son suficientemente pequeos (prximos al lmite inferior permitido para long double), el primer producto x * y puede resultar inferior al mnimo que puede representar el compilador, originndose un "underflow". El resultado intermedio sera cero, y su producto por z tambin cero, con independencia del valor de esta ltima variable (que suponemos grande). El valor cero del resultado r podra a su vez propagarse inadvertidamente a otros clculos. Observe tambin, que si la operacin hubiese sido programada en otro orden. Por ejemplo: long double r = x * z * y; Tal vez el error no hubiese llegado a presentarse, dando la sensacin que el cdigo es seguro con los mismos valores de las variables. No es necesario sealar que este tipo de errores pueden acarrear consecuencias desastrosas. Por ejemplo, en clculos de ingeniera. Para que el lector pueda formarse visin ms tangible del problema, le invito a visitar esta interesante pgina (en ingls) Some disasters attributable to bad numerical computing.

Otros tipos de errores de precisin son ms insidiosos an. Para comprobarlo pruebe el lector este sencillo programa: #include <iostream.h> int main (void) { float f = 3.0/7.0; if (f == 3.0/7.0) cout << "Igual" << endl; else cout << "Desigual" << endl; return 0; } La salida, con el compilador Borland C++ 5.5 es Desigual La explicacin es que aqu las constantes 3.0 y 7.0 han sido consideradas como nmeros de coma flotante de doble precisin (double), y el resultado de 3.0/7.0, que es del mismo tipo, sufre una conversin estrechante ( 4.9.9) a float, con prdida de precisin, antes de la asignacin a f en M1. El mismo valor 3.0/7.0 (double) es comparado en M2 con el de f (float), con el resultado de que no son iguales. La comprobacin de las afirmaciones anteriores es muy sencilla: basta modificar la lnea M1 del programa por alguna de estas dos: double f = 3.0/7.0; long double f = 3.0/7.0; // M1.1: // M1.2:

// M1: // M2:

Despus de la sustitucin, en ambos casos la salida es Igual. Si deseamos mantener la variable f como un float, una posible solucin sera cambiar la sentencia M2 (intente encontrar la explicacin por s mismo en 3.2.3c): if (f == 3.0f/7.0f) cout << "Igual" << endl; // M2.1:

En el apartado que dedicamos a las conversiones estndar ( porqu no funcionara ninguna de las versiones siguientes: if (f == 3.0f/7.0) cout << "Igual" << endl; if (f == 3.0/7.0f) cout << "Igual" << endl;

2.2.5), encontrar explicacin del

// M2.2: // M2.3:

2 Formas de representacin binaria La necesidad de representar no solo enteros naturales (positivos), sino tambin valores negativos e incluso fraccionarios (racionales), ha dado lugar a diversas formas de representacin binaria de los nmeros. En lo que respecta a los enteros, se utilizan principalmente cuatro:

Binario sin signo Binario con signo Binario en complemento a uno Binario en complemento a dos .

Lo relativo a los fraccionarios se indica ms adelante

2.1 Cdigo binario sin signo: Las cantidades se representan de izquierda a derecha (el bit ms significativo a la izquierda y el menos significativo a la derecha) como en el sistema de representacin decimal. Los bits se representan por ceros y unos; cero es ausencia de valor, uno es valor. Por ejemplo, la representacin del decimal 33 es 100001. Si utilizamos un octeto para representar nmeros pequeos, y mantenemos la costumbre de separar las cifras en grupos de 4 para mejorar la legibilidad, su representacin es: 0010 0001. Con este sistema todos los bits estn disponibles para representar una cantidad; por consiguiente, un octeto puede albergar nmeros de rango 0 <= n <= 255 Nota: aunque la representacin interna (en memoria) suele tener el bit ms significativo a la izquierda, el almacenamiento en dispositivos externos (disco) no tiene que ser forzosamente igual. Existen casos en los que la representacin externa es justamente al contrario, el bit ms significativo a la derecha, lo que supone que estos bytes deben ser invertidos durante los procesos de lectura/escritura. Existen casos en que una misma aplicacin sigue distintos criterios para la alineacin del bit ms significativo segn el tipo de dato que se escribe en el disco. Por supuesto la situacin se complica cuando el nmero est representado por ms de un octeto. En este caso tambin puede jugarse con el orden de escritura de los octetos. Vase al respecto Orden de Almacenamiento ( 2.2.6a)

2.2 Cdigo binario con signo Ante la necesidad de tener que representar enteros negativos, se decidi reservar un bit para representar el signo. Es tradicin destinar a este efecto el bit ms significativo (izquierdo); este bit es 0 para valores positivos y 1 para los negativos. Por ejemplo, la representacin de 33 y -33 sera: +33 -33 0010 0001 1010 0001

Como en un octeto solo quedan siete bits para representar la cantidad, con este sistema un Byte puede representar nmeros en el rango: - 127 <= n <= 127

El sistema anterior se denomina cdigo binario en magnitud y signo. Aparentemente es el

primero y ms sencillo de los que se pueden discurrir, adems de ser muy simple para codificar y decodificar. Sin embargo, la circuitera electrnica necesaria para implementar con ellos operaciones aritmticas es algo complicada, por lo que se dispusieron otros sistemas que se revelaron ms simples en este sentido.

2.3 Cdigo binario en complemento a uno En este sistema los nmeros positivos se representan como en el sistema binario en magnitud y signo, es decir, siguiendo el sistema tradicional, aunque reservando el bit ms significativo, que debe ser cero. Para los nmeros negativos se utiliza el complemento a uno, que consiste en tomar la representacin del correspondiente nmero positivo y cambiar los bits 0 por 1 y viceversa (el bit ms significativo del nmero positivo, que es cero, pasa ahora a ser 1). En captulo dedicado a los Operadores de manejo de bits ( 4.9.3), veremos que C++ dispone de un operador especfico para realizar estos complementos a uno. Como puede verse, en este sistema, el bit ms significativo sigue representando el signo, y es siempre 1 para los nmeros negativos. Por ejemplo, la representacin de 33 y -33 sera: +33 -33 0010 0001 1101 1110

2.4 Cdigo binario en complemento a dos: En este sistema, los nmeros positivos se representan como en el anterior, reservando tambin el bit ms significativo (que debe ser cero) para el signo. Para los nmeros negativos, se utiliza un sistema distinto, denominado complemento a dos, en el que se cambian los bits que seran 0 por 1 y viceversa, y al resultado se le suma uno. Este sistema sigue reservando el bit ms significativo para el signo, que sigue siendo 1 en los negativos. Por ejemplo, la representacin de 33 y -33 sera: +33 -33 0010 0001 1101 1110 + 0000 0001 1101 1111

El hardware necesario para implementar operaciones aritmticas con nmeros representados de este modo es mucho ms sencillo que el del complemento a uno, por lo que es el sistema ms ampliamente utilizado [8]. Precisamente esta forma de representacin interna es la respuesta al problema presentado en la pgina adjunta ( Problema) Nota: el manual Borland C++ informa que los tipos enteros con signo, tanto los que utilizan dos octetos (16 bits) como los que utilizan una palabra de 4 Bytes (32 bits), se representan internamente en forma de cdigo binario en complemento a dos (Fig. 1). Precisamente los procesadores Intel 8088, sus descendientes y compatibles, almacenan

internamente los nmeros en esta forma, y para facilitar la rpida identificacin del signo, disponen de un bit (SF) en el registro de estado ( H3.2) que indica si el resultado de una operacin tiene a 1 o a 0 el bit ms significativo.

3 Nmeros fraccionarios A continuacin exponemos brevemente los detalles del formato utilizado para representacin interna de los nmeros fraccionarios. Es decir, cmo son representados en forma binaria los nmeros con decimales.

3.1 Notacin cientfica En ciencias puras y aplicadas, es frecuente tener que utilizar nmeros muy grandes y muy pequeos. Para facilitar su representacin, se desarroll la denominada notacin cientfica (tambin denominada engineering notation en la literatura inglesa) en la que el nmero es representado mediante dos cantidades, la mantisa y la caracterstica, separadas por la letra E/e. Nota: en esta notacin las letras E/e no tienen nada que ver con la constante e (2.71828182...) base de los logaritmos Neperianos. Es meramente un smbolo para separar dos partes de una expresin (podra haberse utilizado cualquier otro). La mantisa es la parte significativa del nmero (las cifras significativas que se conocen [5] ). La caracterstica es un nmero entero con signo, que indica el nmero de posiciones que hay que desplazar a la derecha o a la izquierda el punto decimal (explcito o implcito). Por la razn sealada (que la caracterstica indica la posicin del punto decimal), esta representacin es tambin conocida como de "punto flotante". La caracterstica puede ser interpretada tambin como la potencia de 10 por la que hay que multiplicar la mantisa para obtener el nmero. Es decir: si V es el nmero, m la mantisa, y c la c caracterstica, resulta: V = m . 10 . Esta notacin (matemtica tradicional) es equivalente a V = mec= mEc en notacin cientfica Ejemplos: Expresin 23.45e6 -2e-5 3E+10 -.09E34 Valor 23.45 10^6 == 23450000 -2.0 10^-5 == -0.00002 3.0 10^10 == 30000000000 -0.09 10^34 == -900000000000000000000000000000000

3.1.1 Notacin normalizada Puede verse que la notacin cientfica permite varias formas para un mismo nmero. Por ejemplo, para el nmero 12.31 seran, entre otras: 12.31e0 1231e-2 123.1e-1 1.231e1

0.1231e2 0.01231e3 La representacin de nmeros fraccionarios que necesita de una menor cantidad de dgitos en notacin cientfica, es aquella que utiliza un punto decimal despus de la primera cifra significativa de la mantisa. Esta forma de representacin se denomina normalizada (el resto de formas posibles se denominan subnormales). En el caso del nmero anterior, la notacin normalizada sera: 1.231e1. Nota: observe que en esta forma el exponente es mnimo, y representa la utilizacin de la mxima cantidad de cifras significativas en la mantisa, de forma que para una cantidad de cifras determinada, es la que permite mayor precisin.

Segn lo anterior, la mantisa m de la forma normalizada de un nmero distinto de cero, puede expresarse como suma de una parte entera j y otra fraccionaria f, m = j + f. Siendo j un dgito decimal distinto de cero (1-9), y f una cantidad menor que la unidad denominada fraccin decimal. De forma el nmero puede ser expresado mediante: V = 0 (j + f) 10
c

7.1.1a
1

En el caso del ejemplo esta representacin sera: + (1+ 0.231) 10 . Nota: cuando el nmero est representado en binario la mantisa tambin puede ser representada en la forma m = j + f, siendo ahora j un dgito binario distinto de cero (que solo puede ser 1), el denominado bit-j. Desde luego f sigue siendo una cantidad menor que la unidad, aunque en este caso representada en binario (una fraccin binaria). Si asumimos que la representacin est siempre precedida de un 1, este bit puede suponerse implcito, y ocupar su posicin para expresar un bit adicional de la fraccin. Esta representacin se denomina de significando normalizado y supone que solo se almacena la fraccin decimal f de la mantisa (como puede ver, se trata de aprovechar al mximo el espacio disponible). La expresin binaria equivalente a la anterior (7.1.1a) es: V = 0 (1+ f) 2
c

7.1.1b

3.2 Representacin binaria La informtica, que en sus comienzos estaba nutrida por profesionales de otras disciplinas tcnicas y cientficas, adopt una variacin de la notacin cientfica para representacin interna (binaria) de las cantidades fraccionarias. Por esta razn, es costumbre que los nmeros fraccionarios sean denominados de coma o punto flotante [1] ("floating-point") y a las operaciones aritmticas realizadas con ellos, operaciones de punto flotante FLOP ("FLoating -point- OPeration"). Para los nmeros de "punto flotante", se ha asignando un bit para el signo; un cierto nmero de bits para representar el exponente y el resto para representar la parte ms significativa del nmero (la mantisa), aunque en este caso, la caracterstica no se refiere a una potencia de diez sino de dos. Es decir: un valor V puede ser representado por su mantisa m y su c caracterstica c mediante: V = m . 2 . As pues, la representacin binaria de los nmeros fraccionarios utiliza tres componentes:

Signo S es un nmero binario de un bit representando el signo (0 == positivo, 1 == negativo). Generalmente es el bit ms significativo (de la izquierda). Exponente c es un nmero binario representando la potencia de 2 por la que hay que multiplicar la mantisa. Cuanto mayor pueda ser este exponente, mayor ser el valor absoluto del mayor nmero que puede ser representado. Mantisa m es un nmero binario que representa las cifras significativas del nmero. Por supuesto, cuanto mayor sea la precisin deseada (ms cifras significativas conocidas), mayor debe ser el espacio destinado a contener esta parte.

Consideramos los bits numerados de derecha a izquierda, de 0 a N-1 (siendo N el nmero total de bits que se utilizar en la representacin). El signo est representado por el ltimo bit (bit N-1). A continuacin le siguen los bits destinados al significando y finalmente los del exponente. Si se destinan e bits para contener al exponente (representados E), y m para contener la mantisa (representados M), el esquema de almacenamiento es: <--------------- N --------------> Espacio total de almacenamiento (bits) S EEEEEEEE MMMMMMMMMMMMMMMMMMMMMMM Distribucin 1 <-- e -> <---------- m --------> Longitud de campos | | | | | N-1m+e m m-1 0 Numeracin de los bits

Es interesante observar que los desplazamientos ("Shift") izquierdo o derecho ( 4.9.3) de los bits de la mantisa, equivalen respectivamente a multiplicar o dividir por dos su valor, lo que podra compensarse disminuyendo o aumentando el valor del exponente en una unidad. Para evitar ambigedades se recurre a la normalizacin ya sealada , de forma que se minimiza el valor del exponente, y cualquier valor V (distinto de cero) puede ser representado mediante la fraccin normalizada f de su mantisa (f # 0), con lo que puede ser representado en la forma: V = 2 (1 + f)
c

Desgraciadamente no existe una absoluta unidad de criterio respecto a los detalles. Segn el Estndar, la representacin (interna) y rango de valores de los nmeros fraccionarios depende del compilador ( 2.2.4). Cada implementacin C++ es libre para definir los detalles. Por ejemplo, que espacio dedica a almacenar el exp, y cuanto a la mantisa; como se representa el cero, Etc [2]. Como consecuencia, existen diferencias en algunos aspectos del comportamiento de los compiladores que pueden llegar a ser cruciales. Por ejemplo, cuando presentan errores de overflow o undeflow.

Nota: el compilador C++Builder utiliza tres tamaos distintos para los nmeros fraccionarios de 32, 64 y 80 bits respectivamente, segn el formato de la IEEE . La representacin interna es la indicada en la figura 2.

3.2.1 Problemas de la representacin binaria de las cantidades fraccionarias La representacin binaria de punto flotante utilizada en los computadores digitales, es muy eficiente y se adapta bastante bien a la mayora de las circunstancias, especialmente en clculos tcnicos y cientficos (aritmtica de punto flotante). Sin embargo, no est exenta de problemas, derivados del hecho de que -como hemos sealado al principio del captulo- las posibilidades (finitas) de representacin del ordenador no pueden cubrir la totalidad (infinita) de los nmeros reales. Esta dificultad es especialmente molesta en los clculos denominados "de gestin", "comerciales" o "financieros" que utilizan nmeros fraccionarios de base 10. Por ejemplo clculos de precios, de conversin de moneda o del resultado de varias pesadas. Este tipo de aplicaciones utilizan (o deberan utilizar) lo que se denomina aritmtica decimal (que realizamos habitualmente con un papel y un lpiz) en la que, por ejemplo, 111.567 - 111 = 0.567. Cuando en los programas C/C++ se utilizan variables fraccionarias para almacenar este tipo de variables (nmeros fraccionarios de base 10), se presentan problemas que, en principio, suelen desconcertar al principiante. Como botn de muestra, incluimos el mensaje de un usuario en un foro de Visual C++, titulado "A very serious bug in MS Visual C++" (evidentemente el usuario est bastante desconcertado con los resultados obtenidos y como suele ser normal en estos casos, echa la culpa al compilador). Try the next code: double a=111.567, b=111, c; c=a-b; // and you will receive // // a=111.56699999999999 // b=111.00000000000000 // c=0.56699999999999307 // // instead => a=111.567, b=111, c=0.567; I found more fractional numbers that show a similar error. The problem is that the fractional numbers and their actions can not be produced otherwise. I try this example in all MS Visual C/C++ compilers from version 6.0 to version 2008 and the bug appears everywhere.
Regards.

Mejor que puedan hacerlo mis palabras, en la pgina Decimal Arithmetic FAQ de Mike Cowlishaw de IBM, encontrar el lector una amplia explicacin del porqu de estos aparentemente errneos resultados. Como sntesis indicaremos aqu que, para prevenir estos problemas, algunos lenguajes incluyen un tipo especial de variable "decimal" y funciones y operadores especficos que permiten realizar clculos de aritmtica decimal. En lo que respecta a C++, debido a sus orgenes "cientficos", por el momento no dispone de forma nativa de ningn tipo decimal por lo que las aplicaciones que necesitan de estos de clculos deben recurrir a libreras especficas. Nota: aunque por el momento (Septiembre 2008) el lenguaje C++ no dispone de ningn tipo decimal, el comit de estandarizacin ya est trabajando en una especificacin que se ajusta

al estndar IEEE 754R (ver Decimal Types for C++). Seguramente se definirn tres tipos decimales de punto flotante de 32, 64 y 128 bits respectivamente. Tambin es previsible que, del mismo modo que los procesadores modernos incluyen unidades hardware (FPU) para clculos con nmeros de punto flotante de codificacin binaria, en un futuro prximo se implementen tambin en hardware unidades para clculos con nmeros de punto flotante de codificacin decimal, ya que las rutinas software actuales para tratar la aritmtica decimal son considerablemente ms lentas que las de aritmtica binaria.

3.3 El Estndar IEEE 754 En 1985, el IEEE (Institute of Electrical and Electronics Engineers IEEE Standards Site) public la norma IEEE 754. Una especificacin relativa a la precisin y formato de los nmeros de "punto flotante". Incluye una lista de las operaciones que pueden realizarse con dichos nmeros, entre las que se encuentran las cuatro bsicas: suma, resta, multiplicacin, divisin. As como el resto, la raz cuadrada y diversas conversiones. Tambin incluye el tratamiento de circunstancias excepcionales, como manejo de nmeros infinitos y valores no numricos. Nota: en Junio de 2008 se aprob una revisin de dicho Estndar, conocido como IEEE 754R, que incluye recomendaciones para la aritmtica de punto flotante de codificacin decimal. La explicacin que sigue se refiere exclusivamente a la codificacin de nmeros de punto flotante de codificacin binaria (versin inicial del estndar). Dado que la mayora de compiladores utilizan este formato para la representacin de los nmeros fraccionarios, es ms que probable que el informtico se tope con ellos en alguna ocasin, por lo que dedicaremos unas lneas a describir sus caractersticas principales [7]. En realidad, la adopcin de este estndar por parte de los compiladores se debe a que el hardware que los sustenta tambin lo sigue. De hecho, esta es la representacin interna utilizada por los procesadores, ya que en la actualidad (2002) prcticamente el 100% de las mquinas que se fabrican siguen el Estndar en lo que se refiere al tratamiento y operacin de los nmeros de "punto flotante". El proceso de estandarizacin de las operaciones de punto flotante comenz paralelamente al desarrollo por Intel (1976) de lo que seran los coprocesadores aritmticos 8087. A partir de entonces poda asegurarse que X + (Y + Z) proporcionara el mismo resultado que (X + Y) + Z con cualquier compilador y cualquier terna de nmeros. No olvidemos que es precisamente a partir de la aparicin de los coprocesadores matemticos, cuando la realizacin de operaciones con nmeros fraccionarios se encomiendan al silicio (hardware), en vez de a rutinas software que, hasta entonces, eran especficas de cada compilador y cada plataforma [9]. Los coprocesadores matemticos, denominados tambin FPUs (Floating-Pount Units) comenzaron siendo circuitos integrados (opcionales) que se insertaban en la placa base junto al procesador principal [4]. Por ejemplo los 8087, 80287 y 80387 de Intel (este ltimo fue el primero que proporcion soporte completo para la versin final del Estndar). A partir del 80486, Intel incorpor el coprocesador matemtico junto con el principal, con lo que su existencia dej de ser opcional y se convirti en estndar. Estas "unidades de punto flotante" no solo realizan las operaciones aritmticas bsicas (suma resta, multiplicacin y divisin). Tambin incluyen operaciones como la raz cuadrada, redondeo, resto, y funciones trascendentes como seno, coseno, tangente, cotangente, logaritmacin y exponenciacin.

3.3.1 Formatos

En lo referente a la representacin binaria de los nmeros, el Estndar utiliza tres formatos denominados de precisin simple (equivalente al floatC++); doble (equivalente al double) y extendida (que podra corresponder al long double), aunque existe un cuarto, denominado de cudruple precisin, no contemplado en la norma que es tambin un estndar de facto. Los tamaos son los siguientes:

Precisin Simple Doble Extendida Cudruple

Bytes 4 8 >= 10 16

bits 32 64 >= 80 128

En todos los casos se utilizan tres campos para describir el nmero: El signo S, el exponente k y el significando (mantisa) n, que se almacenan en ese orden en memoria (no en los registros del procesador). El signo S, se almacena como es usual en un bit (0 significa positivo, 1 negativo). El exponente k se almacena en forma de un nmero binario con signo segn una regla que, como veremos a continuacin, depende del rango y del formato. El significando n se almacena en forma normalizada, salvo cuando se representan significados especiales (ver a continuacin).

El esquema de la distribucin utilizada para los de simple y doble precisin es el indicado.

Espacio (bits): 1 <-- 8 -> <-------- 23 ---------> Simple precisin: S EEEEEEEE MMMMMMMMMMMMMMMMMMMMMMM posicin: 31 30 23 22 0

Espacio (bits): 1 <--- 11 --> <-------------------- 52 -------------------------> Doble precisin: S EEEEEEEEEEE MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM posicin: 63 62 51 52 0

Como veremos a continuacin, la interpretacin de los patrones de bits contenidos en el exponente y en el significando sigue reglas algo complicadas. El motivo es que del espacio total de posibilidades se han reservado algunas para significados especiales y circunstancias excepcionales, que es necesario considerar para prevenir los errores e imprecisiones aludidas al principio del captulo . Por ejemplo, se considera la existencia de valores especiales: +Infinito; -

Infinito; NaN ("Not a Number") y una representacin especial para el valor cero, lo que ha obligado a definir reglas especiales de aritmtica cuando estos valores intervienen en operaciones con valores normales o entre ellos . A lo anterior se aade que existen dos tipos de representacin para los valores no especiales, cada uno con sus reglas; son las denominadas formas normalizadas y subnormales. Empezaremos por la representacin de los significados especiales.

3.3.2 Significados especiales Definicin del cero: puesto que el significando se supone almacenado en forma normalizada , no es posible representar el cero (se supone siempre precedido de un 1). Por esta razn se convino que el cero se representara con valores 0 en el exponente y en elsignificando. Ejemplo: 0 00000000 00000000000000000000000 = +0 1 00000000 00000000000000000000000 = -0 Observe que en estas condiciones el bit de signo S an permite distinguir +0 de -0. De hecho, el compilador lo hace as, permitiendo distinguir divisiones por cero con resultado +4 y -4 . Sin embargo el Estndar establece que al comparar ambos "ceros" el resultado debe indicar que son iguales. Infinitos: se ha convenido que cuando todos los bits del exponente estn a 1 y todos los del significando a 0, el valor es +/- infinito (segn el valor S). Esta distincin ha permitido al Estndar definir procedimientos para continuar las operaciones despus que se ha alcanzado uno de estos valores (despus de un overflow). Ejemplo: 0 11111111 00000000000000000000000 = +Infinito 1 11111111 00000000000000000000000 = -Infinito Valores no-normalizados (denominados tambin "subnormales"). En estos casos no se asume que haya que aadir un 1 al significado para obtener su valor. Se identifican porque todos los bits del exponente son 0 pero el significado presenta un valor distinto de cero (en caso contrario se tratara de un cero). Ejemplo: 1 00000000 00100010001001010101010 Valores no-numricos: Denominados NaN ("Not-a-number"). Se identifican por un exponente con todos sus valores a 1, y unsignificando distinto de cero. Existen dos tipos QNaN ("Quiet NaN") y SNaN ("Signalling NaN"), que se distinguen dependiendo del valor 0/1 del bit ms significativo del significando. QNaN tiene el primer bit a 1, y significa "Indeterminado", SNaN tiene el primer bit a 0 y significa "Operacin no-vlida". Ejemplo: 0 11111111 10000100000000000000000 = QNaN 1 11111111 00100010001001010101010 = SNaN

3.3.3 Significados normales La representacin de nmeros no incluidos en los casos especiales (distintos de cero que no sean infinitos ni valores no-numricos), sigue reglas distintas segn la precisin y el tipo de representacin (normal o subnormal). Para calcular el valor V de un nmero binario IEEE 754 de exponente E y mantisa M, debe recordarse que esta ltima representa una fraccin binaria (no decimal ;-) en notacin normalizada . Es decir, hay que sumarle una unidad. En estas condiciones, si por ejemplo, el contenido de la mantisa es 0.254 se supone que M = 1 + 0.254. Por su parte el clculo de la fraccin binaria es anlogo al de la fraccin decimal. Recordemos que la fraccin decimal 1304 1 2 3 4 (0.1304) equivale a 1/10 + 3/10 + 0/10 + 4/10 . Del mismo modo, la fraccin binaria 1101 1 2 3 4 (0.1101) equivale a 1/2 + 1/2 + 0/2 + 1/2 = 0.8125. Teniendo en cuenta estas observaciones, el valor decimal V de una representacin binaria estndar, puede calcularse mediante las siguientes frmulas: Nota: en las frmulas que siguen puede suponerse sustituido el signo por la expresin (S 1) . Donde S es el valor del bit de signo; cero si es positivo y 1 si es negativo. Si es un problema real, en el que es preciso calcular el valor correspondiente a un binario que sigue el estndar (por ejemplo, los datos recibidos de un instrumento de medida), adems de las consideraciones anteriores, tambin hay que tener en cuenta el orden ("Endianness") en que pueden recibirse los datos ( 2.2.6a).

3.3.3a Simple precisin, representacin normalizada: V == (1 + M) 2


E-127

Es evidente que en estos casos E es un nmero tal que 0 < E < 255 (2 - 2 posibilidades), ya que en caso contrario se estara en alguno de los significados especiales (todos los bits del exponente a 0 o a 1). As pues, E se mueve en el intervalo 1 a 254 (ambos inclusive) Al restarle 127 queda un -126 127 rango entre 2 y2 . Ejemplos: 0 00001100 11010000000000000000000 Signo = +; E = 12; M = 1/2 + 1/2 + 0/2 + 1/2 + 0 + 0 + ... = 0.8125 V = + (1 + 0.8125) 2
12-127 1 2 3 4

= 1.8125 2

-115

= 4.3634350 10

-35

1 10001101 01101000000000000000000 Signo = -; E = 141; M = 0/2 + 1/2 + 1/2 + 0/2 + 1/2 + 0 + ... = 0.40625 V = - (1 + 0.40625) 2
141 1 2 3 4 5

= - 1.40625 2

14

= - 23040

3.3.3b Simple precisin, representacin subnormal: V == (0 + M) 2


-127

Como se ha sealado , en estos casos es E = 0, y M es distinto de cero. La operatoria es anloga al caso anterior. Ejemplo: 0 00000000 11010000000000000000000 Signo = +; E = 0; M = 1/2 + 1/2 + 0/2 + 1/2 + 0 + 0 + ... = 0.8125 V = + 0.8125 2
-127 1 2 3 4

= 4.77544580 10

-39

3.3.3c Doble precisin, representacin normalizada: V == (1 + M) 2


E-1023

En estos casos es 0 < E < 2047. En caso contrario se estara en alguno de los significados especiales (todos los bits del exponente a 0 o a 1). La operatoria es anloga a la de simple precisin, con la diferencia de que en este caso se dispone de ms espacio para representar la mantisa M y el exponente E (52 y 11 bits respectivamente).

3.3.3d Doble precisin, representacin subnormal: V == (0 + M) 2


-1023

En estos casos es E = 0, y M es distinto de cero. La operatoria es anloga a la sealada en casos anteriores.

3.3.4 Conversor automtico de formatos Con objeto de facilitar al lector la realizacin de algunos ejemplos que le permitan terminar de comprender y comprobar estas reglas, en la pgina adjunta se incluye un convertidor automtico de formatos que permite introducir un nmero en formato decimal (incluso en notacin cientfica), y comprobar el aspecto de su almacenamiento binario segn el Estndar IEEE 754 en simple y doble precisin ( Conversor). Nota: en la librera de ejemplos ( 9.4.1) se incluye un programa C++ que realiza la misma conversin que el anterior (realizado en javascript), aunque est limitado a la representacin de nmeros en precisin simple.

3.3.5 Operaciones con nmeros especiales La tabla adjunta establece las reglas que, segn el Estndar IEEE 754, rigen las operaciones en que intervienen magnitudes de significado especial.

Operacin: cualquiera con NaN

Resultado NaN

n / Infinito Infinito * Infinito Infinito + Infinito Infinito - Infinito Infinito * 0 Infinito / Infinito 0 / 0 n / 0

0 Infinito Infinito NaN NaN NaN NaN Infinito

3.3.6 Rango de la representacin IEEE 754 Exceptuando los valores especiales infinitos, est claro que para la simple precisin, los valores mnimos y mximos que pueden representarse de forma estandarizada son: Vmin = - (0 + M) 2 representacin es
-127

, donde M sea el valor mnimo de la mantisa distinto de cero. Su

1 00000000 00000000000000000000001 Traduccin: Signo = E=0 23 -23 -7 M = 1/2 = 2 = 1.19209289551 10 -23 -127 -150 -46 Vmin = 2 2 =2 = 7.00649232163 10 En la prctica solo se consideran las representaciones normales, de forma que la forma normal ms pequea corresponde a la siguiente representacin binaria: 1 00000001 00000000000000000000001 Traduccin: Signo = E=1 23 -23 M = 1/2 = 2 -23 1-127 -23 -126 -38 Vmin = -(1 + 2 ) 2 = -(1 + 2 ) 2 = -1.17549449 10 Es significativo que el prximo valor en escala ascendente es 1 00000001 00000000000000000000010 Signo = -; E=1 22 -22 M = 1/2 = 2

V = -(1 + 2 ) 2

-22

-126

La diferencia entre ambos es: Imin = V - Vmin = 2 - 2 = 1.192092 10 , lo que representa algo ms de una parte en 10 millones. Es importante recordar que esta ser la mejor precisin que podr alcanzarse en los procesos con nmeros de coma flotante de simple precisin. En la prctica la precisin alcanzada ser an menor, dependiendo de la suerte que tengamos y del nmero de operaciones encadenadas que se hayan realizado (los errores pueden ser aleatorios -que tienden a anularse entre s- o acumulativos).

-22

-23

-7

El valor mximo en la representacin normal, corresponde a la forma binaria 0 11111110 11111111111111111111111 Signo = + E = 254 1 2 23 M = 1/2 + 1/2 + ... + 1/2 = 0.9999999999 254-127 127 38 Vmax = (1 + 0.999999) 2 = (1.99999999) 2 = 3.40282346 10

2.2.4b Formas de representacin simblica


1 Sinopsis En el epgrafe dedicado al Ordenador Electrnico Digital ( 0.1), se seal que para la representacin de los datos textuales (alfanumricos) se utilizan los sistemas de codificacin UsASCII y Unicode. El lenguaje y el sistema de escritura varan, pero desde el punto de vista del programador C++, el texto de sus programas fuente es siempre texto plano (sin formatear) codificado en Us-ASCII ( 2.2.1a), que es el sistema exigido como entrada por los compiladores ( 1.4). Sin embargo, la representacin simblica de datos numricos (como aparecen representados estos nmeros en el texto del cdigo fuente), no siempre ocurre en formato decimal, el sistema de numeracin "Occidental", como cabra esperar. Por una larga tradicin informtica, de cuando las consolas de entrada de los ordenadores eran exclusivamente numricas, adems del sistema decimal, se conservan otras dos formas de codificacin numrica: hexadecimal y octal. Cualquier cantidad numrica entera, puede ser representada en el texto del programa C++ en cualquiera de los sistemas citados. Adems, las funciones de salida de la propia Librera Estndar, tambin permite que tales cantidades puedan ser expresadas en cualquiera de estos formatos. Sin embargo, salvo caso de programas antiguos o que se trate de direcciones de memoria, es raro encontrar otras formas de expresin distintas de la decimal que es mucho ms legible. Por su parte, las cantidades numricas fraccionarias (de punto flotante) se representan siempre en formato decimal. Nota: en la exposicin que sigue, nos referimos exclusivamente a la representacin de cantidades numricas en el Fuente (desde el punto de vista del programador). Cuestin esta que no tiene nada que ver con el formato de entrada/salida para las cantidades numricas en tiempo de ejecucin (como las ve el usuario del programa).

2 Formato decimal Poco hay que decir respecto a este formato, de base 10, utiliza las cifras 0 a 9. Las cantidades fraccionarias utilizan el punto en vez de la coma. Salvo el propio cero (0), las cantidades expresadas no pueden empezar por cero, porque seran confundidas con el formato octal (afortunadamente el cero octal y el decimal coinciden). Ejemplos: int x = 12, y = 0; float y = 3.14, z = .16; En ocasiones, cuando hay posibilidad de confusin, los textos informticos aaden una "d" al final de las cantidades enteras decimales. Por ejemplo, 125d; 0125 y 125h son cantidades distintas (ver a continuacin). Cuando se trata de representar cantidades decimales muy grandes o muy pequeas, es posible tambin utilizar la notacin decimal cientficacomentada en el captulo anterior ( 2.2.4a). Por ejemplo: float f = 2.54E20; double d = -1.55E-200; long double ld = 2.33E-480;

3 Formato hexadecimal Este sistema de codificacin numrica utiliza un sistema de numeracin de base 16 ( E0.1w2). Como el sistema arbigo solo posee diez cifras (del 0 al 9), las restantes se complementan con letras del alfabeto, de la 'A' a la 'F'. C++ permite la utilizacin indistinta de maysculas y minsculas para representar cantidades en este formato, aunque es ms frecuente la utilizacin de maysculas. Es la forma tradicional de representar direcciones de memoria. La representacin de estos nmeros debe ir precedido de 0x 0X, para indicar al compilador que lo que sigue es formato hexadecimal. Tambin es costumbre representar estas cantidades en grupos de 8 dgitos (aadiendo ceros a la izquierda). Ejemplo: int x = 0xFF, y = 0x000000FF;

En ocasiones los textos informticos aaden una "h" al final de las cantidades hexadecimales. Por ejemplo, 125h sera equivalente a 0x125, aunque la primera notacin no puede ser utilizada en los fuentes de los programas C++.

4 Formato octal Utiliza un sistema de numeracin de base 8, por lo que utiliza las cifras del sistema arbigo 0 a 7. Cualquier representacin octal que utilice los dgitos 8 o 9 es un error. La representacin octal

de estos nmeros debe ir precedido por el 0 (cero), para indicar al compilador que lo que sigue es octal. Ejemplo: int x = 0377, y = 0377634; // ojo: cantidades en octal

5 Ejemplo resumen #include <iostream.h> int main() { int x = 255, y = 0377, z = 0x000000FF; cout << "Direccion de x: " << &x << endl; cout << "Direccion de x: " << long(&x) << endl; cout << "Valor de x: " << x << endl; cout << "Valor de y: " << y << endl; cout << "Valor de z: " << z << endl; } Salida: Direccion de x: 0065FE00 Direccion de x: 6684160 Valor de x: 255 Valor de y: 255 Valor de z: 255 Como puede verse en L.4, la forma estndar utilizada por el compilador para presentar direcciones de memoria, es hexadecimal y con maysculas; en L.5 se ha incluido un "casting" ( 4.9.9) para forzar una salida en formato decimal (ms legible) de la misma direccin. Nota: en el captulo dedicado a la representacin de Constantes Numricas ( incluyen detalles adicionales sobre la forma de utilizar estos formatos. 3.2.3b) se

// L.4: // L.5:

Tamao de los tipos bsicos C++


1 Sinopsis En lo tocante al tamao de los tipos bsicos, el Estndar C++ es bastante liberal y establece muy pocas directivas al respecto. Cosa que no ocurre en otros lenguajes. Por ejemplo, Java. Es precisamente esta falta de concrecin, uno de los puntos ms oscuros en cuanto a la portabilidad del lenguaje. Una de las razones de esta "permisividad" es que en el diseo del C y C++ se prim sobre todo la velocidad de ejecucin. Esta libertad para elegir dentro de ciertos lmites el tamao de los tipos, facilita que los constructores de compiladores puedan adecuar los tipos a las caractersticas de cada hardware. Por ejemplo, el tamao de un char se supone que es el ms adecuado para

manipular caracteres en una mquina determinada, mientras que el de un int debera ser el ms adecuado para almacenar y manipular enteros en la misma mquina. Los tamaos se definen siempre como mltiplos del tamao de un char, as que el tamao de este es siempre 1, sizeof (char) == 1, y no existen tamaos del tipo 3.5 char por ejemplo. As pues, en lo que se refiere al tamao de los tipos en C++, la unidad de medida es el tamao de char. En las expresiones que siguen 1 significa justamente esto. Respecto al tamao de los tipos bsicos C++, en realidad, las nicas asunciones ciertas que se pueden hacer son las siguientes: 1 == char <= short <= int <= long 1 <= bool <= long char <= wchar_t <= long float <= double <= long double X == signed X == unsigned X donde X puede ser: char, short int, int o long int. Ademsse garantiza que: 8 bits <= char 16 bits <= int 32 bits <= long Los aspectos especficos de los tipos bsicos en cada implementacin estn contenidos en la plantilla numeric_limits que puede encontrarse en el fichero <limits>. Los ficheros de cabecera <climits> y <float.h> contienen definiciones de los rangos de valor de todos los tipos fundamentales.

2.2.5 Conversiones estndar


1 Presentacin El tema de las conversiones de tipo es uno de los puntos que generalmente se le reprochan a C++. Una divisin de tipos no excesivamente rgida, o simplemente permisiva como la del C++ tiene sus ventajas, aunque tambin sus inconvenientes. Hemos sealado ( 1.2) que despus de la premisa fundamental de diseo: Potencia y velocidad de proceso, otra de las caractersticas de su antecesor C, es la de ser permisivo; "Intentando hacer algo razonable con lo que se haya escrito", lo que incluye naturalmente el asunto de los tipos. Aunque C++ dispone de mecanismos de comprobacin ms robustos en este sentido, de alguna forma "hereda" la tradicin de su antecesor. El resultado es un nuevo frente para el programador que debe prestar atencin al

asunto. En especial porque muchas de estas conversiones de tipo son realizadas por el compilador sin que el programador tenga constancia explcita de ello. En ocasiones este "automatismo" es realmente una comodidad; en otras es origen de problemas y quebraderos de cabeza.

2 Conversiones estndar Se denominan conversiones estndar a determinadas conversiones de tipo que en ocasiones realiza espontneamente el compilador para ajustar el tipo utilizado por el programador con las necesidades del momento. Estas conversiones se refieren casi siempre a tipos bsicos preconstruidos en el lenguaje ( 2.2), y pueden clasificarse en alguno de los supuestos que se relacionan a continuacin (existen unas pocas conversiones que afectan a los tipos abstractos y son tratadas en el siguiente captulo 2.2.5a). Algunas de ellas, denominadas conversiones triviales, se realizan entre tipos que son muy parecidos, hasta el extremo que para ciertas cuestiones no se consideran tipos distintos. Por ejemplo, para la sobrecarga de funciones ( 4.4.1a). Conversin nula: no existe conversin. Conversiones triviales o Conversin de tipo a referencia ( T T&) o Conversin de referencia a tipo ( T& T) o Conversin de matriz a puntero ( T[ ] T*) . o Conversin de funcin a puntero-a-funcin ( T(arg) T(*)(arg) ) o Conversin de calificacin de tipo ( 2.2) Tipo a constante ( T const T ) Tipo a volatile ( T volatile T ) Puntero-a-tipo a puntero-a-tipo constante ( T* cons T* ) puntero-a-tipo a puntero-a-tipo volatile ( T* volatile T* ) Conversin de Lvalue a Rvalue. Conversiones y promociones entre tipos numricos Conversiones a puntero Conversiones a booleano .

Ejemplo: cuando se utiliza una expresin aritmtica, como a + b, donde a y b son tipos numricos distintos, el compilador realiza espontneamente ciertas conversiones de tipo antes de evaluar la expresin. Estas conversiones incluyen la promocin de los operandos de tipo ms bajo a tipos ms altos, a fin de mejorar la homogeneidad y la precisin del resultado ( 2.2.4 Precisin y rango). En ocasiones la conversin de un tipo a otro exige la realizacin de una secuencia de varias de las conversiones estndar anteriores. Ejemplo: en la definicin char* cptr = "ABC"; para el compilador la expresin de la derecha es de tipo matriz-de-const char ( 3.2.3f), que es convertida a puntero-a-const char. Posteriormente, una segunda conversin (de calificacin) transforma el puntero-a-cons char en puntero-a-char.

Las conversiones estndar se realizan siempre porque las circunstancias exigen un tipo (de destino o final), y los tipos disponibles son distintos. Esto puede ocurrir en diversos contextos: Cuando se realizan sobre los operandos de operadores son las exigencias del operador las que dictan el tipo de destino. Cuando se realizan en la expresin de condicin de una sentencia if ( 4.10.2) o de iteracin do...while ( 4.10.3) el tipo de destino es un booleano ( 3.2.1b). Cuando se realizan en sentencias switch de seleccin ( 4.10.2) el tipo de destino es un entero. Cuando se utiliza en el Rvalue de una asignacin, el tipo de destino es el del Lvalue. Cuando se utiliza en los argumentos de una funcin o en el valor devuelto por esta, el tipo de destino es el establecido en la declaracin de la funcin.

A su vez existen contextos en los que las conversiones automticas se impiden expresamente. Por ejemplo, la conversin de Lvalue a Rvalue no se realiza en el operando del operador & ( 4.9.11) de referencia.

Para que una expresin exp pueda ser convertida implcitamente a un tipo T, es condicin necesaria que pueda existir un objeto temporal t tal que la asignacin T t = exp sea correcta.

3 Conversiones entre tipos numricos Dentro de este epgrafe consideramos en realidad varios tipos de conversiones: Promociones a entero Promociones a fraccionario . Conversiones entre asimilables a entero . Conversiones entre tipos fraccionarios . Conversiones fraccionario entero .

3.1 Promociones a entero. Comprende las siguientes conversiones: Un Rvalue de los tipos char, signed char, unsigned char, short int, o unsigned short int puede ser convertido a un Rvalue de tipo int si en la implementacin un int puede contener todos los valores de los tipos a convertir. En caso contrario son convertidos a unsigned int. Un Rvalue del tipo wchar_t ( 2.2.1a1) o un enumerador ( 3.2.3g) pueden ser convertidos a un Rvalue del primero de los tipos: int;unsigned int; long, o unsigned long, que pueda representar el valor correspondiente. Un Rvalue de tipo campo de bits ( 4.6) puede ser convertido al primero de los tipos int o unsigned int capaz de representar el rango de valores posibles del campo de bits. En caso contrario no se realiza ninguna promocin. Un Rvalue de tipo lgico (bool) puede ser promovido a un Rvalue tipo int. La regla es que false se transforma en cero, y true en 1 ( 3.2.1b).

3.2 Promocin a tipo fraccionario Los Rvalues de tipo float o long pueden ser promovidos a Rvalue de tipo double. Este tipo de promocin se denomina tambin de punto flotante.

3.3 Conversiones entre asimilables a entero Cualquiera de los asimilables a entero ( 2.2.1) pueden ser convertido a otro tipo asimilable a entero. Las conversiones permitidas bajo el epgrafe anterior (promociones a entero) estan excluidas de las que se consideran aqu. Un Rvalue de tipo enumeracin puede ser convertido a un Rvalue de tipo entero. La conversin de un entero largo a entero corto trunca los bits de orden superior, manteniendo sin cambios el resto. La conversin de un entero corto a largo, pone a cero los bits extra del entero largo y/o los correspondientes al signo, dependiendo que el entero corto fuese con o sin signo. La asignacin de un carcter con signo (signed char) a un entero, origina la adopcin del signo. Los caracteres con signo siempre utilizan signo. Los caracteres sin signo (unsigned char) siempre ponen a cero el bit ms significativo cuando son asignados a enteros. Si el tipo de destino es signed, el valor origen permanece sin cambio si puede ser representado en el tipo destino (manteniendo el ancho del campo de bits). En caso contrario, el valor depende de la implementacin [3]. Si el tipo de destino es bool la conversin se efecta segn se indica ms adelante . Si por el contrario el tipo origen es bool, las reglas son las indicadas en la promocin a entero: false se transforma en cero, y true en 1.

3.4 Conversiones fraccionario <=> entero Los tipos fraccionarios (de punto flotante) pueden ser promovidos a cualquier tipo asimilable a entero. Para ello se elimina la parte fraccionaria (decimal). Si la parte entera no cabe en el tipo de destino, el resultado es indefinido. Si el tipo de destino es un bool se siguen las pautas indicadas . A su vez los tipos enteros y las constantes de enumeracin pueden ser promovidos a fraccionarios. Si la conversin es posible (lo que ocurre efectivamente en la mayora de las implementaciones) el resultado es exacto. En algunos casos el valor del entero no puede ser representado exactamente por el fraccionario, lo que acarrea una prdida de precisin. En tal caso, el valor fraccionario adoptado es uno de los dos valores ms prximos posibles (por arriba y por abajo) del valor entero. Si el tipo origen es un booleano, false se transforma en cero, y true en 1.

3.5 Conversiones aritmticas estndar, reglas de conversin A continuacin se exponen los pasos que sigue C++ durante la conversin de operandos en las expresiones aritmticas. El resultado de la expresin es del mismo tipo que uno de los operandos: 1.- Cualquier tipo entero es convertido segn se muestra en la tabla.

Tipo char

convierte a Mtodo de conversin seguido int Con o sin signo (dependiente del tipo char por defecto) Siempre rellena con cero el byte ms significativo Siempre un signed int Mismo valor; signed int

unsigned char int signed char short int int

unsigned short unsigned int Mismo valor; rellena con ceros el byte ms significativo enum int El mismo valor

2.- Despus de esto, cualquier par de valores asociados con un operador son: Un int (incluyendo sus variedades long y unsigned), Un fraccionario de cualquiera de sus tres variedades: double, float o long double.

3.- A partir de este momento, la homogenizacin de tipos se realiza ahora siguiendo los patrones que se indican (en el orden sealado) Algn operando es long double el otro es convertido en long double. Algn operando es double el otro es convertido en double. Algn operando es float el otro es convertido en float. Algn operando es unsigned long el otro es convertido en unsigned long. Algn operando es long el otro es convertido en long. Algn operando es unsigned el otro es convertido en unsigned. Ambos aperandos son de tipo int.

Observaciones: Generalmente las funciones matemticas (como las incluidas en <math.h>) esperan argumentos en doble precisin (double 2.2.1), pero hay que tener en cuenta que las variables float no son convertidas automticamente a double, y por supuesto, los double tampoco son convertidos automticamente a float (supondra una prdida de precisin). Ver un ejemplo comentado en ( 2.2.4a). Sobre la forma de convertir double a float, o cualquier tipo a otro, ver el operador de modelado de tipos ( 4.9.9).

3.6 Precauciones

Las conversiones aritmticas son unos de los puntos en que el programador C++ debe prestar especial atencin si no quiere dispararse accidentalmente en los pies ( 1), y donde el lenguaje puede gastarnos insidiosas jugarretas. Como ejemplo mostramos una funcin prevista para calcular la inversa de cualquier entero que se pase como argumento: void inverso (int x) { float f = 1/x; cout << "X = " << x << " 1/x = " << f << endl; }

La funcin se obstina en devolver siempre cero como resultado de la inversa de cualquier entero. El compilador Borland C++ no muestra la menor advertencia de que estemos haciendo nada mal y aparentemente el valor 1/x debe ser promovido a float, con lo que tenemos garantizado que el resultado puede ser fraccionario. Si una cuestin como esta se presenta cualquier da que estemos especialmente cansados, puede mandarnos directamente a limpiar cochineras a Carolina del Norte. Con un poco de suerte y descanso, quizs caigamos en la cuenta que la promocin se produce "despus" que se haya efectuado la divisin, y que esta considera todava como "enteros" a los miembros implicados (la constante 1 y el argumento x), con lo que el cociente, que es siempre menor que la unidad [1], es redondeado a cero, y este valor (int) es el que es promovido afloat. Una solucin inmediata y obvia (?) permite resolver la situacin (ver: Modelado de tipos void inverso (int x) { float f = float(1)/float(x); cout << "X = " << x << " 1/x = " << f << endl; } Una solucin un poco ms elegante: void inverso (int x) { float f = float(1)/x; cout << "X = " << x << " 1/x = " << f << endl; } En este caso, el compilador realiza automticamente la promocin de x a float antes de efectuar la divisin (ver reglas anteriores ). Una solucin an ms elegante que tambin produce resultados correctos: void inverso (int x) { float f = 1.0/x; cout << "X = " << x << " 1/x = " << f << endl; 4.9.9):

4 Conversiones a puntero Un Rvalue que sea una expresin constante ( 3.2.3a) que se resuelva a 0, puede ser convertida a puntero de cualquier tipo T. Se transforma entonces en una constante-puntero nulo ("Null pointer constant"), y su valor es el valor del puntero nulo del tipo T*.

Para entender estos conceptos considere que en C++, dos punteros son distintos si apuntan a tipos distintos. Por ejemplo, un puntero-a-int (int*) es distinto de un puntero-a-char (char*), y sus valores son de tipo distinto. Resulta as que el valor (0) del puntero-a-int nulo es de tipo distinto del valor (0) del puntero-a-char nulo. Si representamos ambos valores por 0i y 0c respectivamente, diramos que: 0i es el valor del puntero nulo de int* (puntero-a-int) 0c es el valor del puntero nulo de char* (puntero-a-char) Ejemplo: int const nulo = 0; int* pint = nulo; // L1: // L2:

En L1 nulo es un objeto tipo int calificado const ( 2.2), cuyo Rvelue es 0. En L2 este objeto sufre una conversin estndar y se convierte al tipo int*; en este momento su valor no es ya un 0 "pelado" ("plain 0"); es el valor del puntero nulo del tipo int*. A continuacin su Rvalue es copiado a la direccin del objeto pint, que toma as su valor. Observe que si a la expresin L1 anterior se le suprime el calificador const: int nulo = 0; int* pint= nulo; // L1a: // L2: Error!!

se obtiene un error de compilacin en L2. La causa es que la conversin estndar no puede realizarse porque, aunque nulo sigue siendo un int de valor 0, le falta el calificador const. Considere ahora otra variacin del ejemplo anterior: int const nulo const int* pi1 int const* pi2 int* const pi3 = = = = 0; nulo; nulo; nulo; // // // // L1: L2: L3: L4:

Los nuevos objetos son tambin punteros, aunque ahora pi1 y pi2 son punteros-a-int constante (L2 y L3 son equivalentes); el objeto al que sealan no puede cambiar su valor. Su tipo es const int*. Por su parte, pi3 es tambin puntero-a-int, aunque con el calificador const. Su tipo int* no se distingue del de pint en el caso anterior. En este caso el objeto nulo sufre una conversin estndar a tipo int* calificado. La norma nos avisa que esta conversin del objeto const al tipo int*calificado es una sola conversin, y no una conversin a int* seguida de una calificacin.

5 Conversiones de constantes de enumeracin Para las conversiones de las constantes de enumeracin ver Enumeraciones ( 4.8).

6 Conversiones de matriz a puntero El compilador puede realizar expontneamente la conversin de una matriz-de-elementos-tipoX a puntero-a-tipoX ( 4.3.2). Este tipo de conversin es la que permite que la etiqueta de una matriz M pueda ser tomada en determinados contextos como un puntero a su primer elemento: M &M[0] pM

Este tipo de conversin tambin ocurren en las asignaciones del tipo: char* cptr = "ABC";

7 Conversin a booleano Los Rvelues de tipo numrico ( 2.2.1), las constante de enumeracin, los punteros y los punteros a miembro, pueden ser convertidos a Rvelues de tipo bool ( 3.2.1b). La regla es que un valor cero o un puntero nulo son convertidos a false. Cualquier otro valor es convertido a true.

8 Conversiones de funcin a puntero-a-funcin Esta conversin permite que el nombre de una funcin F pueda ser tomada en caso necesario como su puntero ( 4.2.4a) [2]. En realidad, para el compilador, el tipo de una funcin es punteroa-funcin, de forma que en lo tocante a este atributo, no distingue entre ambas entidades ( Ejemplo comentado). Temas relacionados: Modelado de tipos ( 4.9.9) Bsqueda de nombres ( Name-lookup) Congruencia estndar de argumentos ( 4.4.1a) Conversiones definidas por el usuario ( 4..9.18k)

2.2.5a Conversiones estndar con tipos abstractos


1 Sinopsis Adems de las conversiones estndar realizadas con los tipos bsicos ( 2.2.5), existe ocasiones en que el compilador realiza espontneamente ciertas adaptaciones de tipo para que puedan realizarse determinadas operaciones con objetos abstractos, cuando tales objetos pertenecen a jerarquas de clases.

Nota: las conversiones que se relacionan exigen que la superclase o subclase sean accesibles, y que en casos de herencia mltiple, puedan puedan realizarse sin ambigedad.

2 Conversin de referencias En las jerarquas de clases, las referencias a subclases pueden ser promovidas a referencias a la superclase. El resultado de la conversin es una referencia al subobjeto de la superclase contenido en el objeto de la clase derivada (miembros heredados 4.11.2b). Ejemplo: class Bas { ... }; class Der : public Bas { ... }; void foo(Bas&); Der d; Der& rd = d;

// referenica-a-d (objeto de subclase)

En este contexto, aunque foo espera una referencia a la superclase, es legal la invocacin: foo(rd); El compilador se encarga de realizar una conversin al tipo requerido, de forma que la invocacin es transformada en: foo( (Bas&)rd );

3 Conversin de punteros a clase En las jerarquas de clases, los objetos de las clases derivadas pueden utilizarse con punteros a la superclase. En realidad, cuando se manipulan mediante punteros, los objetos de la clase derivada pueden tratarse como si fuesen objetos de la superclase. Ejemplo: class Bas { ... }; class Der : public Bas { ... }; Bas* bptr; Der d; // puntero-a-superclase // instancia de sub-clase

En este contexto, aunque bptr es puntero-a-superclase, puede ser asignado con la direccin de un objeto de la subclase. Es legal la asignacin: bptr = &d; El compilador se encarga de realizar una conversin al tipo requerido, de forma que la asignacin es transformada en: bptr = &( (Bas)d ); Este tipo de conversin Sub-clase* Super-clase* es realizada automticamente por el compilador en determinadas circunstancias (congruencia estndar de argumentos 4.4.1a).

Nota: cuando se acceden a travs de punteros objetos de clases que pertenecen a una jerarqua, es importante tener en cuenta las precauciones indicadas en "Consideraciones sobre punteros en jerarquas de clases" ( 4.11.2b1).

4 Conversin de punteros a miembro Con los punteros a miembro ocurre una conversin que, en cierta forma, es inversa de la anterior: los punteros a miembro de una superclase pueden tratarse como si fuesen punteros a objetos de una subclase. Ejemplo: class Bas { public: int bi; }; class Der : public Bas { public: int di; }; int Bas::* bpi = &Bas::bi; int Der::* dpi = &Der::di; // puntero-a-miembro de superclase // puntero-a-miembro de subclase

En este contexto, el puntero puede ser utilizado con objetos de la subclase, en cuyo caso sealar al miembro heredado: Der d; Der* dp = &d; d.*bpi = 2; dp->*bpi = 3; d.*dpi = 2; dp->*dpi = 3; Bas b; b.*dpi = 2; // Ok. // Ok. // OK. // Ok. d.bi = 2 d.bi = 3 d.di = 2 d.di = 3

// Error!! b NO posee un miembro dpi

2.2.6 Almacenamiento
Recordemos que al describir la estructura de un programa, se dedic un captulo a explicar las formas de almacenamiento de algoritmos y datos ( 1.3.2). Aqu nos referimos exclusivamente al almacenamiento de datos. En especial a aquellos aspectos del soporte fsico que tienen repercusiones de inters para el programador.

1 Sinopsis El almacenamiento de los datos de un programa puede ser considerado desde varios puntos de vista; trataremos aqu dos de ellos: uno fsico, y otro lgico. Desde el punto de vista fsico existen cinco zonas de almacenamiento: los registros, el segmento de datos, el montn y la pila.

Pila ("Stack") Montn ("Heap") Segmento de datos ("Data segment" en el PC) Registros ("Registers")

2 Caractersticas fsicas Cada zona tiene unas caractersticas propias que imprimen carcter a la informacin almacenada en ellas. Pila: a menos que se especifique lo contrario las variables locales se almacenan aqu, tambin los parmetros, es decir: las variables automticas ( 1.3.2). Los elementos almacenados en esta zona son de naturaleza automtica; esto significa que el compilador se encarga de crearlas y destruirlas automticamente cuando salen de mbito. Montn: es utilizado para asignacin dinmica de bloques de memoria de tamao variable ( 1.3.2). Muchas estructuras de datos, como rboles y listas, lo utilizan como sitio de almacenamiento. Esta zona est bajo el control del programador con new, malloc y free. Los elementos almacenados en esta zona se asocian a una existencia persistente [3]. Esto significa que se crean y destruyen bajo directo control del programador que debe preocuparse de su destruccin cuando ya no son necesarios para liberar la memoria y permitir que pueda ser usada por otros objetos. Segmento de datos: es una zona de memoria utilizada generalmente por las variables estticas y globales. Registros: son espacios de almacenamiento en el interior del procesador, por lo que su nmero depende de la arquitectura del mismo. Los programas C++ no pueden garantizar que una variable se almacene en un registro (variable de registro), aunque podemos solicitarlo ( 4.1.8b). Es la zona de memoria de ms rpido acceso, por lo que se utiliza para guardar contadores de bucle y usos parecidos en los que la velocidad sea determinante, sin embargo son un recurso escaso (hay pocos). Los objetos almacenados aqu son tambin de naturaleza automtica; generalmente de tipos asimilables a entero ( 2.2.1). Nota: los trminos automtico versus persistente, que en la prctica son respectivamente sinnimos de existencia en la pila/registros o en el montn, son conceptos que se utilizan constantemente en C++, por lo que es vital entender sus diferencias y las consecuencias que de ello se derivan. Tema relacionado: formas de representacin binaria de las magnitudes numricas (

2.2.4a)
Nota: en lo que sigue, el trmino identificador ( 3.2.2) se refiere al nombre arbitrario (dentro de ciertas reglas) que se da a una entidad (clase, objeto, funcin, variable, etc) en el

cdigo de un programa. Posteriormente pueden ser transformados por la accin del compilador y enlazador hasta quedar total o parcialmente irreconocibles en el ejecutable.

3 Caractersticas lgicas Desde el punto de vista lgico, existen tres aspectos bsicos a tener en cuenta en el almacenamiento de los objetos: mbito, visibilidad (scope) yduracin (lifetime). mbito o campo de accin de un identificador es la parte del programa en que es conocido por el compilador ( 4.1.3). Visibilidad de un identificador es la regin de cdigo fuente desde la que se puede acceder al objeto asociado al identificador sin utilizar especificadores adicionales de acceso (simplemente con el identificador 4.1.4). Duracin: define el periodo durante el que la entidad relacionada con el identificador tiene existencia real, es decir, un objeto fsicamente alojado en memoria ( 4.1.5).

Nota: observe que los dos primeros, mbito y visibilidad, se refieren al identificador y al fuente, decimos que son propiedades de tiempo de compilacin. El tercero, la duracin, se refiere a objetos reales en memoria. Decimos que es una propiedad de runtime.

Tanto las caractersticas fsicas (donde se almacena) como lgicas (mbito, visibilidad y duracin), estn determinadas por dos atributos de los objetos: clase de almacenamiento y tipo de dato (abreviadamente conocido como tipo 2.1). El compilador C++ deduce estos atributos a partir del cdigo, bien de forma implcita bien mediante declaraciones explcitas. Las declaraciones explcitas de clase de almacenamiento son: auto; register; static; extern; typedef y mutable ( 4.1.8 Especificadores de clase de almacenamiento). Las declaraciones explcitas de tipo de dato son: char; int; float; double y void ( 2.2.1 Tipos bsicos), a estos se pueden aadir matices utilizando ciertos modificadores opcionales: signed; unsigned; long y short ( 2.2.3 Modificadores de tipo).

4 El concepto "esttico" El concepto esttico ("Static") tiene en C++ varias connotaciones distintas; algunas de ellas son herencia del C clsico; otras son significados aadidos en la parte POO del lenguaje. Desafortunadamente (sobre todo para el principiante) algunos de los significados no tienen absolutamente ninguna relacin entre si y se refieren a conceptos distintos. Las diversas connotaciones del concepto, podramos resumirlas del siguiente modo: Relativa al conocimiento o no del compilador de los valores de un objeto en tiempo de compilacin, y como consecuencia directa de esto, el lugar de almacenamiento del objeto, ya que los objetos cuyos valores son conocidos por el compilador se almacenan en sitio distinto que los que solo son conocidos en tiempo de ejecucin ( 1.3.2). Relativa al enlazado de funciones; cuando una llamada a funcin puede traducirse en una direccin concreta en tiempo de compilacin ( 1.4.4), el enlazado (esttico) es diferente

del que se realiza cuando esta direccin solo es conocida en tiempo de ejecucin (dinmico). Relativa a la duracin o permanencia de un objeto. Relativa a la visibilidad de un objeto, lo que est relacionado directamente con otro concepto: el tipo de enlazado ( 1.4.4), que se refiere a las variables que puede ver el enlazador.

Refirindonos a la primera de ellas, esttico (versus dinmico), significa que el compilador conoce los valores en tiempo de compilacin (frente a tiempo de ejecucin -runtime-). Por tanto, puede asignar zonas predeterminadas de memoria para estos objetos (variables y constantes). Por el contrario, para los objetos dinmicos se asigna y desecha espacio de memoria en tiempo de ejecucin, lo que significa que se crean y se destruyen con cada llamada de la funcin en que han sido declaradas. Esto explica por ejemplo, que cada llamada recursiva a una funcin pueda generar su propio conjunto de variables locales (dinmicas). Si el espacio fuese asignado de forma fija en tiempo de compilacin, la recursin sera imposible, pues cada nueva invocacin de la funcin machacara los valores anteriores. Nota: Si la "profundidad" de la recursin se pudiese conocer en tiempo de compilacin, el compilador podra asignar espacio a los sucesivos juegos de variables, pero tngase en cuenta que este es precisamente un valor que a veces solo se conoce en tiempo de ejecucin. Por ejemplo, no es lo mismo calcular el factorial de 5 que el de 50 [2].

En principio las variables globales (definidas fuera de una funcin) son estticas (en este sentido) y las locales son dinmicas (de la variedad llamada automtica) es decir: las primeras pueden conservar su valor entre llamadas y las segundas no. En este orden de cosas, la declaracin como static de una variable local, definida dentro de una funcin, le confiere permanencia entre las sucesivas llamadas a dicha funcin (igual que las globales). Desafortunadamente [1], la declaracin static de una variable global (que debera ser redundante e innecesaria), supone una declaracin de visibilidad, en el sentido de que dicha variable global (aparte de su estaticidad), solo ser conocida por las funciones dentro del fichero en que se ha declarado. Resulta as que, desgraciadamente, la palabra clave static tiene un doble sentido (y uso), el primero est relacionado con la duracin ( 4.1.5), el segundo con la visibilidad ( 4.1.4). Finalmente, cuando el modificador static se utiliza para miembros de clase adquiere una peculiaridades especficas ( 4.11.7 Miembros estticos).

5 Resumen: Con el fin de aclarar un poco este pequeo galimatas semntico, resumimos lo dicho: Automtico versus Persistente Propiedad de los objetos de crearse/destruirse automticamente (al entrar y salir del bloque de cdigo) o bajo control directo del programador mediante sentencias especficas de creacin y destruccin ( new y delete). Existen respectivamente en la Pila/Montn. Tanto los objetos automticos como los persistentes son de naturaleza dinmica.

Esttico versus Dinmico Caracterstica de ser conocido en tiempo de compilacin o en tiempo de ejecucin, lo que significa que el compilador puede reservar almacenamiento desde el principio o este debe ser creado y destruido en tiempo de ejecucin.

6 Ejemplo: Intentaremos aclarar los conceptos anteriores comentando el ciclo vital de los elementos en un sencillo programita: #include <iostream.h> void func(int); char* version = "V.0.0"; // prototipo // L.4:

int main() { // ============= int x = 1; char* mensaje = "Programa demo "; cout << mensaje << endl; cout << "Introduzca numero de salidas (0 para terminar): "; while ( x != 0) { cin >> x ; func(x); cout << "Otra vez? (numero): " << endl; } return 0; // L.15: } void func(int i) { // L.17: definicion static int j = 1; cout << "Se han solicitado: " << i << " salidas." << endl; int* v = new int; // L.20: *v = 1; register int n; // L.22: for (n = 1; n <= i; n++) { cout << " - " << *v << "/" << i << " total efectuadas: " << j << " salidas." << endl; j++; (*v)++; // L.26: } cout << version << endl; // L.28: delete v; // L.29: } Volcado de pantalla con la salida del programa despus de marcar 3 y 2 como valores de entrada: Programa demo Introduzca numero de salidas (0 para terminar): 3 Se han solicitado: 3 salidas. - 1/3 total efectuadas: 1 salidas. - 2/3 total efectuadas: 2 salidas. - 3/3 total efectuadas: 3 salidas. V.0.0 Otra vez? (numero): 2 Se han solicitado: 2 salidas.

- 1/2 total efectuadas: 4 salidas. - 2/2 total efectuadas: 5 salidas. V.0.0 Comentario Cuando se inicia el programa, el SO reserva un nmero determinado de bloques del total de memoria disponible para uso del nuevo ejecutable [4]. Este espacio es exclusivo del programa y no puede ser violado por otra aplicacin ni an intencionadamente; de esto se encarga el propio SO. Por ejemplo, si un puntero de una aplicacin se descontrola y seala una zona de memoria que no le pertenece, surge el conocido mensaje Windows: "La aplicacin ha efectuado una operacin no vlida y ser detenido...". Si es Linux, el clsico error fatal con volcado de memoria. Si el programa lo necesita, el espacio destinado inicialmente puede crecer; el SO puede seguir asignando nuevos bloques de memoria. Cuando se acaba la memoria fsica disponible, los modernos SO empiezan a asignar memoria virtual ( H5.1) haciendo constante intercambio con el disco de las partes que no pueden estar simultneamente en la memoria central (RAM). Este proceso ("Swapping") es totalmente transparente para el programa usuario, y puede crecer hasta el lmite del almacenamiento disponible en disco. Por supuesto, antes que se alcance este punto, el programa se muestra especialmente perezoso, ya que estos intercambios entre el disco y la RAM son comparativamente lentos. La ejecucin del programa comienza por el mdulo de inicio ( 1.5), que crea e inicia las variables estticas y globales. En este caso, la cadena de caracteres, "V.0.0", accesible mediante el puntero version, y la variable j de la funcin func. Salvo indicacin en contrario, j se habra inicializado a cero, pero en este caso se instruye al compilador (L.18) que se inicialice a 1, que es el valor inicial que queremos para este contador. Observe que esta asignacin solo ocurre una vez durante la vida del programa (en el mdulo de inicio), no con cada invocacin defunc. A partir de este momento, esta variable conserva su valor entre cada invocacin sucesiva a la funcin, aunque va siendo incrementado progresivamente en L.26. Tanto el puntero version como la cadena sealada por l, permanecen constantes a lo largo de toda la vida del programa, adems este nemnico es visible desde todos los puntos (tiene visibilidad global), por eso puede ser utilizado desde el interior de func, en L.28. La variable j, el punteroversion y la propia cadena "V.0.0" son creados en el segmento ( ).

Al llegar a L.15, se inicia la secuencia de finalizacin ( 1.5). En este momento se destruyan las variables globales anteriormente descritas, as como las locales de la propia funcin main. El SO recibe un entero como valor devuelto por el programa que termina. Generalmente el valor 0 es sinnimo de terminacin correcta; cualquier otro valor significa terminacin anormal. En este momento, el SO recupera el espacio de memoria asignada al programa que queda disponible para nuevas aplicaciones, y borra del disco el posible fichero imagen de memoria virtual que hubiera utilizado. Observe que adems de las constantes literales ( 3.2.3f) sealadas por los punteros version y mensaje, el programa utiliza otra serie de literales: "Introduzca numero..."; "Otra vez..."; "Se han solicitado:"; etc. Todas ellas son constantes conocidas en tiempo de compilacin [5]; se trata por tanto de objetos estticos, mientras que el resto son dinmicos, ya que sus valores solo son conocidos durante la ejecucin.

Al ejecutarse la funcin main, se van creando e iniciando sucesivamente las variables (dinmicas); en este caso el entero x que recibe un valor inicial 1, y una constante de valor cero [5] en la sentencia return (L.15). Cada invocacin a func provoca la creacin de un juego de variables dinmicas. En este caso el entero i (argumento recibido por la funcin), variable local de func que recibe el mismo valor que tiene la variable x de main; el puntero-a-int v y el entero n. Preste atencin a que (suponiendo que el compilador atienda la peticin en L.22

4.1.8b), n se

crea en el registro ( ), mientras que i se crea en la pila ( ). Ambas son de naturaleza automtica, por lo que son destruidas al salir de mbito la funcin, cosa que ocurre al llegar al corchete de cierre ( } ) en L.30. Sin embargo, observe que el entero sealado por el puntero v, se crea en el montn ( ), lo que le confiere existencia persistente; esto hace que el espacio reservado (4 bytes en este caso 2.2.4) tenga que ser especficamente desasignado (en L.29), pues de lo contrario cada invocacin de func supondra la prdida irrecuperable (para el programa) de 4 bytes de memoria. Suponiendo que estuvisemos corriendo el programa en un servidor, seramos directamente responsables de una progresiva ralentizacin del sistema (posiblemente hasta que el "Sysmanager" descubriera una utilizacin inusual de recursos por nuestra parte y nos desconectara).

2.2.6a Orden de almacenamiento (endianness)


1 Sinopsis Adems de las cuestiones relativas a la zona en que se almacenan los datos, que fueron objeto del epgrafe anterior ( 2.2.6), existe otro aspecto que tambin puede ser de inters para el programador C++; es la cuestin del orden en que se almacenan en memoria los objetos multibyte. Por ejemplo, como se almacenan los Bytes de un long ( 2.2.4) o de un wchar_t ( 2.2.1a1).

Nota: la cuestin no se refiere solo al orden de almacenamiento en la memoria interna. Puede ser tambin el caso de en un volcado de memoria a disco, o como se reciben los datos en una lnea de comunicacin.

La cuestin no es tan trivial como pudiera parecer a primera vista. Lo mismo que en el mundo real, donde donde existen sistemas de escritura que se leen de izquierda a derecha (el que est utilizando ahora) y otros que se leen en sentido contrario, tambin en el mundo de las computadoras existen sistemas que leen y escriben los Bytes de cada palabra en un sentido u otro. Naturalmente en el interior de la mquina no existe el concepto de izquierda o derecha, pero s puede utilizarse un orden u otro para colocar los Bytes respecto al sentido ascendente de las posiciones de memoria, o respecto al orden de salida en una lnea de transmisin. Para concretar un ejemplo, tomemos los unsigned short, que en el compilador Linux GCC, en Borland C++ 5.5 y en MS Visual C++ 6.0 ocupan 2 Bytes. Supongamos ahora que una variable X de este tipo adopta el valor 255. La representacin binaria convencional para los lectores humanos occidentales (que escribimos de izquierda a derecha) es del tipo 00000000 11111111. Al octeto de valor cero (0h) lo denominamos Byte ms significativo, o byte alto ( high byte) y al otro (FFh), Byte menos significativo, o byte bajo ( low byte). Para su almacenamiento

interno caben dos posibilidades: que se coloque primero el ms significativo y a continuacin el otro, o a la inversa (suponiendo el orden creciente de posiciones de memoria). Desgraciadamente no ha habido acuerdo entre los fabricantes respecto al sistema a adoptar y existen dispositivos hardware de ambos tipos. Es tradicin informtica que la primera disposicin se denomina big-endian y la segunda littleendian [1]. Si leemos la memoria desde las posiciones ms bajas a las ms altas, la zona que contiene el nmero X en una mquina que siga la convencin big-endian contendr los valores00h FFh, mientras que en una little-endian los valores encontrados sern FFh 00h. En concreto, las arquitecturas x86 de Intel y los procesadores Alpha de DEC son little-endian mientras que las plataformas Sun's SPARC, Motorola, e IBM PowerPC utilizan la convencin big-endian. En lo que respecta al software, Java utiliza el formato big-endian con independencia de la plataforma utilizada (es un lenguaje con una clara vocacin hacia Internet, y los protocolos TCP/IP utilizan esta convencin). Por contra, C y C++ utilizan la convencin dictada por el Sistema Operativo. Los sistemas Windows utilizan la convencin little-endian, mientras que la mayora de plataformas Unix utilizan big-endian. Nota: es tradicin, que cuando se trata de cantidades de 32 bits. Por ejemplo, un long, la mitad ms significativa se denomine palabra alta (high word), y la menos significativa palabra baja (low word). Lo que supone evidentemente que nos referimos a palabras de 16 bits.

2 Tratamiento Normalmente el programador no debe preocuparse por estas cuestiones de orden ("endianness") mientras trabaja en una plataforma determinada, pero debe estar prevenido si maneja datos provenientes de otras plataformas o que deben ser compartidos con ellas [2]. Un ejemplo paradigmtico es el de las comunicaciones TCP/IP. Este conjunto de protocolos utiliza la convencin big-endian en todas sus estructuras. De forma que, por ejemplo, las direcciones IP, que son nmeros de multiBytes (de 4 octetos), se construyen colocando primero el Byte ms significativo. Este es el orden en que se transmiten, viajan y son recibidos las magnitudes multibyte en las comunicaciones de Internet (el denominado "network-byte order"). En caso de utilizar un equipo con hardware little-endian. Por ejemplo con un procesador Intel x86, la representacin interna (el denominado "host-byte order") seguir esta convencin y ser preciso recolocar los Bytes en el orden adecuado, tanto en los flujos de entrada como en los de salida, para que los datos puedan ser interpretados correctamente. 2.1 Una forma de realizar estas manipulaciones en C++ es recurriendo a los operadores de bit ( 4.9.3). Por ejemplo, si uShort es ununsigned short (de 2 Bytes) y debemos invertir el orden de sus octetos, pueden utilizarse las siguientes expresiones: uShort // Valor original a cambiar (por ejemplo big-endian) unsigned short uS1 = uShort >> 8; // valor del byte ms significativo unsigned short uS2 = uShort << 8; // valor del byte menos significativo + 255 unsigned short uSwap = uS2 | uS1; // valor little-endian El resultado puede obtenerse en una sentencia: unsigned short uSwap = (uShort << 8) | (uShort >>8);

Tambin mediante una directiva de preproceso (

4.9.10b):

#define SWAPSHORT(US) ((US << 8) | (US >>8)) ... unsigned short uSwap = SWAPSHORT(uShort); // valor little-endian

2.2 El procedimiento puede hacerse extensivo para los valores de 4 Bytes. Por ejemplo, supongamos un unsigned long uLong cuyo valor es 4000967017 (puede ser cualquier otro). Su mapa de bits big-endian tiene el siguiente esquema: 11101110 01111001 11101001 01101001 Para colocarlos en posicin invertida, aislamos sus 4 Bytes con el auxilio de unos patrones que responden a los siguientes valores: unsigned long k = 0xFF; unsigned long k1 = k | k << 8 | k << 16; 00000000 00000000 00000000 11111111 00000000 11111111 11111111 11111111 11111111 00000000 11111111 11111111 11111111 11111111 00000000 11111111 11111111 11111111 11111111 00000000

unsigned long k2 = k | k << 8 | k << 24;

unsigned long k3 = k | k << 16 | k << 24; unsigned long k4 = k << 8 | k << 16 | k << 24;

Con ellos podemos construir las expresiones que proporcionan los Bytes individuales ( unsigned long B1 = (uLong ^ k1 & uLong) >> 24; unsigned long B2 = (uLong ^ k2 & uLong) >> 16; unsigned long B3 = (uLong ^ k3 & uLong) >> 8; unsigned long B4 = uLong ^ k4 & uLong;

4.9.3a):

00000000 00000000-00000000 11101110 00000000 00000000-00000000 01111001 00000000 00000000-00000000 11101001 00000000 00000000-00000000 01101001

A partir de aqu es trivial construir el valor deseado, con los Bytes en orden little-endian o en cualquier otro, mediante desplazamientos combinados con el operador OR inclusivo.

unsigned long uLong_Swap = B4 << 24 | B3 << 16 | B2 << 8 | B1; Observe que es posible simplificar algo las expresiones anteriores aprovechando que los desplazamientos derecha + izquierda de B2 y B3 pueden ser combinados en uno solo. 2.3 El procedimiento puede hacerse extensivo a cualquier valor value, expresado por una sucesin de n bytes. De forma que su representacin big-endian puede expresarse: value = (byte[0] << 8*(n-1)) | (byte[1] << 8*(n-2)) | ... | byte[n-1];

Generalmente, estas cuestiones de "endianness" son manejadas mediante directivas de preproceso (#derfine) existentes al efecto en los ficheros de cabecera. De esta forma, las aplicaciones son independientes de la plataforma (para adaptar el compilador a otra plataforma solo hay que modificar las directivas correspondientes). Para que el lector tenga una idea de la mecnica utilizada, a continuacin se incluyen algunas muy frecuentes en la programacin Windows. #define LOWORD(x) ((WORD) (l)) #define HIWORD(x) ((WORD) (((DWORD) (l) >> 16) & 0xFFFF)) Con estas definiciones, y sabiendo que a su vez, WORD y DWORD estn definidas como unsigned short y unsigned long respectivamente, supongamos que dos valores, ancho y alto de cierta propiedad, se reciben codificados en las mitades superior e inferior de un long, al que llamaremos param. En este contexto, ambos valores pueden ser fcilmente determinados con las expresiones siguientes: WORD alto = LOWORD(param); WORD ancho = HIWORD(param); Otras expresiones utilizadas en el compilador MS Visual C++ (BYTE est definida como unsigned char, y LONG es long): #define #define #define #define MAKEWORD(a, b) ((WORD)(((BYTE)(a)) | ((WORD)((BYTE)(b))) << 8)) MAKELONG(a, b) ((LONG)(((WORD)(a)) | ((DWORD)((WORD)(b))) << 16)) LOBYTE(w) ((BYTE)(w)) HIBYTE(w) ((BYTE)(((WORD)(w) >> 8) & 0xFF))

Como el lector puede comprobar en todos estos casos, si se modifican las condiciones de entorno, la adaptacin de las aplicaciones resulta muy fcil, ya que se limita a modificar adecuadamente los ficheros de cabecera.

3 Identificacin del endianness del sistema

323

También podría gustarte