При приведении int к byte или short Java просто отбрасывает старшие биты и оставляет младшие 8 или 16 (JLS, раздел 5.1.3). Если старший из оставшихся битов равен 1, число читается как отрицательное в дополнительном коде: поэтому (byte) 450 дает -62, а (short) 10_000_000 дает -27008. Ниже разбор по битам: как получить эти результаты вручную.

Кратко

  • (byte) i и (short) i это сужающее преобразование: остаются только младшие 8 или 16 бит, остальные отбрасываются без округления и без ошибки.
  • 450 в двоичной записи это 1 1100 0010; после отбрасывания лишних битов остается 1100 0010.
  • Старший бит 1 означает отрицательное число в дополнительном коде, поэтому читать биты как обычное число нельзя.
  • Чтобы получить модуль числа: вычесть единицу, инвертировать все биты (получится прямой код), перевести в десятичную систему и приписать минус: 1100 0010 превращается в 0011 1110 = 62, ответ -62.
  • Обратный путь, от числа к дополнительному коду: инвертировать биты положительного представления и прибавить 1: для 15 получится 1111 0001, это -15.
  • Для short все то же самое, только остаются 16 бит: (short) 10_000_000 дает -27008.
Рукописная таблица Лейбница: степени двойки в двоичной и десятичной записи

Пример 1: что дает (byte) 450

Почему, если привести какие-нибудь значения типа int к типу short или byte, результаты бывают неожиданными? Давайте разбираться!

int i = 450;
byte b = (byte)i;
System.out.println(b);
Результат на экране:

-62

Переводим 450 в двоичную систему

Неожиданно, но этому есть логическое объяснение, более того, это действие можно сделать собственноручно. Для этого, нам нужно перевести 450 из десятичной системы в двоичную:

450/2.    0
225/2.    1
112/2.    0
56/2.     0
28/2.     0
14/2.     0
7/2.      1
3/2.      1
1/2.      1
В правый столбик от числа мы записываем его остаток от деления на 2, а под самим числом пишем результат деления нашего числа на два, если остаток 0. Если остаток 1, то ниже записываем целую часть от деления на два. (Онлайн калькулятор с объяснением вычислений). В итоге, мы получаем, что в двоичной системе счисления 450 = 1 1100 0010. Любое число типа int занимает 4 байта или 32 бита, где каждый бит - это 0 или 1. В нашем случае заняты только 9 битов, и в принципе наш int i = 450 в двоичной системе выглядит так:

0000_0000_0000_0000_0000_0001_1100_0010
Мы хотим записать нашу переменную в переменную типа byte, но число типа byte занимает 1 байт (следует из названия этого типа) или 8 бит. Поэтому лишние биты слева просто отбрасываются, и в итоге мы получаем:

1100 0010

Знаковый бит и дополнительный код

Диапазон значений типа byte: от -128 до 127. Каждое число занимает 8 бит и у каждого числа крайний левый бит - знаковый бит (sign bit). У всех положительных чисел он равен 0, у всех отрицательных он равен 1. Спешить переводить наш результат, полученный выше, в 10-ную систему не нужно, т.к. мы получили дополнительный код искомого числа, а не прямой. Крайний бит слева получился равен 1, следовательно число у нас отрицательное, а у отрицательных чисел прямой и обратный код не совпадает, в отличие от положительных. Если бы знаковый бит был равен 0, то мы могли бы сразу перевести число в десятичную систему счисления и получить: 66. Но знаковый бит равен 1, поэтому сначала дополнительный код нужно перевести в прямой и добавить к ответу знак минус.

Тренировка: дополнительный код числа -15

Для наглядности и тренировки, сначала попробуем получить дополнительный код какого-нибудь числа, например -15. Для этого в прямом коде его положительного представления (числа 15) нужно поменять все 0 на 1 и наоборот (получить обратный код, также называют инверсный), а затем добавить к результату единицу. В двоичной системе 15 = 0000 1111; Обратный код (меняем все 0 на 1 и наоборот) = 1111 0000; Дополнительный код (прибавляем единицу):

1 1 1 1 0 0 0 0
0 0 0 0 0 0 0 1
1 1 1 1 0 0 0 1
Дополнительный код числа -15: 1111 0001;

Переводим дополнительный код 1100 0010 в прямой

Отлично. Теперь, по аналогии с примером выше, нам нужно перевести уже наш дополнительный код в прямой, напомню, он равен 1100 0010.
  1. Отнимаем единицу и получаем обратный код. Удобно делать так, записать дополнительный код в ответ и смотреть, к чему нужно прибавить единицу, чтобы получить такой дополнительный код. Начинаем с крайнего правого разряда и смотрим: к чему нужно прибавить 1 чтобы получить 0? К 1, получим 10, 0 уходит в ответ, а единичка на следующий разряд. Далее, что нужно прибавить к 0, чтобы получить один. Единицу, но так как мы с предыдущего разряда имеем единичку, то в ответ пишем 0. Далее, чтобы получить 0, что нужно прибавить к 0? Конечно, 0. Так еще 4 раза. И остались последние 2 разряда, где к 0 надо что-то прибавить, чтобы получить 1. Конечно, в обоих случаях нужно прибавить 1. Итого:

    
    1 1 0 0 0 0 0 1
    0 0 0 0 0 0 0 1
    1 1 0 0 0 0 1 0
    
  2. Самое сложное позади! Мы получили инверсный (обратный) код и нам осталось получить прямой. Инвертируем все 0 на 1 и наоборот:

    1100 0001 - инверсный код;

    0011 1110 - прямой код нашего числа, точнее его положительного представления;

  3. Переводим в десятичную систему счисления (Онлайн калькулятор с объяснением вычислений):

    0011 1110 = 0∙2^7+0∙2^6+1∙2^5+1∙2^4+1∙2^3+1∙2^2+1∙2^1+0∙2^0 = 0+0+32+16+8+4+2+0 = 62;

    Осталось только добавить к числу минус и наш ответ: -62.

Пример 2: что дает (short) 10_000_000

Таким же образом преобразуются числа типа int к типу short:

    int i = 10_000_000;
    short s = (short)i;
    System.out.println(s); // -27008
  1. 10.000.000 в 10-ной системе счисления = 0000 0000 1001 1000 1001 0110 1000 0000 в 2-ной.

    В Java число типа int занимает 4 байта, а short - 2 байта, или 16 бит, поэтому отсекаем слева до 16 цифр:

    1001 0110 1000 0000.

    Крайний левый бит (старший бит, он же знаковый бит) получился равен 1. Значит перед нами дополнительный код отрицательного числа, поэтому переходим к следующему пункту.

  2. Переводим в обратный код:

    
    1 0 0 1 0 1 1 0 0 1 1 1 1 1 1 1
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    1 0 0 1 0 1 1 0 1 0 0 0 0 0 0 0
    

    Обратный код: 1001 0110 0111 1111;

  3. Инвертируем и получаем прямой код: 0110 1001 1000 0000.

  4. Переводим в десятичную систему счисления и получаем положительное представление нашего числа:

    1∙2^14+1∙2^13+1∙2^11+1∙2^8+1∙2^7 = 16384+8192+2048+256+128 = 27008.
  5. Добавляем минус и получаем ответ: -27008

Алгоритм: как вручную привести int к byte или short

Соберем весь разбор в один список, он подходит для любого числа:
  1. Переведите число в двоичную систему: делите на 2 и записывайте остатки, затем прочитайте их снизу вверх.
  2. Оставьте младшие 8 бит для byte или 16 бит для short, все биты левее отбросьте.
  3. Посмотрите на старший из оставшихся битов. Если он 0, число положительное: переведите его в десятичную систему, и ответ готов.
  4. Если он 1, перед вами дополнительный код отрицательного числа: вычтите единицу и инвертируйте все биты, получится прямой код модуля числа.
  5. Переведите модуль в десятичную систему и припишите минус.

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

Почему (byte) 450 в Java дает -62?

Потому что byte хранит только 8 бит, а при явном приведении Java отбрасывает все старшие биты (JLS 5.1.3). 450 в двоичной записи это 1 1100 0010; после отбрасывания остается 1100 0010. Старший бит здесь 1, значит число отрицательное и записано в дополнительном коде: вычитаем единицу, инвертируем биты, получаем 0011 1110 = 62, итого -62.

Что такое дополнительный код и зачем он нужен?

Это способ записи отрицательных чисел, при котором старший бит означает знак, а само число получают из положительного представления инверсией всех битов и прибавлением единицы: 15 это 0000 1111, а -15 это 1111 0001. Благодаря этому процессор складывает и вычитает положительные и отрицательные числа одной и той же схемой, а ноль представлен единственным образом. Именно так Java хранит byte, short, int и long.

Как быстро посчитать результат приведения без перевода в двоичную систему?

Возьмите остаток от деления на 256 для byte или на 65536 для short, и если он больше 127 (для short больше 32767), вычтите 256 (65536). Для 450: 450 % 256 = 194, это больше 127, значит 194 - 256 = -62. Для 10 000 000 остаток от деления на 65536 равен 38528, и 38528 - 65536 = -27008. Для отрицательных чисел берите неотрицательный остаток (в Java его дает Math.floorMod): (byte) -450 равно 62.

Как проверить эти вычисления в Java?

Integer.toBinaryString(450) выведет 111000010, а Integer.toBinaryString(b & 0xFF), где b = (byte) 450, покажет младшие 8 бит: 11000010. Само значение печатает обычный System.out.println((byte) 450), а Byte.toUnsignedInt(b) вернет 194, то есть те же 8 бит, прочитанные как число без знака.

Почему компилятор не предупреждает о потере данных?

Явное приведение (byte) i это и есть ваше согласие на потерю: без него строка byte b = i; не скомпилируется. Исключение одно: константа, которая помещается в тип, присваивается без приведения, поэтому byte b = 100; допустимо, а byte b = 200; это ошибка компиляции (JLS, раздел 5.2). Остальные правила работы с примитивами разобраны в статье об операциях над примитивными типами.

Работает ли то же правило для long и для дробных чисел?

Для целых типов да: (int) от long, (short) и (byte) от int оставляют младшие 32, 16 или 8 бит. Для дробных иначе: при (int) 3.99 дробная часть отбрасывается и получается 3, слишком большие значения не заворачиваются по кругу, а превращаются в Integer.MAX_VALUE или Integer.MIN_VALUE, а NaN становится нулем. Если же привести double сразу к byte, Java сначала делает из него int, а потом отбрасывает старшие биты (JLS 5.1.3). Баннер JavaRush: научитесь программировать с нуля, 1200 задач с автопроверкой решения и стиля кодаСсылка на онлайн-калькулятор прямого, обратного и дополнительного кода. Также на этом сайте под калькулятором есть немного теории об обратном и дополнительном коде. Читайте также: