Menu

C# Data Types: int, double, decimal, char, bool and Their Ranges

Every built-in C# type with its size, range and literal suffix, the difference between value and reference types, why decimal is the type for money, integer overflow and checked, and default values.

This page includes runnable editors - edit, run, and see output instantly.

C# is statically typed: every variable, field and expression has a type known at compile time. The language has a set of built-in types with keywords of their own (int, double, string...). Each keyword is an alias for a type in the System namespace, so int and System.Int32 are the same type, and string and System.String are the same type.

The built-in types

Type.NET typeSizeRange or precisionLiteral
boolBoolean1 bytetrue or falsetrue
byteByte1 byte0 to 255200
sbyteSByte1 byte-128 to 127-5
shortInt162 bytes-32,768 to 32,7671200
ushortUInt162 bytes0 to 65,53560000
intInt324 bytes-2,147,483,648 to 2,147,483,64742
uintUInt324 bytes0 to 4,294,967,29542u
longInt648 bytesabout ±9.2 × 10^1842L
ulongUInt648 bytes0 to about 1.8 × 10^1942ul
floatSingle4 bytesabout ±3.4 × 10^38, ~6 to 9 digits2.5f
doubleDouble8 bytesabout ±1.8 × 10^308, ~15 to 17 digits2.5 or 2.5d
decimalDecimal16 bytesabout ±7.9 × 10^28, 28 to 29 digits2.5m
charChar2 bytesone UTF-16 code unit, U+0000 to U+FFFF'A'
stringStringreferenceany length of text, immutable"hello"
objectObjectreferencethe base of every typenew object()

An integer literal without a suffix is an int, or the first of uint, long and ulong that can hold it if it is too big for an int, and a literal with a decimal point is a double. That is why float f = 2.5; and decimal d = 2.5; fail to compile: the literal is a double, and there is no implicit conversion from double to float or decimal. Add the suffix: 2.5f, 2.5m.

Every numeric type exposes its limits as constants:

Output:

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

In practice most code uses four of these: int for counts and indexes, long for IDs and large totals, double for measurements and math, and decimal for money.

decimal vs double

double (and float) store numbers in binary. Most decimal fractions, including 0.1, have no exact binary form, so they are stored as the nearest representable value and small errors accumulate. decimal stores a base-10 integer plus a scale, so 0.1 is exactly 0.1.

Output:

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

Use decimal for money, prices, tax and anything a person will add up by hand and compare. It keeps trailing zeros from its inputs (1.50m prints as 1.50) and has 28 to 29 significant digits.

Use double for physical measurements, statistics, geometry and games. It is several times faster, uses half the memory, and its range is enormous. Never compare two computed doubles with ==; compare the difference against a tolerance: Math.Abs(x - y) < 1e-9.

decimal is not magic: 1m / 3m is still 0.3333333333333333333333333333, and double arithmetic does not throw on division by zero (it produces infinity, or NaN for 0.0 / 0.0), while decimal and integer division by zero throw a DivideByZeroException.

Integer overflow and checked

Integer arithmetic that exceeds the type's range wraps around by default, silently. The compiler catches overflow in constant expressions, but not when the values come from variables:

Output:

-2147483648
OverflowException
-1294967296
2147483648

checked(...) or a checked { ... } block turns overflow into an OverflowException. You can also turn it on for the whole project with <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>. Writing int x = int.MaxValue + 1; with constants is a compile error (CS0220), because the compiler can see the overflow.

The last line shows the fix for most overflow bugs: convert to a wider type before the arithmetic. (long)(max + 1) would be wrong, since the addition has already wrapped by the time the cast runs.

Value types and reference types

Every C# type is either a value type or a reference type, and the difference decides what assignment does.

  • Value types: all numeric types, bool, char, DateTime, enums, and every struct. The variable contains the data; assignment copies it.
  • Reference types: string, arrays, object, and every class. The variable contains a reference to an object; assignment copies the reference, so both variables see the same object.

Output:

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

string is a reference type that behaves like a value in practice: it is immutable, and == compares the text, not the references. More on user-defined value types in structs.

char and bool

A char is one UTF-16 code unit, written in single quotes. It is a numeric type underneath, so it converts to int and supports arithmetic:

Output:

66
C
True
True
7
b
True

bool has exactly two values and does not convert to or from numbers: if (count) is a compile error, not a test for non-zero. Write if (count != 0). Characters outside the Basic Multilingual Plane, such as most emoji, take two char values (a surrogate pair), so a string's Length counts UTF-16 units, not visible characters.

Default values

Fields, array elements and default(T) get the type's default value: zero for numbers, false for bool, '\0' for char, and null for reference types. Local variables get no default and must be assigned before use.

Output:

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

Since C# 7.1 you can write the default literal without the type when the compiler can infer it (int x = default;). A value type cannot be null, which is what int? (a nullable value type) is for; see nullable types.

Native-sized and big integers

Two more numeric types exist for special cases. nint and nuint (C# 9) are integers the size of a pointer, 32 or 64 bits depending on the platform, used in interop and low-level code. System.Numerics.BigInteger holds integers of any size, at the cost of speed:

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

Frequently Asked Questions

What are the data types in C#?

The built-in types are the integers (sbyte, byte, short, ushort, int, uint, long, ulong), the floating-point types float and double, decimal for exact decimal arithmetic, char for a UTF-16 character, bool, and the reference types string and object. Each keyword is an alias for a .NET type: int is System.Int32, string is System.String.

Should I use decimal or double for money in C#?

Use decimal. A double stores binary fractions, so values like 0.1 cannot be represented exactly and sums drift (0.1 + 0.2 == 0.3 is false). A decimal stores base-10 digits, keeps 28 to 29 significant digits, and gives the results accountants expect. double is faster and has a far larger range, so use it for science, graphics and measurements.

What is the max value of int in C#?

int.MaxValue is 2,147,483,647 and int.MinValue is -2,147,483,648 (a 32-bit signed integer). Adding 1 to int.MaxValue silently wraps to int.MinValue unless the code runs in a checked context, where it throws an OverflowException. Use long (up to about 9.2 quintillion) for larger values.

What is the difference between float and double in C#?

float is 32 bits with about 6 to 9 significant digits; double is 64 bits with about 15 to 17. Floating-point literals are double by default, so a float needs the f suffix: float speed = 2.5f;. Use double unless memory or an API (such as Unity's Vector3) calls for float.

What is the difference between value types and reference types in C#?

A value type variable (int, double, bool, DateTime, any struct) holds the data itself, and assignment copies it. A reference type variable (string, arrays, any class) holds a reference to an object on the heap, and assignment copies the reference, so two variables can point at the same object.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED