Menu

Tipos de datos en C#: int, double, decimal, char, bool y sus rangos

Todos los tipos incorporados de C# con su tamaño, su rango y el sufijo de sus literales, la diferencia entre tipos de valor y de referencia, por qué decimal es el tipo para el dinero, el desbordamiento de enteros y checked, y los valores por defecto.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

C# tiene tipado estático: cada variable, campo y expresión tiene un tipo conocido en tiempo de compilación. El lenguaje tiene un conjunto de tipos incorporados con palabras clave propias (int, double, string...). Cada palabra clave es un alias de un tipo del namespace System, así que int y System.Int32 son el mismo tipo, y string y System.String también.

Los tipos incorporados

TipoTipo .NETTamañoRango o precisiónLiteral
boolBoolean1 bytetrue o falsetrue
byteByte1 bytede 0 a 255200
sbyteSByte1 bytede -128 a 127-5
shortInt162 bytesde -32.768 a 32.7671200
ushortUInt162 bytesde 0 a 65.53560000
intInt324 bytesde -2.147.483.648 a 2.147.483.64742
uintUInt324 bytesde 0 a 4.294.967.29542u
longInt648 bytesunos ±9,2 × 10^1842L
ulongUInt648 bytesde 0 a unos 1,8 × 10^1942ul
floatSingle4 bytesunos ±3,4 × 10^38, ~6 a 9 dígitos2.5f
doubleDouble8 bytesunos ±1,8 × 10^308, ~15 a 17 dígitos2.5 o 2.5d
decimalDecimal16 bytesunos ±7,9 × 10^28, 28 a 29 dígitos2.5m
charChar2 bytesuna unidad de código UTF-16, de U+0000 a U+FFFF'A'
stringStringreferenciatexto de cualquier longitud, inmutable"hello"
objectObjectreferenciala base de todos los tiposnew object()

Un literal entero sin sufijo es un int, o el primero de uint, long y ulong que pueda contenerlo si es demasiado grande para un int, y un literal con punto decimal es un double. Por eso float f = 2.5; y decimal d = 2.5; no compilan: el literal es un double, y no hay conversión implícita de double a float ni a decimal. Añade el sufijo: 2.5f, 2.5m.

Cada tipo numérico expone sus límites como constantes:

Salida:

byte:  0 to 255
short: -32768 to 32767
int:   -2147483648 to 2147483647
long:  -9223372036854775808 to 9223372036854775807
decimal max: 79228162514264337593543950335
double max:  1.798E+308
sizes: int 4, long 8, double 8, char 2
255 161 3000000000

En la práctica, la mayor parte del código usa cuatro de ellos: int para recuentos e índices, long para ID y totales grandes, double para medidas y matemáticas, y decimal para el dinero.

decimal frente a double

double (y float) guardan los números en binario. La mayoría de las fracciones decimales, incluida 0,1, no tienen una forma binaria exacta, así que se guardan como el valor representable más cercano y los pequeños errores se acumulan. decimal guarda un entero en base 10 más una escala, así que 0,1 es exactamente 0,1.

Salida:

False
True
0.30000000000000004
0.3
double sum == 1.0: False
decimal sum == 1.0: True
Total: 59.97
Split three ways: 19.99

Usa decimal para el dinero, los precios, los impuestos y todo lo que una persona vaya a sumar a mano y comparar. Conserva los ceros finales de sus entradas (1.50m se imprime como 1.50) y tiene de 28 a 29 dígitos significativos.

Usa double para medidas físicas, estadística, geometría y juegos. Es varias veces más rápido, usa la mitad de memoria y su rango es enorme. Nunca compares dos doubles calculados con ==; compara la diferencia con una tolerancia: Math.Abs(x - y) < 1e-9.

decimal no hace magia: 1m / 3m sigue siendo 0.3333333333333333333333333333, y la aritmética double no lanza una excepción al dividir entre cero (produce infinito, o NaN con 0.0 / 0.0), mientras que la división entre cero de decimal y de los enteros lanza una DivideByZeroException.

Desbordamiento de enteros y checked

La aritmética entera que supera el rango del tipo da la vuelta por defecto, en silencio. El compilador detecta el desbordamiento en las expresiones constantes, pero no cuando los valores vienen de variables:

Salida:

-2147483648
OverflowException
-1294967296
2147483648

checked(...) o un bloque checked { ... } convierte el desbordamiento en una OverflowException. También puedes activarlo para todo el proyecto con <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>. Escribir int x = int.MaxValue + 1; con constantes es un error de compilación (CS0220), porque el compilador puede ver el desbordamiento.

La última línea muestra la solución para la mayoría de los bugs de desbordamiento: convierte a un tipo más ancho antes de la aritmética. (long)(max + 1) estaría mal, porque la suma ya ha dado la vuelta cuando se ejecuta el cast.

Tipos de valor y tipos de referencia

Todo tipo de C# es un tipo de valor o un tipo de referencia, y la diferencia decide qué hace la asignación.

  • Tipos de valor: todos los tipos numéricos, bool, char, DateTime, los enums y todos los struct. La variable contiene los datos; la asignación los copia.
  • Tipos de referencia: string, los arrays, object y todas las class. La variable contiene una referencia a un objeto; la asignación copia la referencia, así que las dos variables ven el mismo objeto.

Salida:

a=10 b=99
first[0]=99
s1=cat s2=cats

string es un tipo de referencia que en la práctica se comporta como un valor: es inmutable, y == compara el texto, no las referencias. Más sobre los tipos de valor definidos por el usuario en structs.

char y bool

Un char es una unidad de código UTF-16, escrita entre comillas simples. Por debajo es un tipo numérico, así que se convierte a int y admite aritmética:

Salida:

66
C
True
True
7
b
True

bool tiene exactamente dos valores y no se convierte a números ni desde ellos: if (count) es un error de compilación, no una prueba de distinto de cero. Escribe if (count != 0). Los caracteres fuera del plano multilingüe básico, como la mayoría de los emoji, ocupan dos valores char (un par suplente), así que el Length de un string cuenta unidades UTF-16, no caracteres visibles.

Valores por defecto

Los campos, los elementos de los arrays y default(T) reciben el valor por defecto del tipo: cero para los números, false para bool, '\0' para char y null para los tipos de referencia. Las variables locales no reciben ningún valor por defecto y deben asignarse antes de usarse.

Salida:

0
0
False
True
0001-01-01
0,0,0

Desde C# 7.1 puedes escribir el literal default sin el tipo cuando el compilador puede deducirlo (int x = default;). Un tipo de valor no puede ser null, y para eso existe int? (un tipo de valor nullable); consulta tipos nullable.

Enteros de tamaño nativo y enteros grandes

Existen dos tipos numéricos más para casos especiales. nint y nuint (C# 9) son enteros del tamaño de un puntero, de 32 o 64 bits según la plataforma, que se usan en la interoperabilidad y en código de bajo nivel. System.Numerics.BigInteger contiene enteros de cualquier tamaño, a costa de la velocidad:

using System.Numerics;
BigInteger factorial = 1;
for (int i = 2; i <= 30; i++) factorial *= i;
// 265252859812191058636308480000000

Preguntas frecuentes

¿Cuáles son los tipos de datos de C#?

Los tipos incorporados son los enteros (sbyte, byte, short, ushort, int, uint, long, ulong), los tipos de coma flotante float y double, decimal para la aritmética decimal exacta, char para un carácter UTF-16, bool, y los tipos de referencia string y object. Cada palabra clave es un alias de un tipo de .NET: int es System.Int32 y string es System.String.

¿Debo usar decimal o double para el dinero en C#?

Usa decimal. Un double guarda fracciones binarias, así que valores como 0,1 no pueden representarse exactamente y las sumas se desvían (0.1 + 0.2 == 0.3 es false). Un decimal guarda dígitos en base 10, mantiene de 28 a 29 dígitos significativos y da los resultados que esperan los contables. double es más rápido y tiene un rango muchísimo mayor, así que úsalo para ciencia, gráficos y medidas.

¿Cuál es el valor máximo de int en C#?

int.MaxValue es 2.147.483.647 e int.MinValue es -2.147.483.648 (un entero de 32 bits con signo). Sumar 1 a int.MaxValue da la vuelta en silencio hasta int.MinValue, salvo que el código se ejecute en un contexto checked, donde lanza una OverflowException. Usa long (hasta unos 9,2 trillones) para valores más grandes.

¿Qué diferencia hay entre float y double en C#?

float tiene 32 bits y unos 6 a 9 dígitos significativos; double tiene 64 bits y unos 15 a 17. Los literales de coma flotante son double por defecto, así que un float necesita el sufijo f: float speed = 2.5f;. Usa double salvo que la memoria o una API (como el Vector3 de Unity) pidan float.

¿Qué diferencia hay entre tipos de valor y tipos de referencia en C#?

Una variable de un tipo de valor (int, double, bool, DateTime, cualquier struct) contiene los propios datos, y la asignación los copia. Una variable de un tipo de referencia (string, los arrays, cualquier class) contiene una referencia a un objeto del heap, y la asignación copia la referencia, así que dos variables pueden apuntar al mismo objeto.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR