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

Предел 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, но я понятия не имею, почему второй вариант не вызывает ошибок.

Ответ 1

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

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

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      

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


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

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

Ответ 2

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

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

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

Пример

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

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

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

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


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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

Это не назначение, которое вызывает ошибку, а просто использование литерала. При попытке

long i = 2147483648;

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

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

Ответ 5

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

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

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

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