Menu

Typy danych w C#: int, double, decimal, char, bool i ich zakresy

Każdy wbudowany typ C# z rozmiarem, zakresem i przyrostkiem literału, różnica między typami wartościowymi a referencyjnymi, dlaczego decimal to typ do pieniędzy, przepełnienie liczb całkowitych i checked oraz wartości domyślne.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

C# jest typowany statycznie: każda zmienna, pole i wyrażenie ma typ znany w czasie kompilacji. Język ma zestaw typów wbudowanych z własnymi słowami kluczowymi (int, double, string...). Każde słowo kluczowe to alias typu z przestrzeni nazw System, więc int i System.Int32 to ten sam typ, a string i System.String to ten sam typ.

Typy wbudowane

TypTyp .NETRozmiarZakres lub precyzjaLiterał
boolBoolean1 bajttrue lub falsetrue
byteByte1 bajtod 0 do 255200
sbyteSByte1 bajtod -128 do 127-5
shortInt162 bajtyod -32,768 do 32,7671200
ushortUInt162 bajtyod 0 do 65,53560000
intInt324 bajtyod -2,147,483,648 do 2,147,483,64742
uintUInt324 bajtyod 0 do 4,294,967,29542u
longInt648 bajtówokoło ±9.2 × 10^1842L
ulongUInt648 bajtówod 0 do około 1.8 × 10^1942ul
floatSingle4 bajtyokoło ±3.4 × 10^38, ~6 do 9 cyfr2.5f
doubleDouble8 bajtówokoło ±1.8 × 10^308, ~15 do 17 cyfr2.5 lub 2.5d
decimalDecimal16 bajtówokoło ±7.9 × 10^28, 28 do 29 cyfr2.5m
charChar2 bajtyjedna jednostka kodowa UTF-16, od U+0000 do U+FFFF'A'
stringStringreferencjatekst dowolnej długości, niemodyfikowalny"hello"
objectObjectreferencjapodstawa każdego typunew object()

Literał całkowity bez przyrostka ma typ int, a jeśli jest za duży na int, to pierwszy z typów uint, long i ulong, który go pomieści; literał z kropką dziesiętną ma typ double. Dlatego float f = 2.5; i decimal d = 2.5; się nie kompilują: literał jest typu double, a nie ma niejawnej konwersji z double na float ani decimal. Dodaj przyrostek: 2.5f, 2.5m.

Każdy typ liczbowy udostępnia swoje granice jako stałe:

Wynik:

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

W praktyce większość kodu używa czterech z nich: int do liczników i indeksów, long do identyfikatorów i dużych sum, double do pomiarów i obliczeń matematycznych oraz decimal do pieniędzy.

decimal a double

double (i float) przechowują liczby w systemie dwójkowym. Większość ułamków dziesiętnych, w tym 0.1, nie ma dokładnej postaci binarnej, więc są zapisywane jako najbliższa możliwa wartość, a drobne błędy się kumulują. decimal przechowuje liczbę całkowitą w systemie dziesiętnym plus skalę, więc 0.1 to dokładnie 0.1.

Wynik:

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

Używaj decimal do pieniędzy, cen, podatków i wszystkiego, co człowiek zsumuje ręcznie i porówna. Zachowuje końcowe zera z danych wejściowych (1.50m wypisuje się jako 1.50) i ma od 28 do 29 cyfr znaczących.

Używaj double do pomiarów fizycznych, statystyki, geometrii i gier. Jest kilka razy szybszy, zajmuje połowę pamięci, a jego zakres jest ogromny. Nigdy nie porównuj dwóch obliczonych wartości double przez ==; porównaj różnicę z tolerancją: Math.Abs(x - y) < 1e-9.

decimal nie jest magiczny: 1m / 3m to nadal 0.3333333333333333333333333333, a arytmetyka na double nie rzuca wyjątku przy dzieleniu przez zero (daje nieskończoność albo NaN dla 0.0 / 0.0), podczas gdy dzielenie przez zero na decimal i liczbach całkowitych rzuca DivideByZeroException.

Przepełnienie liczb całkowitych i checked

Arytmetyka całkowita, która wychodzi poza zakres typu, domyślnie zawija się, po cichu. Kompilator wyłapuje przepełnienie w wyrażeniach stałych, ale nie wtedy, gdy wartości pochodzą ze zmiennych:

Wynik:

-2147483648
OverflowException
-1294967296
2147483648

checked(...) albo blok checked { ... } zamienia przepełnienie w OverflowException. Można to też włączyć dla całego projektu przez <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>. Zapis int x = int.MaxValue + 1; ze stałymi to błąd kompilacji (CS0220), bo kompilator widzi przepełnienie.

Ostatnia linia pokazuje rozwiązanie większości błędów przepełnienia: konwertuj na szerszy typ przed obliczeniem. (long)(max + 1) byłoby błędne, bo dodawanie zawinęło się, zanim zadziałało rzutowanie.

Typy wartościowe i referencyjne

Każdy typ w C# jest albo wartościowy, albo referencyjny, a ta różnica decyduje o tym, co robi przypisanie.

  • Typy wartościowe: wszystkie typy liczbowe, bool, char, DateTime, enumy i każda struct. Zmienna zawiera dane; przypisanie je kopiuje.
  • Typy referencyjne: string, tablice, object i każda class. Zmienna zawiera referencję do obiektu; przypisanie kopiuje referencję, więc obie zmienne widzą ten sam obiekt.

Wynik:

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

string to typ referencyjny, który w praktyce zachowuje się jak wartość: jest niemodyfikowalny, a == porównuje tekst, a nie referencje. Więcej o typach wartościowych definiowanych przez użytkownika w strukturach.

char i bool

char to jedna jednostka kodowa UTF-16, zapisywana w pojedynczych cudzysłowach. Pod spodem to typ liczbowy, więc konwertuje się na int i obsługuje arytmetykę:

Wynik:

66
C
True
True
7
b
True

bool ma dokładnie dwie wartości i nie konwertuje się na liczby ani z liczb: if (count) to błąd kompilacji, a nie test na wartość różną od zera. Napisz if (count != 0). Znaki spoza Basic Multilingual Plane, takie jak większość emotek, zajmują dwie wartości char (parę zastępczą), więc Length w string liczy jednostki UTF-16, a nie widoczne znaki.

Wartości domyślne

Pola, elementy tablic i default(T) dostają wartość domyślną typu: zero dla liczb, false dla bool, '\0' dla char i null dla typów referencyjnych. Zmienne lokalne nie dostają wartości domyślnej i trzeba je przypisać przed użyciem.

Wynik:

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

Od C# 7.1 można pisać literał default bez typu, gdy kompilator potrafi go wywnioskować (int x = default;). Typ wartościowy nie może być null i do tego służy int? (typ wartościowy nullable); zobacz typy nullable.

Liczby całkowite o rozmiarze natywnym i duże liczby całkowite

Dla szczególnych przypadków istnieją jeszcze dwa typy liczbowe. nint i nuint (C# 9) to liczby całkowite o rozmiarze wskaźnika, 32 lub 64 bity w zależności od platformy, używane przy współpracy z kodem natywnym i w kodzie niskopoziomowym. System.Numerics.BigInteger przechowuje liczby całkowite dowolnej wielkości kosztem szybkości:

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

Najczęściej zadawane pytania

Jakie są typy danych w C#?

Typy wbudowane to liczby całkowite (sbyte, byte, short, ushort, int, uint, long, ulong), typy zmiennoprzecinkowe float i double, decimal do dokładnej arytmetyki dziesiętnej, char dla znaku UTF-16, bool oraz typy referencyjne string i object. Każde słowo kluczowe to alias typu .NET: int to System.Int32, string to System.String.

decimal czy double do pieniędzy w C#?

Używaj decimal. double przechowuje ułamki binarne, więc wartości takich jak 0.1 nie da się zapisać dokładnie, a sumy się rozjeżdżają (0.1 + 0.2 == 0.3 daje false). decimal przechowuje cyfry dziesiętne, zachowuje od 28 do 29 cyfr znaczących i daje wyniki, jakich oczekują księgowi. double jest szybszy i ma znacznie większy zakres, więc używaj go w nauce, grafice i pomiarach.

Jaka jest maksymalna wartość int w C#?

int.MaxValue to 2,147,483,647, a int.MinValue to -2,147,483,648 (32-bitowa liczba całkowita ze znakiem). Dodanie 1 do int.MaxValue po cichu zawija się do int.MinValue, chyba że kod działa w kontekście checked, gdzie rzuca OverflowException. Dla większych wartości użyj long (do około 9,2 tryliona).

Czym różni się float od double w C#?

float ma 32 bity i około 6 do 9 cyfr znaczących; double ma 64 bity i około 15 do 17. Literały zmiennoprzecinkowe są domyślnie typu double, więc float wymaga przyrostka f: float speed = 2.5f;. Używaj double, chyba że pamięć albo API (na przykład Vector3 w Unity) wymaga float.

Czym różnią się typy wartościowe od referencyjnych w C#?

Zmienna typu wartościowego (int, double, bool, DateTime, dowolna struct) przechowuje same dane, a przypisanie je kopiuje. Zmienna typu referencyjnego (string, tablice, dowolna class) przechowuje referencję do obiektu na stercie, a przypisanie kopiuje referencję, więc dwie zmienne mogą wskazywać ten sam obiekt.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ