Почему int i = 1024 * 1024 * 1024 * 1024 компилируется без ошибок?

152

Предел intсоставляет от -2147483648 до 2147483647.

Если я введу

int i = 2147483648;

затем Eclipse предложит красное подчеркивание под «2147483648».

Но если я сделаю это:

int i = 1024 * 1024 * 1024 * 1024;

это будет компилироваться нормально.

public class Test {
    public static void main(String[] args) {        

        int i = 2147483648;                   // error
        int j = 1024 * 1024 * 1024 * 1024;    // no error

    }
}

Может быть, это основной вопрос в Java, но я понятия не имею, почему второй вариант не дает ошибок.

WUJ
источник
10
Даже если компилятор обычно «свернет» вычисление в одно значение в качестве оптимизации, он не сделает этого, если результатом будет переполнение, так как никакая оптимизация не должна изменить поведение программы.
Горячая Ликс
1
И это не может интерпретировать 2147483648: этот буквальный смысл не имеет смысла.
Денис Сегюре
1
И Java не сообщает о целочисленных переполнениях - операция молча завершается неудачей.
Hot Licks
5
@JacobKrall: C # сообщит об этом как о дефекте независимо от того, включена ли проверенная проверка; все вычисления, которые состоят только из константных выражений, автоматически проверяются, если они не находятся в непроверенной области.
Эрик Липперт
54
Я не рекомендую вам задавать вопросы «почему бы и нет» в StackOverflow; на них сложно ответить. Вопрос «почему нет» предполагает, что мир, очевидно, должен быть таким, каким он не является, и что для этого должен быть веский повод. Это предположение почти никогда не верно. Более точным вопросом будет что-то вроде «какой раздел спецификации описывает, как вычисляется постоянная целочисленная арифметика?» или "как целочисленные переполнения обрабатываются в Java?"
Эрик Липперт

Ответы:

233

В этом утверждении нет ничего плохого; Вы просто умножаете 4 числа и присваиваете его целому числу, просто случается переполнение. Это отличается от присвоения одного литерала , который будет проверяться границами во время компиляции.

Это литерал вне пределов, который вызывает ошибку, а не присваивание :

System.out.println(2147483648);        // error
System.out.println(2147483647 + 1);    // no error

В противоположность этому, longлитерал будет компилироваться нормально:

System.out.println(2147483648L);       // no error

Следует отметить , что, по сути, результат будет по- прежнему вычисляется во время компиляции , потому что 1024 * 1024 * 1024 * 1024это выражение константы :

int i = 1024 * 1024 * 1024 * 1024;

будет выглядеть так:

   0: iconst_0      
   1: istore_1      

Обратите внимание, что result ( 0) просто загружается и сохраняется, и умножение не происходит.


Из JLS §3.10.1 (спасибо @ChrisK за то, что он поднял это в комментариях):

Это ошибка времени компиляции, если десятичный литерал типа intбольше, чем 2147483648(2 31 ), или если десятичный литерал 2147483648появляется где-то, кроме как операнд унарного минусового оператора ( §15.15.4 ).

arshajii
источник
12
А для умножения JLS говорит: если целочисленное умножение переполняется, то результатом являются биты младшего разряда математического произведения, представленные в некотором достаточно большом формате двоичного дополнения. В результате, если происходит переполнение, знак результата может не совпадать со знаком математического произведения двух значений операнда.
Крис К
3
Отличный ответ. У некоторых людей создается впечатление, что переполнение - это какая-то ошибка или сбой, но это не так.
Ваут Ливенс
3
@ iowatiger08 Семантика языка описывается JLS, который не зависит от JVM (поэтому не должно иметь значения, какую JVM вы используете).
Аршаджи
4
@WouterLievens, переполнение это обычно является «необычным» состояние, если не откровенное условие ошибки. Это результат математики конечной точности, которую большинство людей не ожидают интуитивно, когда они занимаются математикой. В некоторых случаях, например -1 + 1, это безвредно; но 1024^4это может привести к слепоте людей с совершенно неожиданными результатами, далекими от того, что они ожидают увидеть. Я думаю, что должно быть по крайней мере предупреждение или примечание для пользователя, а не молча игнорировать его.
Фил Перри
1
@ iowatiger08: размер int фиксирован; это не зависит от JVM. Ява не C.
Мартин Шредер
43

1024 * 1024 * 1024 * 1024и 2147483648не имеют одинаковое значение в Java.

На самом деле, 2147483648 даже не имеет значения (хотя и 2147483648Lесть) в Java. Компилятор буквально не знает, что это такое и как его использовать. Так что скулит.

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

пример

Рассмотрим следующий пример кода:

public static void main(String[] args) {
    int a = 1024;
    int b = a * a * a * a;
}

Вы ожидаете, что это приведет к ошибке компиляции? Это становится немного более скользким теперь.
Что если мы поместим цикл с 3 итерациями и умножим на цикл?

Компилятору разрешено оптимизировать, но он не может изменить поведение программы, пока он это делает.


Некоторая информация о том, как на самом деле обрабатывается этот случай:

В Java и многих других языках целые числа будут состоять из фиксированного числа битов. Вычисления, которые не соответствуют данному количеству битов, будут переполнены ; вычисление в основном выполняется по модулю 2 ^ 32 в Java, после чего значение преобразуется обратно в подписанном целое.

Другие языки или API используют динамическое число битов ( BigIntegerв Java), вызывают исключение или устанавливают магическое значение, например not-a-number.

Cruncher
источник
8
Для меня ваше утверждение « 2147483648НЕ ДАЖЕ ЗНАЧЕНИЕ (хотя и 2147483648Lесть)» действительно укрепило мысль, которую @arshajii пытался сделать.
kdbanman
Ах, прости, да, это был я. Мне не хватало понятия переполнения / модульной арифметики в вашем ответе. Обратите внимание, что вы можете откатиться, если вы не согласны с моей правкой.
Мартен Бодьюз
@owlstead Ваше редактирование действительно правильно. Моя причина не включать это было то, что: независимо от того, как 1024 * 1024 * 1024 * 1024обрабатывается, я действительно хотел подчеркнуть, что это не то же самое, что писать 2147473648. Есть много способов (и вы перечислили несколько), что язык потенциально может с этим справиться. Это разумно разделено и полезно. Так что я оставлю это. Много информации становится все более необходимым, когда у вас есть высокий рейтинг ответа на популярный вопрос.
Cruncher
16

Я понятия не имею, почему второй вариант не дает ошибки.

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

Для Java одна или несколько вещей из этого списка не произошли, и поэтому у вас нет возможности. Я не знаю, какой из них; вам придется спросить Java-дизайнера.

Для C # все это произошло - около четырнадцати лет назад, и поэтому соответствующая программа на C # вызвала ошибку, начиная с C # 1.0.

Эрик Липперт
источник
45
Это не добавляет ничего полезного. Хотя я не возражаю против использования Java, он не ответил на вопрос ОП.
Сейрия
29
@Seiyria: оригинальный постер спрашивает "почему бы и нет?" вопрос - "почему мир не такой, каким я думаю, должен быть?" это не точный технический вопрос о реальном коде , и поэтому это плохой вопрос для StackOverflow. Тот факт, что правильный ответ на неопределенный и нетехнический вопрос является расплывчатым и нетехническим, не должен удивлять. Я рекомендую оригинальному постеру задать лучший вопрос и избегать "почему бы и нет?" вопросы.
Эрик Липперт
18
@Seiyria: принятый ответ, который я отмечаю, также не отвечает на этот расплывчатый и нетехнический вопрос; вопрос "почему это не ошибка?" и принятый ответ "потому что это законно". Это просто повторяет вопрос ; отвечая "почему небо не зеленое?" с "потому что это синий" не отвечает на вопрос. Но так как вопрос плохой, я вовсе не виню ответчика; ответ является вполне разумным ответом на плохой вопрос.
Эрик Липперт
13
Мистер Эрик, вот вопрос, который я написал: «Почему int i = 1024 * 1024 * 1024 * 1024; без сообщения об ошибке в затмении?». и ответ на аршаджи именно то, что я, что (может быть, больше). Иногда я не могу выразить какие-либо вопросы очень точно. Я думаю, именно поэтому некоторые люди изменяют некоторые опубликованные вопросы более точно в Stackoverflow. Я думаю, что если я хочу получить ответ «потому что это законно», я не буду публиковать этот вопрос. Я сделаю все возможное, чтобы опубликовать некоторые "обычные вопросы", но, пожалуйста, поймите кого-то, такого же как я, который является студентом и не настолько профессионален. Спасибо.
WUJ
5
@ WUJ Этот ответ ИМХО дает дополнительное понимание и перспективу. Прочитав все ответы, я обнаружил, что этот ответ дает столько же обоснованности, сколько и остальные ответы. Также это повышает осознание того, что разработчики не являются единственными разработчиками некоторых программных продуктов.
SoftwareCarpenter
12

В дополнение к ответу Аршаджи я хочу показать еще одну вещь:

Не присваивание вызывает ошибку, а просто использование литерала . Когда вы пытаетесь

long i = 2147483648;

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

Таким образом, операции с int-values ​​(включая присваивания) могут переполняться без ошибки компиляции (и без ошибки времени выполнения), но компилятор просто не может обработать эти слишком большие литералы.

piet.t
источник
1
Правильно. Назначение int для long включает в себя неявное приведение. Но значение никогда не может существовать как int в первую очередь для кастования :)
Cruncher
4

A: Потому что это не ошибка.

Фон: умножение 1024 * 1024 * 1024 * 1024приведет к переполнению. Переполнение очень часто является ошибкой. В случае переполнения разные языки программирования ведут себя по-разному. Например, C и C ++ называют это «неопределенным поведением» для целых чисел со знаком, а поведение определяется целыми числами без знака (взять математический результат, сложить, UINT_MAX + 1если результат отрицательный, вычесть, UINT_MAX + 1если результат больше, чем UINT_MAX).

В случае Java, если результат операции со intзначениями не находится в допустимом диапазоне, концептуально Java добавляет или вычитает 2 ^ 32, пока результат не окажется в допустимом диапазоне. Так что утверждение абсолютно законно и не по ошибке. Это просто не дает результата, на который вы, возможно, надеялись.

Вы можете с уверенностью утверждать, полезно ли такое поведение, и должен ли компилятор предупреждать вас. Я бы лично сказал, что предупреждение было бы очень полезно, но ошибка была бы неправильной, так как это допустимая Java.

gnasher729
источник