8. Enteros en C (parte I)
0. Números enteros, reales y complejos: Tenemos tres clases tipos de datos numéricos en
C:
enteros, reales y complejos.
Representación de números en C y en la computadora
Los enteros son los que normalmente usamos para contar: 0, 1, 2, 3, 4, etc., y pueden ser positivos o negativos (-1, -2, -3, -4, etc.).
Los reales son aquellos que admiten un punto y dígitos decimales, o parte fraccionaria, a la derecha. Por ejemplo: 3.141592, 1.414213, 0.25, -7.777777, etc.
Los complejos están compuestos por dos componentes, llamadas parte real y parte imaginaria. Ambas partes son números reales, e \( i \) representa la unidad imaginaria, es decir la raíz cuadrada algebraica de \( -1 \):
\( i=\sqrt{-1}. \)
Recordemos siempre: Los números en una computadora no se comportan como los de la matemática.
La razón principal es que el procesador de una computadora tiene espacio limitado para alojar números.
Esto obliga a que todo tipo de datos numérico tenga un rango de valores restringido.
Asimismo, los números reales no pueden tener, un número arbitrariamente grande de dígitos. Así, sólo pueden pertenecer a un conjunto reducido de la clase de los racionales.
Las últimas versiones del lenguaje C permiten incorporar números complejos.
Estos se implementan simplemente como un par de números reales, y tienen las mismas limitaciones que ellos.
Aunque los tipos aritméticos básicos de C tienen las limitaciones mencionadas, pueden remediarse con ciertas técnicas de programación avanzadas.
¿Para qué distinguir tipos enteros y reales? Es que las operaciones con números fraccionarios son proclives a producir errores de redondeo, mientras que los enteros nunca pierden exactitud.
El tipo de datos estándar que se usa en C para trabajar con números reales son los llamados números de punto flotante.
1. Constantes numéricas de tipo entero: Las constantes numéricas enteras se escriben en
C en formato decimal (o sea, en base diez), en octal (base ocho) o en hexadecimal (base dieciséis). ichas constantes pueden ser positivas o cero, y sólo están limitadas por el tamaño máximo que el compilador o el sistema subyacente puede interpretar. El signo
- delante de una constante positiva ya no se considera parte de una constante, sino que es un operador de negación.)
Si un número es demasiado grande, requiere más bytes en memoria RAM para poder ser representado. En el siguiente Spoiler mostramos una tabla con el máximo entero positivo que puede representarse, según la cantidad de bytes disponibles para un tipo dado:
1.1. Tabla de enteros positivos máximos
Suponiendo bytes de 8 bits, tenemos la siguiente tabla:
Bytes Entero positivo máximo que puede representare
1 \( 2^{8} - 1 = 255 \)
2 \( 2^{16} - 1 = 65535 \)
3 \( 2^{24} - 1 = 16'777215 \)
4 \( 2^{32} - 1 = 4294'967295 \)
5 \( 2^{40} - 1 = 1'099511'627775 \)
6 \( 2^{48} - 1 = 281'474976'710655 \)
7 \( 2^{56} - 1 = 72057'594037'927935 \)
8 \( 2^{64} - 1 = 18'446744'073709'551615 \)
9 \( 2^{72} - 1 = 4722'366482'869645'213695 \)
10 \( 2^{80} - 1 = 1'208925'819614'629174'706175 \)
11 \( 2^{88} - 1 = 309'485009'821345'068724'781055 \)
12 \( 2^{96} - 1 = 79228'162514'264337'593543'950335 \)
¿Qué ocurre si escribimos enteros demasiado grandes? ¿El compilador los entiende? En principio, eso depende del compilador. Pero aún si el compilador sabe que se trata de una constante entera, no necesariamente "sabe cuánto vale".
El
C99 asegura la disponibilidad de constantes enteras positivas tan grandes como \( 2^{64}-1=18'446744'073709'551615 \), es decir, del orden de los 18 trillones (en nomenclatura inglesa, se diría "18 quintillones", lo cual es confuso...). Así que no vamos a dar por sentado la existencia de constantes enteras mayores que esa en nuestro compilador. De hecho, el compilador (GCC) reconoce ese número.
Los tipos de datos de
enteros sin signo (o sea positivos) en
C son los siguientes (según
C99):
unsigned charunsigned short int (declaración abreviada:
unsigned short)
unsigned int (declaración abreviada:
unsigned)
unsigned long int (declaración abreviada:
unsigned long)
unsigned long long int (declaración abreviada:
unsigned long long)
En todos ellos el mínimo entero representable es
0, pero el máximo en cada uno
no está claramente definido en el estándar, dejándolo a criterio de los compiladores.
Rango de valores admitidos para los tipos enteros en C. El estándar
C99 reclama que:
\( \bullet \)
unsigned char:
Representa valores al menos 8 bits, con lo cual se asegura al menos el rango de 0 a \( 2^8-1=255 \).
\( \bullet \)
unsigned short int Representa valores al menos en el rango de 0 a \( 2^{16}-1=65535 \).
Además se exige que, en cualquier circunstancia, contenga el rango de
unsigned char.
\( \bullet \)
unsigned int Representa valores al menos en el rango de 0 a \( 2^{16}-1=65535 \).
Además se exige que, en cualquier circunstancia, contenga el rango de
unsigned short int.
\( \bullet \)
unsigned long int Representa valores al menos en el rango de 0 a \( 2^{16}-1=65535 \).
Además se exige que, en cualquier circunstancia, contenga el rango de
unsigned int.
\( \bullet \)
unsigned long long int Representa valores al menos en el rango de 0 a \( 2^{64}-1=18'446744'073709'551615 \).
Además se exige que, en cualquier circunstancia, contenga el rango de
unsigned long.
Los tipos
char se consideran tipos numéricos enteros. Detalles en el spoiler:
1.2. Relación entre el tipo char y los tipos enteros
Es posible hacer cálculos con valores de tipo
char, igual que con cualquier otro tipo entero. Además, las constantes de caracteres, tales como
'm',
'@',
'&', etc., representan números enteros en
C, aunque nos resulte extraño concebir esto así. Tiene sentido una operación como ésta:
'm' + 5 - '@', cuyo resultado es
50.

Para mostrar un número tipo
char en pantalla, el programador elegirá el formato que prefiera, según el contexto: caracter o número.
Digamos además que el tipo
unsigned char en general siempre ocupa exactamente 8 bits en (casi) todas las implementaciones reales. Pero el estándar
C99 no exige esto, y podría ocupar más bits, si hiciera falta. Sin embargo, su rango de valores no podrá ser mayor que el de un dato de tipo
unsigned short int.
También debemos mencionar el tipo entero sin signo
booleano (disponible desde el estándar
C99):
_Bool Ocupa (al menos) 1 byte, y es capaz de alojar los valores 0 y 1.
El
0 se usa para indicar
FALSO, y el
1 es
VERDADERO.
El tipo
_Bool no ocupa más bits que el
unsigned char.
Ahora pasemos a estudiar los tipos de datos de
enteros con signo. Para
C99 son los siguientes:
signed char signed short int (formas abreviadas:
signed short,
short int,
short)
signed int (forma abreviada:
int)
signed long int (formas abreviadas:
signed long,
long int,
long)
signed long long int (formas abreviadas:
signed long long,
long long int,
long long)
Las formas abreviadas son sinónimas. Lo típico en
C es usar las formas abreviadas:
short,
int,
long,
long long.
Rangos de valores de los tipos enteros signados Los enteros con signo necesitan 1 bit para indicar el signo del número, influyendo en el rango de valores admisibles.
C99 asegura los siguientes rangos de valores para los tipos signados:
\( \bullet \)
signed char:
de \( -2^7=-127 \) a \( 2^7-1=127 \)
\( \bullet \)
signed short:
al menos de \( -2^{15}=-32767 \) a \( 2^{15}-1=32767 \).
\( \bullet \)
signed int:
al menos el mismo rango que
signed short.
\( \bullet \)
signed long:
al menos el mismo rango que
signed int.
\( \bullet \)
signed long long:
al menos de \( -2^{63}=-9223372036854775807 \) a \( 2^{63}-1=9223372036854775807 \).
Además, el rango de valores debe ser al menos tan grande como el de
signed long int.
2. Compatibilidad del tipo char: La forma aparentemente breve
char no significa signed char.
El tipo char siempre se considera distinto de unsigned char y signed char, aunque su rango de valores coincide siempre con alguno de los dos. Desde el estándar no se asegura si
char tiene signo o no, pero claramente contiene siempre los valores del rango de 0 a 127.
Además, los tres tipos de datos
char,
unsigned char y
signed char, ocupan todos la misma cantidad de bytes.
Nota técnica: Los tipos con igual denominación, en general ocupan la misma cantidad de bytes en sus versiones
unsigned y
signed. (Hay excepciones, pero es un tema complicado de abordar aquí).
3. Enteros de tamaño fijo. Según vimos, no es posible predecir la longitud exacta en bits que tiene cada tipo entero en
C. La librería
<stdint.h> del estándar
C99 define nuevos tipos de enteros, que tienen una longitud fija medida en bits.
Se definen los siguientes tipos enteros:
Sin signo con longitud fija en bits:
uint8_t, uint16_t, uint32_t, uint64_t.
Con signo con longitud fija en bits:
int8_t, int16_t, int32_t, int64_t.
Sin signo con longitud en bits mínima asegurada:
uint_least8_t, uint_least16_t, uint_least32_t, uint_least64_t.
Con signo con longitud en bits mínima asegurada:
int_least8_t, int_least16_t, int_least32_t, int_least64_t.
Sin signo, rápidos, con longitud en bits mínima asegurada:
uint_fast8_t, uint_fast16_t, uint_fast32_t, uint_fast64_t.
Con signo, rápidos, con longitud en bits mínima asegurada:
int_fast8_t, int_fast16_t, int_fast32_t, int_fast64_t.
Los tipos con longitud fija tienen la cantidad de bits que su nombre indica. Ejemplo:
uint8_t es un entero de 8 bits.
La longitud mínima asegurada fija un mínimo de bits, pero la longitd real puede ser aún mayor. Ejemplo:
uint_least8_t es un tipo entero con al menos 8 bits, pero podría ser mayor en una implementación dada.
Los tipos rápidos con longitud mínima asegurada también fijan un mínimo en bits para tipos enteros, y que además son
rápidos. ¿Qué significa
rápidos? El estándar no lo define ni lo sugiere. Pero se supone que un sistema dado puede aprovechar alguna característica interna que implemente versiones de enteros que permiten operar más rápido.
También se definen los tipos de longitud máxima en bits que la implementación local detecta:
uintmax_t: Tipo entero
sin signo de longitud máxima.
intmax_t: Tipo entero
con signo de longitud máxima.
Dado que
C99 asegura la existencia del tipo de 64 bits
long long int, podemos asegurar que
uintmax_t y
intmax_t tienen al menos 64 bits.
El estándar estipula además que es posible, aunque no obligatorio, definir los tipos
intptr_t,
uintptr_t. Son las versiones sin signo y con signo de un tipo de enteros portable a utilizar en operaciones con punteros (que involucran cálculos con posiciones en la memoria RAM).
4. Especificación de constantes enteras y establecimiento de tipos enteros en forma implícita. Si escribimos un número entero en nuestro programa, ¿de qué tipo de todos los anteriores es? Según las reglas de
C99:
\( \bullet \)
Dada una constante numérica entera, escrita con números decimales, su tipo es el más pequeño en el que "aún" cabe el número que pretendemos representar, de entre:
int
long
long long
Un ejemplo en el Spoiler:
4.1. Ejemplo de asignación de tipos a constantes
Supongamos que nuestro sistema admite
int de 16 bits,
long de 32 bits y
long long de 64 bits.
Números como -35, 0, 19, 126, 1848, 31009, serían considerados de tipo
int.
En cambio 32768 ya no cabe en nuestro
int, y pasa a ser
long. (El -32768 puede o no estar en el rango de
int.)
Números como 267543, -999999, 1012345678, serían aún de nuestro tipo
long.
En cambio 2147483648 ya no cabe, y es de tipo
long long.
En nuestro
long long ya no cabe 9223372036854775808.
¿Qué ocurre con ese número tan grande, que no cabe ni siquiera en un
long long?
El estándar ya no especifica nada al respecto, y así la constante entera más grande
que podemos asegurar que funciona 
en
C es:
9223372036854775807

Las constantes escritas en base decimal nunca pueden adjudicarse a un tipo entero sin signo. Ver:
Sección 7.1 del libro de King.
5. Constantes enteras octales y hexadecimales: Si delante de un número anteponemos un
0, el compilador de
C interpreta que el número está
en base 8 (octal). Estos números sólo admiten los dígitos 0, 1, 2, 3, 4, 5, 6, 7, así que si aparecen los caracteres 8 ó 9, dará un error, por no ser un número octal válido.
Ejemplos: 017, 033, 041234, -0167216.
Atención:

No hay que confundirse con estos números. Si en
C escribimos un
0 a la izquierda de un número, se lo considera octal, y así no es lo mismo
17 (en decimal) que
017, que está en octal, y que como número decimal equivale a
15.

Así que hay que extremar las precauciones y, en general:
No anteponer un 0 a la izquierda de una constante de número entero en un programa en C, a menos que a propósito queramos escribir un número en base octal. Es posible escribir números en base 16 (hexadecimal), anteponiendo el prefijo
0x delante del número. Por ejemplo, los siguientes son números hexadecimales:
0x34 (hexadecimal
34 = decimal
52),
0x7FC (hexadecimal
7FC = decimal
2044), etc.
Los dígitos hexadecimales A, B, C, D, E, F, pueden escribirse en mayúsculas y/o en minúsculas, e incluso se pueden mezclar ambos estilos. Ejemplo:
0x1Aa (hexadecimal
1AA = decimal
426).
En vez de
0x, se puede usar también mayúsculas poniendo
0X, por ejemplo:
0X4D (hexadecimal
4D = decimal
77).
Observación: No hay que confundirse con el uso de la
x al definir caracteres con código hexadecimal, ya que allí no está permitida la
X mayúscula:
'\x3F' está bien, pero
'\X3F' está mal, mientras que para constantes numéricas, tanto
0x3F como
0X3F son ambas correctas.
Para las constantes en octal y en hexadecimal existe una regla distinta de asignación de tipo entero:
\( \bullet \)
Dada una constante de número entero escrito en octal o hexadecimal, el tipo de datos entero que se le asigna es el primero de los siguientes, tal que el número puede representarse (o cabe) en dicho tipo:
int
unsigned int
long int
unsigned long int
long long int
unsigned long long int
La máxima constante entera positiva que el estándar
C99 nos asegura en un programa en
C es \( 2^{63}-1 \), como ya vimos. En octal es:
0777777777777777777777. Y en hexadecimal es:
7FFFFFFFFFFFFFFF.
6. Forzando el tipo de una constante numérica. Es posible forzar el tipo de una constante numérica entera, mediante el uso de sufijos.
Para forzar al compilador a interpretar una constante de número entero como
unsigned (del tamaño que sea), se agrega como sufijo al número una
U ó una
u (es indistinto el uso de mayúsculas o minúsculas).
Así, la constante
317, que normalmente sería un
int, al escribir
317u pasa a ser un
unsigned int.
El compilador, al tomar un número entero con sufijo
u, le asignará el primero de los siguientes tipos en que el valor del número "cabe":
unsigned int
unsigned long
unsigned long long
(Esto vale para números decimales, octales y hexadecimales).
Otro sufijo es
L ó
l (de nuevo es indistinto el uso de mayúsculas o minúsculas), para indicar
long.
El efecto de agregar este sufijo es que el compilador trata de encajar la constante decimal en la más pequeña de las opciones:
long ó
long long.
En cambio, para el sufijo
L o
l de una constante octal o hexadecimal, el compilador tratará de asignar el primero que sea posible de los siguientes tipos:
long int
unsigned long int
long long int
unsigned long long int
También existe el sufijo
LL ó
ll (
no se pueden mezclar mayúsculas y minúsculas como:
Ll ó
lL). Con este sufijo, una constante entera escrita en base decimal pasa a considerarse enseguida de tipo
long long int.
Mientras que si la constante es octal o hexadecimal, se intentará asignarle el primerlo de los tipos siguientes, que sea posible:
long long int
unsigned long long int
Se pueden combinar los sufijos
U con los
L ó
LL, para obtener
UL (asegura
unsigned long o mayor) y
ULL (asegura tipo
unsigned long long).
Se pueden cambiar de orden o escribir en indistintamente en mayúsculas y minúsculas, y así todas estas opciones son válidas:
ul uL Ul UL ull uLL Ull ULL lu Lu lU LU llu LLu llU LLU Todos los sufijos
U,
L,
LL,
UL,
ULL, pueden colocarse detrás de constantes octales y hexadecimales:
0331413L,
0x35ULL.
Al colocar el sufijo
UL a una constante entera, el compilador intentará asignarle el primero que sea posible de la lista de tipos siguientes:
unsigned long int
unsigned long long int
(Esto vale para bases decimal, octal, hexadecimal).
Si anteponemos el sufijo
U a una constante más grande que el máximo valor aceptado en un
signed long long int, pero que todavía está en el rango del tipo
unsigned long long int. ¿Es una constante que el compilador debe aceptar?
Debo revisar el estándar al respecto. (
Revisar) En mi sistema, cuyo
long long es de 64 bits, admite sin problemas la constante \( 2^{64}-1 \) seguida de sufijo
U, como un
unsigned long long int, escribiéndola así:
18446744073709551615U 
(El estándar discute la posibilidad de tipos de datos extendidos, pero yo me restrinjo siempre a lo estrictamente asegurado por el estándar, y estos tipos extendidos no están especificados en el
C99 estricto).
7. Constantes numéricas: tres caras de una misma moneda Podemos preguntar por las constantes numéricas desde 3 puntos de vista distintos. Ver spoiler:
(Discusión sobre las constantes enteras)
(i) Constantes numéricas que hemos escrito, y que el compilador es capaz de reconocer como tales.
(ii) Constantes numéricas que el compilador reconoce como tales, y que a su vez fue capaz de asociarles correctamente un tipo numérico en C.
(iii) Valores en el rango admitido para un tipo de datos numérico dado.
Esos 3 tipos de constantes son esencialmente diferentes.
En general siempre se cumple (i) pues es fácil reconocer si algo es sintácticamente una constante entera.
Si una constante cumple (ii), entonces también, siempre, cumple (i) y (iii).
Si cumple (iii), ¿cumple (i)? En general sí. Si no, sería indicio de un mal compilador.
Supongamos que nuestro compilador tiene tipos int de 16 bits, long de 32 bits, long long de 64 bits, y además no tiene tipos mayores que estos.
Consideremos las constantes enteras 10 trillones y 100 trillones, escritas en decimal:
10000000000000000000 (\( 10^{19} \)), 100000000000000000000 (\( 10^{20} \)).
El número 10 trillones no está en el rango de valores de signed long long int, pero sí de unsigned long long int.
Mi compilador GCC actual avisa que sólo el estándar C90 acepta ese número como de tipo unsigned long long int (se siguen otras reglas). Pero con las reglas de C99 no se puede asignar un tipo entero.
Sin embargo, 10 trillones es un valor correcto en el rango de unsigned long long int.
Hemos encontrado un caso donde vale (iii), pero no (ii).
El número 100 trillones no cabe en el rango de valores de unsigned long long int.
En este ejemplo no valen ni (ii) ni (iii).
OrganizaciónComentarios y Consultas