Переполнение
На приборной панели старой машины стоит механический счётчик пробега: шесть колёсиков с цифрами. После 999999 километров колёсики прокручиваются, и счётчик снова показывает 000000. Седьмой цифры у него нет, и большее число ему негде записать.
Тип int устроен похоже. У него фиксированное число разрядов, поэтому он хранит числа только от int.MinValue до int.MaxValue — эти границы вы уже печатали в теме о типах. Что будет, если прибавить единицу к самому большому значению?
int max = int.MaxValue;
Console.WriteLine(max); // 2147483647
Console.WriteLine(max + 1); // -2147483648
int min = int.MinValue;
Console.WriteLine(min - 1); // 2147483647Давайте разберём программу по строкам:
maxхранит самое большое значениеint, и программа печатает2147483647.max + 1печатает не2147483648, а-2147483648: счёт перескочил на самое маленькое значение.min - 1перескакивает в обратную сторону, на самое большое значение.
Здесь картинка со счётчиком пробега расходится с int. Счётчик после максимума показывает ноль, а int — самое маленькое отрицательное число. Половина значений int отрицательна, и после верхнего края счёт продолжается с нижнего.
Без проверки по умолчанию
Когда результат не помещается в тип, говорят о переполнении. По умолчанию C# не проверяет целые операции на переполнение: лишние двоичные разряды отбрасываются, и значение молча перескакивает через край. Ни ошибки, ни исключения нет, и программа считает дальше с неверным числом.
Этот вопрос часто задают на собеседовании: что будет, если к int.MaxValue прибавить единицу? Ответ: при выполнении получится int.MinValue, без ошибки и без исключения.
Если нужны числа больше 2 млрд, берите long: его максимум состоит из 19 цифр. Но и long переполняется точно так же, только намного позже.
Проверка через checked
Молчаливое переполнение опасно: программа выдаёт неверный результат и ничем этого не показывает. Чтобы C# проверял операцию, оберните её в checked(...). Тогда переполнение выбрасывает исключение OverflowException, и программа останавливается на этой строке:
int max = int.MaxValue;
Console.WriteLine(checked(max + 1)); // ✗Unhandled exception. System.OverflowException: Arithmetic operation resulted in an overflow.Слово checked можно поставить и перед фигурными скобками: checked { max++; } проверяет каждую целую операцию внутри них.
Переполнение при компиляции
Если выражение состоит только из литералов и констант вроде int.MaxValue, компилятор вычисляет результат ещё до запуска и сам замечает переполнение. Такая строка не компилируется:
Console.WriteLine(int.MaxValue + 1); // ✗error CS0220: The operation overflows at compile time in checked modeДля таких выражений проверка включена по умолчанию — об этом и говорят слова checked mode в сообщении. Поэтому в примерах урока максимум сначала кладут в переменную max: значение переменной компилятор заранее не вычисляет.
Частая ошибка
Произведение двух int кладут в long, надеясь, что большой тип вместит результат:
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.