Почему байтовое и короткое деление приводит к int в Java?

В Java, если мы разделим bytes, shorts или ints, мы всегда получим int. Если один из операндов long, мы получим long.

Мой вопрос: почему деление byte или short не приводит к byte или short? Почему всегда int?

По-видимому, я не ищу ответа «потому что JLS так говорит», я спрашиваю о техническом обосновании этого дизайнерского решения на языке Java.

Рассмотрим этот пример кода:

    byte byteA = 127;
    byte byteB = -128;
    short shortA = 32767;
    short shortB = -32768;
    int intA = 2147483647;
    int intB = - -2147483648;
    long longA = 9223372036854775807L;
    long longB = -9223372036854775808L;


    int byteAByteB = byteA/byteB;
    int byteAShortB = byteA/shortB;
    int byteAIntB = byteA/intB;
    long byteALongB = byteA/longB;

    int shortAByteB = shortA/byteB;
    int shortAShortB = shortA/shortB;
    int shortAIntB = shortA/intB;
    long shortALongB = shortA/longB;

    int intAByteB = intA/byteB;
    int intAShortB = intA/shortB;
    int intAIntB = intA/intB;
    long intALongB = intA/longB;

    long longAByteB = longA/byteB;
    long longAShortB = longA/shortB;
    long longAIntB = longA/intB;
    long longALongB = longA/longB;

byteA, разделенное на byteB, не может быть ничем иным, как байтом, не так ли?
Так почему же byteAByteB должно быть int? Почему shortALongB не может быть short?
Почему intALongB должно быть long, результат всегда будет соответствовать int, не так ли?

Обновить

Как указал @Eran, (byte)-128/(byte)-1 приводит к 128, что не соответствует byte. Но почему тогда не short?

Обновление 2

Далее, как указал @Eran (снова), (int) -2147483648 / (int) -1 также не соответствует int, но результат тем не менее int, а не long.


person lexicore    schedule 08.12.2016    source источник
comment
Обоснование не техническое, я совершенно уверен.   -  person Marko Topolnik    schedule 08.12.2016
comment
Правило одинаково для любых C-подобных языков. Более того, JVM написана на C и C++, поэтому следует тому же правилу Почему целочисленные типы продвигаются при добавлении в C?   -  person phuclv    schedule 05.08.2018


Ответы (5)


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

Например, спецификация i386 гласит:

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

Это означает, что внутри любое значение байта расширяется до целого числа (или аналогичного). Ведь это разумно, т.к. процессор 32/64 бит и тогда любые арифметические операции выполняются в этих размерах. Если бы можно было производить арифметические действия в байтах, это, как правило, не считалось бы полезным.

Спецификации JVM говорят, что (для дополнения) у вас есть: iadd, ladd, fadd, dadd. Это просто отражает тот факт, что базовые машины обычно ведут себя именно так. Любой другой выбор мог быть возможен, вероятно, ценой снижения производительности.

person Jean-Baptiste Yunès    schedule 08.12.2016

byteA, разделенный на byteB, не может быть ничем иным, как байтом, не так ли?

Это может быть не byte :

byteA = -128;
byteB = -1;
int div = byteA/byteB; // == 128, not a byte
person Eran    schedule 08.12.2016
comment
Дело принято. Почему бы не short тогда? - person lexicore; 08.12.2016
comment
not a byte .. в Java, потому что у него нет понятия без знака .. 128 по-прежнему просто байт (char) в других языках, которые поддерживают беззнаковый - person txtechhelp; 08.12.2016
comment
Обратите внимание, что char — это 16-битный тип unsigned в Java. Хороший. - person Bathsheba; 08.12.2016
comment
@lexicore Вы правы, и мой пример не работает при делении Integer.MIN_VALUE на -1 (вы получите переполнение int, так как операнды не будут повышены до длинных). - person Eran; 08.12.2016
comment
@Eran -2147483648/-1 приводит к -2147483648. Прохладный. Ломает мою логику на сегодня. - person lexicore; 08.12.2016

Я считаю, что оправдание в том, что простые правила вызывают наименьшее удивление. Результатом всегда является более широкий тип из двух (минимум int), он не зависит от операции.

Лучшим подходом может быть всегда расширение +, *, -, если явно (или, возможно, неявно) не сужено. т. е. не делайте переполнения или недополнения, если вы не используете приведение. Например, / всегда может быть операцией double или long, если только она не приведена.

Но C и, следовательно, Java этого не делают.

Короче говоря, у него есть одно простое правило, чтобы справиться с этим, хорошо это или плохо.


См. мои разглагольствования здесь http://vanillajava.blogspot.co.uk/2015/02/inconsistent-operation-widen-rules-in.html

person Peter Lawrey    schedule 08.12.2016
comment
Вы уверены, что это более широкий тип из двух? Насколько я понимаю, это тот случай, когда один из аргументов больше, чем int, но если оба аргумента int или меньше, результатом всегда будет int. Но я не эксперт по продвижению типа Java. - person Bathsheba; 08.12.2016
comment
По крайней мере, хотя он не вводит грузовик UB в Java ;-) - person Bathsheba; 08.12.2016
comment
@Вирсавия УБ...? - person Peter Lawrey; 08.12.2016
comment
Неопределенное поведение. - person Bathsheba; 08.12.2016
comment
В вашем сообщении в блоге: char * char isn't a meaningful operation even though it is allowed --- Я категорически не согласен с этим. char — единственный целочисленный тип без знака в Java, мы все должны научиться любить и лелеять его! - person Marko Topolnik; 08.12.2016
comment
@MarkoTopolnik char полезен, но его использование для хранения несимволов может сбивать с толку. - person Peter Lawrey; 08.12.2016
comment
Только пока не привыкнешь. Мы уже используем byte для хранения целых чисел со знаком, так что разберитесь :) - person Marko Topolnik; 08.12.2016

Я предполагаю, что-то, что было заимствовано из C, возможно, через C++.

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

Хотя в Java это не слишком пагубно. (В C и C++ это может застать вас врасплох: умножение двух больших unsigned short может переполнить int, поведение которого не определено.)

Обратите внимание, что если один из аргументов больше int, то тип выражения является самым большим из типов аргументов.

person Bathsheba    schedule 08.12.2016

Всякий раз, когда вы определяете переменную типа byte, все, что вы вводите напротив нее, должно иметь тип byte.

Это означает, что это может быть только число в диапазоне байтов (от -128 до 127).

Однако при вводе выражения, например. byteA/byteB, что отличается от ввода литерала, например. число 127.

Такова природа Java: integer является типом данных по умолчанию, используемым для целых чисел.

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

Итак, что происходит, когда вы определяете байт и назначаете выражение в качестве значения этого байта, Java нужно будет преобразовать его в целое число:

int byteAByteB = byteA / byteB;

Однако вы можете обойти это, приведя назначенное выражение и, таким образом, заставив Java рассматривать его как байт.

byte byteAByteB = (byte) (byteA / byteB);

Таким образом, вы говорите Java рассматривать это как байт. (Также можно сделать с короткими и т.д.)

person Decrepit warrior    schedule 28.11.2017