Переполнение
Калькулятор Java отбрасывает дробь при целом делении, и это одно его правило. Другое касается размера: у каждого целого типа ограниченное число разрядов.
Вспомните счётчик пробега в старой машине. У него шесть колёсиков с цифрами, и больше 999999 он показать не может. Проедьте ещё километр — и счётчик покажет 000000: колёсики провернулись и начали сначала.
С int происходит похожее. В него помещаются числа примерно от минус двух до плюс двух миллиардов. Посмотрим, что будет, если прибавить единицу к самому большому из них:
public class Main {
public static void main(String[] args) {
int max = Integer.MAX_VALUE;
System.out.println(max); // 2147483647
System.out.println(max + 1); // -2147483648
}
}Давайте разберём программу по строкам:
Integer.MAX_VALUE— константа из стандартной библиотеки, самое большое значениеint. Программа кладёт его вmax.System.out.println(max)печатает2147483647.System.out.println(max + 1)печатает-2147483648: число2147483648вintне помещается, и значение перешло на другой конец диапазона.
Ни ошибки компиляции, ни исключения: программа молча напечатала неверное число.
Здесь аналогия со счётчиком расходится с Java. У счётчика нет отрицательных чисел, а у int есть. Поэтому после максимума идёт не ноль, а самое маленькое отрицательное число.
Границы int
Самое маленькое значение int хранит константа Integer.MIN_VALUE, она равна -2147483648. Если результат не помещается в диапазон типа, происходит переполнение. В Java оно молчаливое в обе стороны:
- после максимума идёт минимум:
Integer.MAX_VALUE + 1равноInteger.MIN_VALUE; - ниже минимума идёт максимум:
Integer.MIN_VALUE - 1равноInteger.MAX_VALUE; - с
longвсё так же, только граница дальше:Long.MAX_VALUEравно9223372036854775807.
Этот вопрос часто задают на собеседовании: что даст Integer.MAX_VALUE + 1? Ответ: Integer.MIN_VALUE, без ошибки и без исключения.
Восемь битов byte
У int 32 бита, и следить сразу за всеми неудобно. Тип byte переполняется по тому же правилу, только битов у него восемь, а диапазон — от -128 до 127.
В каждом бите стоит 0 или 1, а число в byte — это сумма весов тех битов, которые равны единице. Веса идут справа налево: 1, 2, 4 и так до 64. У левого бита вес отрицательный, -128, поэтому его называют знаковым битом: если он равен единице, число отрицательное.
Дальше byte послужит уменьшенной моделью int. Кнопка x++ прибавляет к числу единицу: нажимайте её и следите, что станет со знаковым битом после 127.
x = 125: в byte восемь битов, самое большое число — 127. Нажмите x++.
В 127 единицы стоят во всех битах, кроме знакового: 0111 1111. Прибавление единицы переносит разряд справа налево до самого левого бита, и получается 1000 0000. Единица осталась только в знаковом бите, поэтому число равно -128. В int всё устроено так же, только знаковый бит весит -2147483648.
b++ и приведение к byte
Кнопка виджета подписана x++ не случайно: так в Java записывают «прибавить к переменной единицу». Давайте повторим то же самое в коде, а заодно посмотрим, что делает приведение к byte:
byte b = 127;
b++;
System.out.println(b); // -128
System.out.println((byte) 200); // -56b++увеличивает переменную на единицу. Тип при этом не меняется, поэтомуbпереходит через границу так же, как в виджете.(byte) 200приводитintкbyte. От числа остаются только восемь младших битов:1100 1000. Знаковый бит равен единице, поэтому получается-128 + 64 + 8, то есть-56.
Приведение к меньшему целому типу тоже не проверяет диапазон: лишние старшие биты молча отбрасываются.
Как защититься
Неверное число опаснее остановленной программы: с ним она продолжит считать деньги или время. Защититься можно двумя способами:
int max = Integer.MAX_VALUE;
long sum = (long) max + 1;
System.out.println(sum); // 2147483648
System.out.println(Math.addExact(max, 1));Exception in thread "main" java.lang.ArithmeticException: integer overflowСчитать в long
В long помещаются числа до 9223372036854775807, поэтому сумма двух int в нём не переполнится. Важно перейти в long до сложения: (long) max + 1 складывает уже два long и печатает 2147483648.
Проверять через Math.addExact
Math.addExact(a, b) возвращает сумму, если она помещается в тип, а иначе бросает ArithmeticException с текстом integer overflow. Для вычитания и умножения есть Math.subtractExact и Math.multiplyExact.
Частая ошибка
Число миллисекунд в 30 днях кладут в переменную long:
public class Main {
public static void main(String[] args) {
long ms = 30 * 24 * 60 * 60 * 1000; // ✗
System.out.println(ms); // -1702967296
}
}Переменная ms имеет тип long, но все числа справа — int. Поэтому Java перемножает их в int, произведение 2592000000 переполняется, и только потом неверное число попадает в long. Поставьте L у первого числа: 30L * 24 * 60 * 60 * 1000 считается в long и даёт 2592000000.
Главное
- Границы
int:Integer.MAX_VALUEравно2147483647,Integer.MIN_VALUEравно-2147483648. - Переполнение молчаливое:
Integer.MAX_VALUE + 1даётInteger.MIN_VALUEбез ошибки и без исключения. - Минус появляется из-за знакового бита: в
byte127 + 1даёт1000 0000, то есть-128. - От переполнения защищают переход в
longдо вычисления, как в30L * …, иMath.addExact, который бросаетArithmeticException.