Статьи

Несовместимая операция расширяет правила в Java

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

умножение

Когда вы выполняете умножение, вы часто получаете намного большее число, чем любое из отдельных чисел по величине. то есть | a * b | >> | а | и | a * b | >> | б | часто бывает. И для небольших типов это работает как ожидалось.

Рассмотрим эту программу

public static void main(String[] args) throws IOException {
    System.out.println(is(Byte.MAX_VALUE * Byte.MAX_VALUE));
    System.out.println(is(Short.MAX_VALUE * Short.MAX_VALUE));
    System.out.println(is(Character.MAX_VALUE * Character.MAX_VALUE));
    System.out.println(is(Integer.MAX_VALUE * Integer.MAX_VALUE));
    System.out.println(is(Long.MAX_VALUE * Long.MAX_VALUE));
}
static String is(byte b) {
    return "byte: " + b;
}
static String is(char ch) {
    return "char: " + ch;
}
static String is(short i) {
    return "short: " + i;
}
static String is(int i) {
    return "int: " + i;
}
static String is(long l) {
    return "long: " + l;
}

какие отпечатки
int: 16129
int: 1073676289
int: -131071
int: 1
long: 1

Только байты * байты и короткие * короткие не переполняются, поскольку они были расширены. char * char не является значимой операцией, даже если она разрешена. Но int * int переполняется, хотя у нас есть длинный тип, который может хранить это значение без переполнения. И байты и короткие расширяются неявно, но не int. longshould действительно расширить, но у нас нет более широкого примитивного типа, который имел бы смысл когда-то, однако 64-битный примитив не кажется таким длинным в наши дни.

разделение


Деление немного странно в том смысле, что делитель может расширить результат.
Наличие более широкого делителя, чем у числителя, не означает, что результат будет больше (но обычно он меньше)
System.out.println(is(Byte.MAX_VALUE / (byte) 1));
System.out.println(is(Byte.MAX_VALUE / (short) 1));
System.out.println(is(Byte.MAX_VALUE / (char) 1));
System.out.println(is(Byte.MAX_VALUE / (int) 1));
System.out.println(is(Byte.MAX_VALUE/ (long) 1));

печать

int: 127
int: 127
int: 127
int: 127
long: 127

Когда вы делите байт / байт, вы получаете целое число, даже если вы не можете получить значение больше байта.
(если вы не разделите Byte.MIN_VALUE на -1, в этом случае будет иметь место короткое замыкание), и если вы разделите байт / длинное, вы получите длинное, даже если значение по-прежнему не может быть больше байта.

модуль


Когда вы выполняете модуль a% b, результат не может быть больше, чем b.
И все же модуль будет шире, а не уменьшит результат.
System.out.println(is(Byte.MAX_VALUE % Byte.MAX_VALUE));
System.out.println(is(Byte.MAX_VALUE % Short.MAX_VALUE));
System.out.println(is(Byte.MAX_VALUE % Character.MAX_VALUE));
System.out.println(is(Byte.MAX_VALUE % Integer.MAX_VALUE));
System.out.println(is(Byte.MAX_VALUE % Long.MAX_VALUE));
   
System.out.println(is(Byte.MAX_VALUE % (byte) 2));
System.out.println(is(Short.MAX_VALUE % (byte) 2));
System.out.println(is(Character.MAX_VALUE % (byte) 2));
System.out.println(is(Integer.MAX_VALUE % (byte) 2));
System.out.println(is(Long.MAX_VALUE % (byte) 2));
печать

int: 0
int: 127
int: 127
int: 127
long: 127
int: 1
int: 1
int: 1
int: 1
long: 1

Если вы модулируете X числом, результат не может быть больше / больше, чем X, он может быть только меньше.
Тем не менее, JLS говорят, что должно стать шире. Если вы модулируете X байтом, результат может быть только в диапазоне байта.

Отрицание


Я также упомянул унарные операции, и, возможно, самый простой — унарный минус.
System.out.println(is(-Byte.MIN_VALUE));
System.out.println(is(-Short.MIN_VALUE));
System.out.println(is(-Character.MIN_VALUE));
System.out.println(is(-Integer.MIN_VALUE));
System.out.println(is(-Long.MIN_VALUE));

печать
int: 128
int: 32768
int: 0
int: -2147483648
long: -9223372036854775808

В первых трех случаях тип расширен.
Байт может быть расширен до короткого, но это правильно как int. Однако для int и long он не расширен, и вы можете получить редкое переполнение.

Немного шансов — унарный плюс, который не меняет значение (и, следовательно, не может изменить его диапазон), но может расширить значение.
System.out.println(is(+Byte.MIN_VALUE));
System.out.println(is(+Short.MIN_VALUE));
System.out.println(is(+Character.MIN_VALUE));
System.out.println(is(+Integer.MIN_VALUE));
System.out.println(is(+Long.MIN_VALUE));

печать
int: -128
int: -32768
int: 0
int: -2147483648
long: -9223372036854775808

Можем ли мы это исправить?


К сожалению нет.
Слишком много кода зависит от этой логики. Например, скажем, вы пишете что-то вроде этого
long i = ...
byte b = ...
long l = i % b + Integer.MAX_VALUE;

Если бы я% b превратился из длинного в байт, это выражение могло бы переполниться.

Вывод


Java может расширять некоторые значения, когда это необходимо, но также не может расширять некоторые int-операции, которые действительно должны быть длинными.
Это никогда не даст более узкого результата, даже если это может быть более логичным.

Нам нужно знать о крайних случаях, в частности int * int, и знать, как расширять их самостоятельно, когда мы видим такую ​​операцию.
например

длинный l = (длинный) a * b;

Если мы не уверены, что a * b будет соответствовать значению int.