Перейти к содержимому
Теория · Операторы · урок 9 из 10

Переполнение

На приборной панели старой машины стоит механический счётчик пробега: шесть колёсиков с цифрами. После 999999 километров колёсики прокручиваются, и счётчик снова показывает 000000. Седьмой цифры у него нет, и большее число ему негде записать.

Тип int устроен похоже. У него фиксированное число разрядов, поэтому он хранит числа только от int.MinValue до int.MaxValue — эти границы вы уже печатали в теме о типах. Что будет, если прибавить единицу к самому большому значению?

C#
int max = int.MaxValue;
Console.WriteLine(max); // 2147483647
Console.WriteLine(max + 1); // -2147483648
int min = int.MinValue;
Console.WriteLine(min - 1); // 2147483647

Давайте разберём программу по строкам:

  1. max хранит самое большое значение int, и программа печатает 2147483647.
  2. max + 1 печатает не 2147483648, а -2147483648: счёт перескочил на самое маленькое значение.
  3. min - 1 перескакивает в обратную сторону, на самое большое значение.

Здесь картинка со счётчиком пробега расходится с int. Счётчик после максимума показывает ноль, а int — самое маленькое отрицательное число. Половина значений int отрицательна, и после верхнего края счёт продолжается с нижнего.

Без проверки по умолчанию

Когда результат не помещается в тип, говорят о переполнении. По умолчанию C# не проверяет целые операции на переполнение: лишние двоичные разряды отбрасываются, и значение молча перескакивает через край. Ни ошибки, ни исключения нет, и программа считает дальше с неверным числом.

Этот вопрос часто задают на собеседовании: что будет, если к int.MaxValue прибавить единицу? Ответ: при выполнении получится int.MinValue, без ошибки и без исключения.

Если нужны числа больше 2 млрд, берите long: его максимум состоит из 19 цифр. Но и long переполняется точно так же, только намного позже.

Проверка через checked

Молчаливое переполнение опасно: программа выдаёт неверный результат и ничем этого не показывает. Чтобы C# проверял операцию, оберните её в checked(...). Тогда переполнение выбрасывает исключение OverflowException, и программа останавливается на этой строке:

C#
int max = int.MaxValue;
Console.WriteLine(checked(max + 1)); // ✗
TEXT
Unhandled exception. System.OverflowException: Arithmetic operation resulted in an overflow.

Слово checked можно поставить и перед фигурными скобками: checked { max++; } проверяет каждую целую операцию внутри них.

Переполнение при компиляции

Если выражение состоит только из литералов и констант вроде int.MaxValue, компилятор вычисляет результат ещё до запуска и сам замечает переполнение. Такая строка не компилируется:

C#
Console.WriteLine(int.MaxValue + 1); // ✗
TEXT
error CS0220: The operation overflows at compile time in checked mode

Для таких выражений проверка включена по умолчанию — об этом и говорят слова checked mode в сообщении. Поэтому в примерах урока максимум сначала кладут в переменную max: значение переменной компилятор заранее не вычисляет.

Частая ошибка

Произведение двух int кладут в long, надеясь, что большой тип вместит результат:

C#
int million = 1000000;
long wrong = million * million; // ✗
Console.WriteLine(wrong); // -727379968
long right = (long)million * million;
Console.WriteLine(right); // 1000000000000

Компилятор ошибки не видит, а программа печатает -727379968. Оба операнда в million * million имеют тип int, поэтому и умножение выполняется в int: результат переполняется раньше, чем попадает в long. Приведите к long один из операндов: (long)million * million умножает уже в long и даёт 1000000000000.

Главное

  • int хранит числа от int.MinValue до int.MaxValue; за краем значение молча перескакивает на другой край.
  • checked превращает переполнение в исключение OverflowException.
  • Если в выражении одни литералы и константы, переполнение находит компилятор, и такой код не компилируется.
  • Тип результата задают операнды: int * int переполняется раньше, чем попадает в long.