programacion-tipos-numericos

Tipos numéricos

Los tipos de variables numéricas nos permiten almacenar y manipular valores numéricos en nuestros programas.

En casi todos nuestros programas vamos a necesitar gestionar y manipular números. Es lógico, ya que un programa no deja de ser “una calculadora gorda” que hace cosas. Y muchas de esas cosas, van a intervenir números.

Un número puede ser cualquier cantidad o medida cuantificable. Por ejemplo, la edad de una persona, el número de ocupantes de un aula, la temperatura de una habitación, o las coordenadas en un mapa.

La representación de los números en los distintos lenguajes de programación varía mucho entre unos y otros, en especial entre los lenguajes tipados y no tipados.

Si recordáis las matemáticas, existen distintos tipos de números. Naturales, enteros, reales. En programación, de forma genérica, vamos a diferenciar entre dos.

  • Números enteros (sin decimales)
  • Números con decimales

Números enteros

Los números enteros representan valores numéricos sin decimales. Pueden ser positivos, negativos o cero.

Los números enteros son los más sencillos de entender y de implementar en un ordenador. Son los números que surgen, por ejemplo, al contar vacas en un prado.

Los lenguajes de programación ofrecen diferentes tipos de variables enteras, que varían en su tamaño y rango de valores.

En general, las diferencias son:

  • Lo grande que es el número que podemos almacenar
  • Si admite números negativos, o no

Internamente los números en un ordenador se guardan en formato binario. Y podemos almacenar 1, 2, 3… así hasta que agotemos la memoria del ordenador. O, más en concreto, hasta que sobrepasemos la capacidad de la variable que lo almacena.

Cuando intentamos guardar en una variable con un tamaño un número más grande del máximo que puede albergar, se llama “desbordamiento” (overflow en inglés).

Números con decimales

La otra gran familia de números que se contemplan en el ámbito de la programación son los números con decimales.

Hay que decir que representar números con decimales en un ordenador no es tan sencillo como puede parecer en primera instancia.

Para ello es frecuente emplear dos mecanismos habituales.

  • Coma flotante
  • Coma fija

La coma flotante (Floating-Point) es la representación más habitual. Utiliza una mantisa y un exponente para almacenar números con decimales. Proporciona una amplia gama de valores y una representación eficiente, pero puede tener errores de redondeo.

Los decimales fijos (Fixed-Point) se almacenan como enteros, y utilizan una convención para determinar la ubicación del punto decimal. Ofrece una representación precisa, pero la cantidad de dígitos decimales está limitada.

Existen otros tipos de representación más específicos como fracciones o números enteros con escala. Son menos empleados, pero pueden ser de utilidad en algunos casos específicos.

Ejemplos de tipos números en distintos lenguajes de programación

Como decíamos, la representación de números en los distintos lenguajes varía, especialmente entre lenguajes tipados y no tipados.

Por ejemplo, lenguajes como C++, C# o Java definen distintos tipos de números.

La diferencia entre ellos es

  • Si permiten números positivos y negativos
  • Si permiten números con decimales o no

Los detalles del tamaño máximo varían entre lenguajes. Además, en último término, depende del Sistema Operativo que estemos empleando, y del compilador que estemos usando.

// numeros enteros positivos
byte bytePequenioSinSigno = 255;
ushort cortoSinSigno = 5;
uint enteroSinSigno = 10;
ulong largoSinSigno = 1000000000;

// numeros enteros con positivos y negativos
short corto = 5;
int entero = 10;
long largo = 1000000000;

// numeros con decimales
float flotante = 3.14f;
double doble = 3.14159265359;
decimal decimalLargo = 3.1415926535897932384626433832m;

Por su parte JavaScript únicamente tiene el tipo Number.

A diferencia de otros lenguajes, JavaScript no distingue entre entero o números con decimales. Todos los números se tratan como punto flotante (64 bits según el estándar IEEE 754), sin existir una separación clara entre enteros y números de punto flotante.

Por lo que el ejemplo anterior quedaría así.

let entero = 10;
let largo = 1000000000;
let corto = 5;
let bytePequenio = 255;

let flotante = 3.14;
let doble = 3.14159265359;
let decimalLargo = 3.1415926535897932384626433832;

Finalmente, si vemos el ejemplo de Python, tampoco es necesario especificar el tipo de variable que vamos a crear.

Internamente, Python ofrece diferentes tipos de datos numéricos, como int, float y complex. El tamaño de las variables numéricas en Python puede variar según la implementación específica y la arquitectura de la máquina en la que se está ejecutando.

Por lo que el ejemplo quedaría así.

entero = 10
largo = 1000000000
corto = 5
bytePequenio = 255

flotante = 3.14
doble = 3.14159265359
decimalLargo = 3.1415926535897932384626433832

Sin embargo, en la mayoría de las implementaciones comunes, el tipo int puede crecer para almacenar cualquier entero de cualquier tamaño. Por su parte, el tipo float se implementa utilizando el estándar IEEE 754 para números de punto flotante con 64bits.

Por supuesto existen distintas peculiaridades y casos más específicos en distintos lenguajes de programación. Pero como vemos, tienen más en común que diferencias.

Problemas de precisión en los números de punto flotante Avanzado

Hemos comentado que la representación de números en coma flotante tiene limitaciones de precisión por la forma en la que trabajan.

En la mayoría de las ocasiones no supone un problema, pero conviene entenderlo correctamente. Porque, a veces ocurren situaciones “raras” o poco intuitivas al trabajar con ellos.

Por ejemplo, supongamos este ejemplo en Python.

resultado = 0.1 + 0.2
print(resultado)  # Resultado: 0.30000000000000004

¿Por qué sale un resultado tan raro? ¿Por qué no da 0.3, que es lo que debería ser? Pues este es el problema de trabajar con representación de números en coma flotante.

El problema es que los sistemas informáticos tienen una cantidad finita de bits para representar números, por lo que no se puede almacenar una fracción infinita con precisión perfecta.

Es importante tener en cuenta que este problema de precisión no es específico de un lenguaje de programación en particular. Ocurriría lo mismo en C#, en JavaScript, o en cualquier otro lenguaje. Es un problema inherente a la representación del número.

No voy a entrar en grandes detalles sobre la implementación interna, si queréis podéis buscar más información al respecto. Pero, de forma muy resumida, un número en coma flotante se representa con la siguiente expresión.

Donde

  • s, es el bit de signo (0 para positivo, 1 para negativo).
  • f, es la fracción (mantisa) del número en binario.
  • e, es el exponente en binario.
  • bias, es un valor constante utilizado para ajustar el rango del exponente.

Lo dicho, no voy a entrar muy profundo en la parte matemática del problema, pero de forma resumida un numero en coma flotante no es continuo, si no que estamos contando en pasitos muy pequeñitos.

Por ejemplo, si sumamos 0.1 y 0.2 en un sistema de coma flotante, podríamos esperar obtener 0.3 como resultado. Sin embargo, debido a la limitación de precisión, el resultado real podría ser 0.30000000000000004.

Es una diferencia muy pequeña, pero puede afectar cálculos sensibles. Estos problemas de precisión deben ser considerados al trabajar con cálculos que requieren una alta precisión, como operaciones financieras o científicas.

Para lidiar con este problema, se recomienda tener en cuenta la precisión de los cálculos y evitar la comparación directa de números en coma flotante utilizando operadores como == (igualdad).

float mi_variable = 0.3;

// no hagais esto
if(mi_variable == 0.3)
{
}

// mejor asi
const float THRESHOLD = 0.0001f;
if(Math.Abs(mi_variable - 0.3) < THRESHOLD)
{
}

En su lugar, se suelen utilizar técnicas como la comparación con un margen de error aceptable o el empleo de un tipo distinto de número que admita mayor precisión, según sea necesario.