Menu

Типы данных в C#: int, double, decimal, char, bool и их диапазоны

Все встроенные типы C# с размером, диапазоном и суффиксом литерала, разница между типами значений и ссылочными типами, почему decimal подходит для денег, переполнение целых чисел и checked, значения по умолчанию.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

C# статически типизирован: у каждой переменной, поля и выражения есть тип, известный при компиляции. В языке есть набор встроенных типов со своими ключевыми словами (int, double, string...). Каждое ключевое слово это псевдоним типа из пространства имён System, поэтому int и System.Int32 это один и тот же тип, и string и System.String тоже один и тот же тип.

Встроенные типы

ТипТип .NETРазмерДиапазон или точностьЛитерал
boolBoolean1 байтtrue или falsetrue
byteByte1 байтот 0 до 255200
sbyteSByte1 байтот -128 до 127-5
shortInt162 байтаот -32 768 до 32 7671200
ushortUInt162 байтаот 0 до 65 53560000
intInt324 байтаот -2 147 483 648 до 2 147 483 64742
uintUInt324 байтаот 0 до 4 294 967 29542u
longInt648 байтпримерно ±9,2 × 10^1842L
ulongUInt648 байтот 0 примерно до 1,8 × 10^1942ul
floatSingle4 байтапримерно ±3,4 × 10^38, ~6 до 9 цифр2.5f
doubleDouble8 байтпримерно ±1,8 × 10^308, ~15 до 17 цифр2.5 или 2.5d
decimalDecimal16 байтпримерно ±7,9 × 10^28, 28 до 29 цифр2.5m
charChar2 байтаодна кодовая единица UTF-16, от U+0000 до U+FFFF'A'
stringStringссылкатекст любой длины, неизменяемый"hello"
objectObjectссылкабазовый тип для всех типовnew object()

Целочисленный литерал без суффикса имеет тип int, а если он слишком велик для int, то первый из uint, long и ulong, в который он помещается; литерал с десятичной точкой имеет тип double. Поэтому float f = 2.5; и decimal d = 2.5; не компилируются: литерал имеет тип double, а неявного преобразования из double во float или decimal нет. Добавьте суффикс: 2.5f, 2.5m.

Каждый числовой тип предоставляет свои границы в виде констант:

Вывод:

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

На практике большая часть кода использует четыре из них: int для счётчиков и индексов, long для идентификаторов и больших сумм, double для измерений и математики и decimal для денег.

decimal или double

double (и float) хранят числа в двоичном виде. У большинства десятичных дробей, включая 0.1, нет точного двоичного представления, поэтому они хранятся как ближайшее представимое значение, и мелкие ошибки накапливаются. decimal хранит десятичное целое плюс масштаб, поэтому 0.1 это ровно 0.1.

Вывод:

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

Используйте decimal для денег, цен, налогов и всего, что человек будет складывать вручную и сверять. Он сохраняет нули в конце из входных данных (1.50m печатается как 1.50) и имеет от 28 до 29 значащих цифр.

Используйте double для физических измерений, статистики, геометрии и игр. Он в несколько раз быстрее, занимает вдвое меньше памяти, а его диапазон огромен. Никогда не сравнивайте два вычисленных double через ==; сравнивайте разницу с допуском: Math.Abs(x - y) < 1e-9.

decimal не волшебный: 1m / 3m всё равно равно 0.3333333333333333333333333333, а арифметика double не выбрасывает исключение при делении на ноль (получается бесконечность или NaN для 0.0 / 0.0), тогда как деление на ноль у decimal и целых чисел выбрасывает DivideByZeroException.

Переполнение целых чисел и checked

Целочисленная арифметика, выходящая за диапазон типа, по умолчанию молча переходит через границу. Компилятор ловит переполнение в константных выражениях, но не тогда, когда значения берутся из переменных:

Вывод:

-2147483648
OverflowException
-1294967296
2147483648

checked(...) или блок checked { ... } превращает переполнение в OverflowException. Можно также включить проверку для всего проекта через <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>. Запись int x = int.MaxValue + 1; с константами это ошибка компиляции (CS0220), потому что компилятор видит переполнение.

Последняя строка показывает исправление большинства ошибок переполнения: преобразуйте в более широкий тип до арифметики. (long)(max + 1) было бы неправильно, потому что к моменту приведения сложение уже переполнилось.

Типы значений и ссылочные типы

Каждый тип C# является либо типом значения, либо ссылочным типом, и от этого зависит, что делает присваивание.

  • Типы значений: все числовые типы, bool, char, DateTime, перечисления и любая struct. Переменная содержит данные; присваивание их копирует.
  • Ссылочные типы: string, массивы, object и любой class. Переменная содержит ссылку на объект; присваивание копирует ссылку, поэтому обе переменные видят один и тот же объект.

Вывод:

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

string это ссылочный тип, который на практике ведёт себя как значение: он неизменяемый, а == сравнивает текст, а не ссылки. Подробнее о пользовательских типах значений в разделе структуры.

char и bool

char это одна кодовая единица UTF-16, записанная в одинарных кавычках. Внутри это числовой тип, поэтому он преобразуется в int и поддерживает арифметику:

Вывод:

66
C
True
True
7
b
True

У bool ровно два значения, и он не преобразуется в числа и из чисел: if (count) это ошибка компиляции, а не проверка на ненулевое значение. Пишите if (count != 0). Символы за пределами базовой многоязычной плоскости, например большинство эмодзи, занимают два значения char (суррогатную пару), поэтому Length у string считает единицы UTF-16, а не видимые символы.

Значения по умолчанию

Поля, элементы массивов и default(T) получают значение по умолчанию для своего типа: ноль для чисел, false для bool, '\0' для char и null для ссылочных типов. Локальные переменные значения по умолчанию не получают, и им нужно присвоить значение перед использованием.

Вывод:

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

Начиная с C# 7.1 можно писать литерал default без типа, если компилятор может его вывести (int x = default;). Тип значения не может быть null, и именно для этого существует int? (тип значения, допускающий null); см. типы, допускающие null.

Целые числа размера указателя и большие целые

Для особых случаев есть ещё два числовых типа. nint и nuint (C# 9) это целые числа размера указателя, 32 или 64 бита в зависимости от платформы, используемые во взаимодействии с нативным кодом и в низкоуровневом коде. System.Numerics.BigInteger хранит целые числа любого размера ценой скорости:

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

Часто задаваемые вопросы

Какие типы данных есть в C#?

Встроенные типы: целые числа (sbyte, byte, short, ushort, int, uint, long, ulong), типы с плавающей точкой float и double, decimal для точной десятичной арифметики, char для символа UTF-16, bool и ссылочные типы string и object. Каждое ключевое слово это псевдоним типа .NET: int это System.Int32, string это System.String.

Что использовать для денег в C#, decimal или double?

Используйте decimal. double хранит двоичные дроби, поэтому значения вроде 0.1 нельзя представить точно, и суммы расходятся (0.1 + 0.2 == 0.3 даёт false). decimal хранит десятичные цифры, сохраняет от 28 до 29 значащих цифр и даёт результаты, которых ожидают бухгалтеры. double быстрее и имеет намного больший диапазон, поэтому его используют в науке, графике и измерениях.

Какое максимальное значение int в C#?

int.MaxValue равно 2 147 483 647, а int.MinValue равно -2 147 483 648 (32-битное знаковое целое). Прибавление 1 к int.MaxValue молча переходит в int.MinValue, если код не выполняется в контексте checked, где выбрасывается OverflowException. Для больших значений используйте long (примерно до 9,2 квинтиллиона).

Чем float отличается от double в C#?

float занимает 32 бита и даёт примерно от 6 до 9 значащих цифр; double занимает 64 бита и даёт примерно от 15 до 17. Литералы с плавающей точкой по умолчанию имеют тип double, поэтому для float нужен суффикс f: float speed = 2.5f;. Используйте double, если только память или API (например, Vector3 в Unity) не требуют float.

Чем типы значений отличаются от ссылочных типов в C#?

Переменная типа значения (int, double, bool, DateTime, любая struct) хранит сами данные, и присваивание их копирует. Переменная ссылочного типа (string, массивы, любой class) хранит ссылку на объект в куче, и присваивание копирует ссылку, поэтому две переменные могут указывать на один и тот же объект.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ