Почему '(int)(char) (byte) -2' производит 65534 в Java?



я столкнулся с этим вопросом в техническом тесте для работы. Приведен следующий пример кода:



public class Manager {
public static void main (String args[]) {
System.out.println((int) (char) (byte) -2);
}
}


Это дает выход как 65534.



это поведение показано только для отрицательных значений; 0 и положительные числа дают одно и то же значение, то есть введенное в SOP. Байт, брошенный здесь, незначителен; я пробовал без него.



Итак, мой вопрос: что именно здесь происходит?

538   4  

4 ответов:

есть некоторые предпосылки, которые мы должны согласовать, прежде чем вы сможете понять, что здесь происходит. С пониманием следующих пунктов маркера, остальное-простой вывод:

  1. все примитивные типы в JVM представлены в виде последовательности битов. Элемент int тип представлен 32 битами,char и short типы по 16 бит и byte тип представлен 8 битами.

  2. все номера JVM подписываются, где то char тип только без знака "номер". Когда число подписано, то высокий бит используется для представления знака числа. Для этого самого высокого бита,0 представляет собой неотрицательное число (положительное или нуль) и 1 представляет собой отрицательное число. Кроме того, с подписанными числами отрицательное значение перевернутый (технически известный как два дополнения нотации) к порядку приращения положительного числа. Например, положительный byte значение представлено в битах следующим образом:

    00 00 00 00 => (byte) 0
    00 00 00 01 => (byte) 1
    00 00 00 10 => (byte) 2
    ...
    01 11 11 11 => (byte) Byte.MAX_VALUE
    

    в то время как порядок битов для отрицательных чисел инвертируется:

    11 11 11 11 => (byte) -1
    11 11 11 10 => (byte) -2
    11 11 11 01 => (byte) -3
    ...
    10 00 00 00 => (byte) Byte.MIN_VALUE
    

    эта инвертированная нотация также объясняет, почему отрицательный диапазон может содержать дополнительное число по сравнению с положительным диапазоном, где последний включает представление числа 0. Помните, все это только вопрос перевод битовый шаблон. Вы можете отметить негативные числа по-разному, но эта инвертированная нотация для отрицательных чисел довольно удобна, потому что она позволяет выполнять некоторые довольно быстрые преобразования, как мы сможем увидеть в небольшом примере позже.

    как уже упоминалось, это не относится к char тип. Элемент char тип представляет собой символ Unicode с неотрицательным "числовой ряд"0 до 65535. Каждое из этих чисел относится к 16-бит Юникод значение.

  3. когда преобразование между int,byte,short,char и boolean типы JVM необходимо либо добавить, либо усечь биты.

    если целевой тип представлен большим количеством битов, чем тип, из которого он преобразуется, то JVM просто заполняет дополнительные слоты значением самого высокого бита данного значения (которое представляет подпись):

    |     short   |     byte    |
    |             | 00 00 00 01 | => (byte) 1
    | 00 00 00 00 | 00 00 00 01 | => (short) 1
    

    благодаря инвертированной нотации, эта стратегия также работает для отрицательных номера:

    |     short   |     byte    |
    |             | 11 11 11 11 | => (byte) -1
    | 11 11 11 11 | 11 11 11 11 | => (short) -1
    

    таким образом, знак значения сохраняется. Не вдаваясь в подробности реализации этого для JVM, обратите внимание, что эта модель позволяет выполнять кастинг дешевым операции сдвига что, очевидно, выгоднее.

    исключением из этого правила является расширение a char тип, который, как мы уже говорили, без знака. А конверсия с a char всегда применяется путем заполнения дополнительного биты с 0 потому что мы сказали, что нет никаких признаков и, следовательно, нет необходимости в перевернутом формате. Преобразование a char до выполняется так:

    |            int            |    char     |     byte    |
    |                           | 11 11 11 11 | 11 11 11 11 | => (char) \uFFFF
    | 00 00 00 00 | 00 00 00 00 | 11 11 11 11 | 11 11 11 11 | => (int) 65535
    

    если исходный тип имеет больше битов, чем целевой тип, дополнительные биты просто отрезать. До тех пор, пока исходное значение будет соответствовать целевому значению, это отлично работает, как, например, для следующего преобразования a short до byte:

    |     short   |     byte    |
    | 00 00 00 00 | 00 00 00 01 | => (short) 1
    |             | 00 00 00 01 | => (byte) 1
    | 11 11 11 11 | 11 11 11 11 | => (short) -1
    |             | 11 11 11 11 | => (byte) -1
    

    однако, если значение слишком большая или слишком маленький, это больше не работает:

    |     short   |     byte    |
    | 00 00 00 01 | 00 00 00 01 | => (short) 257
    |             | 00 00 00 01 | => (byte) 1
    | 11 11 11 11 | 00 00 00 00 | => (short) -32512
    |             | 00 00 00 00 | => (byte) 0
    
    ветвление что такое a дорогостоящая операция. Это особенно важно, так как это два дополнения нотации позволяет выполнять дешевые арифметические операции.

со всей этой информацией, мы можем видеть, что происходит с номером -2 в вашем примере:

|           int           |    char     |     byte    |
| 11 11 11 11 11 11 11 11 | 11 11 11 11 | 11 11 11 10 | => (int) -2
|                         |             | 11 11 11 10 | => (byte) -2
|                         | 11 11 11 11 | 11 11 11 10 | => (char) \uFFFE
| 00 00 00 00 00 00 00 00 | 11 11 11 11 | 11 11 11 10 | => (int) 65534

как вы можете видеть,byte приведение является избыточным как приведение к char отрезал бы такие же биты.

и указанные виртуальные машины, если вы предпочитаете более формальное определение всех этих правил.

последнее замечание: размер бита типа не обязательно представляет количество битов, зарезервированных JVM для представления этого типа в его памяти. На самом деле, JVM не различает boolean,byte,short,char и int типы. Все они представлены одним и тем же JVM-типом, где виртуальная машина просто эмулирует эти отливки. На стеке операндов метода (т. е. любая переменная в пределах метода) все значения именованных типов потребляют 32 бита. Однако это неверно для массивов и полей объектов, которые любой JVM-разработчик может обрабатывать по своему усмотрению.

здесь нужно отметить две важные вещи,

  1. символ без знака и не может быть отрицательным
  2. приведение байта к символу сначала включает скрытое приведение к int согласно Спецификация Языка Java.
литье -2 в int дает нам 11111111111111111111111111111110. Обратите внимание, как значение дополнения двух было расширено знаком с единицей; это происходит только для отрицательных значений. Когда мы сужаем его чар, int усекается до
1111111111111110

наконец, приведение 1111111111111110 к int растягивается на ноль, а не на единицу, потому что значение теперь считается положительным (потому что символы могут быть только положительными). Таким образом, расширение битов оставляет значение неизменным, но в отличие от отрицательного значения случай не меняется в значении. И это двоичное значение при печати в десятичном формате составляет 65534.

A char имеет значение от 0 до 65535, поэтому при приведении отрицательного значения к char результат будет таким же, как вычитание этого числа из 65536, что приведет к 65534. Если вы напечатали его как char, Он попытается отобразить любой символ Юникода, представленный 65534, но затем, когда вы приведете к int, вы на самом деле получаете 65534. Если вы начали с числа, которое было выше 65536, вы увидите аналогичные "запутанные" результаты, в которых большое число (например, 65538) будет в конечном итоге мало (2).

Я думаю, что самый простой способ объяснить это было бы просто разбить его на порядок при выполнении операций

Instance | #          int            |     char    | #   byte    |    result   |
Source   | 11 11 11 11 | 11 11 11 11 | 11 11 11 11 | 11 11 11 10 | -2          |
byte     |(11 11 11 11)|(11 11 11 11)|(11 11 11 11)| 11 11 11 10 | -2          |
int      | 11 11 11 11 | 11 11 11 11 | 11 11 11 11 | 11 11 11 10 | -2          |
char     |(00 00 00 00)|(00 00 00 00)| 11 11 11 11 | 11 11 11 10 | 65534       |
int      | 00 00 00 00 | 00 00 00 00 | 11 11 11 11 | 11 11 11 10 | 65534       |
  1. вы просто принимаете 32-битное значение со знаком.
  2. затем вы преобразуете его в 8-битное значение со знаком.
  3. при попытке преобразовать его в 16-битное беззнаковое значение компилятор быстро преобразует его в 32-битное знаковое значение,
  4. затем преобразование его в 16 бит без поддержания знак.
  5. когда происходит окончательное преобразование в 32 бит, нет никакого знака, поэтому значение добавляет нулевые биты для поддержания значения.

Итак, да, когда вы смотрите на это таким образом, приведение байтов является значительным (академически говоря), хотя результат незначителен (радость для программирования, значительное действие может иметь незначительный эффект). Эффект сужения и расширения при сохранении знака. Где преобразование в char сужается, но не расширяется до знак.

(обратите внимание, что я использовал # для обозначения подписанного бита, и, как уже отмечалось, нет подписанного бита для char, так как это беззнаковое значение).

я использовал parens для представления того, что на самом деле происходит внутри. Типы данных фактически транкируются в своих логических блоках, но если рассматривать их как int, их результаты будут тем, что символизируют parens.

подписанные значения всегда расширяются со значением подписанного бита. Unsigned всегда расширяется с помощью бита выключено.

*Итак, трюк (или gotchas) к этому заключается в том, что расширение до int из байта поддерживает знаковое значение при расширении. Который затем сужается в тот момент, когда он касается char. Это затем отключает подписанный бит.

Если бы преобразование в int не произошло, значение было бы 254. Но, это так, так что это не так.

Comments

    Ничего не найдено.