Чому 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, але я не маю поняття, чому другий варіант не створює помилок.


10
Навіть якщо компілятор зазвичай «згортає» обчислення на одне значення як оптимізацію, це не буде робити, якщо результат буде переповненням, оскільки жодна оптимізація не повинна змінювати поведінку програми.
Гарячі лизання

1
І це не може інтерпретувати 2147483648: цей буквальний сенс не має сенсу.
Denys Séguret

1
І Java не повідомляє про цілочисельні переповнення - операція "провалюється" мовчки.
Гарячі лизання

5
@JacobKrall: C # повідомить про це як про дефект, незалежно від того, включена прапорець; всі обчислення, що складаються лише з постійних виразів, автоматично перевіряються, якщо не знаходиться в неперевіреній області.
Ерік Ліпперт

54
Я заважаю вам задавати питання "чому ні" на StackOverflow; їм важко відповісти. Питання "чому б ні" передбачає, що світ очевидно повинен бути таким, яким він не є, і що для цього має бути вагома причина. Це припущення майже ніколи не відповідає дійсності. Більш точним питанням було б щось на кшталт "який розділ специфікації описує, як обчислюється постійна ціла арифметика?" або "як цілі переповнення обробляються на Java?"
Ерік Ліпперт

Відповіді:


233

У цьому твердженні нічого поганого; ви просто перемножуєте 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 ).


12
А для множення JLS говорить: Якщо ціле множення переповнюється, то результатом є біти низького порядку математичного добутку, представлені у деяких досить великих форматах двох доповнення. Як результат, якщо відбувається переповнення, то знак результату може бути не таким, як знак математичного добутку двох значень операнду.
Кріс К

3
Відмінна відповідь. У деяких людей складається враження, що переповнення - це якась помилка чи збій, але це не так.
Wouter Lievens

3
@ iowatiger08 Мовна семантика окреслена JLS, яка не залежить від JVM (тому не має значення, яким JVM ви користуєтесь).
Аршаджій

4
@WouterLievens, переповнення це зазвичай є «незвичайним» стан, якщо не відверте умова помилки. Це результат математики з обмеженою точністю, яку більшість людей не інтуїтивно очікує, що це станеться, коли вони займаються математикою. У деяких випадках, наприклад -1 + 1, це нешкідливо; але 1024^4це може засліпити людей з абсолютно несподіваними результатами, далеко не тими, які вони очікували б побачити. Я думаю, що має бути принаймні попередження або зауваження користувачеві, а не мовчки його ігнорувати.
Філ Перрі

1
@ iowatiger08: розмір int фіксований; це не залежить від СВМ. Java - це не C.
Мартін Шредер

43

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

Насправді, В 2147483648 ЯВІ НЕ БУДЕ ЦІННА (хоча 2147483648Lє). Компілятор буквально не знає, що це таке, або як ним користуватися. Так воно скуголить.

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), підвищують виняток або встановлюють значення на магічне значення, наприклад, не-а-число.


8
Для мене ваше твердження, " НІКОЛИ ЦІННЕ 2147483648(хоча і 2147483648Lє)", справді цементувало точку, яку намагався зробити @arshajii.
kdbanman

Ах, вибачте, так, це я. У вашій відповіді мені не вистачало поняття переповнення / модульної арифметики. Зауважте, що ви можете відмовитись, якщо ви не згодні з моєю редагуванням.
Maarten Bodewes

@owlstead Ваша редакція фактично правильна. Моя причина не включати в це полягала в тому, що: незалежно від того, як 1024 * 1024 * 1024 * 1024поводиться, я дуже хотів підкреслити, що це не те саме, що писати 2147473648. Існує багато способів (і ви перерахували декілька), що мова може потенційно з цим боротися. Це розумно відокремлене та корисне. Тож я залишу його. Багато інформації стає все більш необхідною, коли у вас є високопоставлена ​​відповідь на популярне питання.
Cruncher

16

Я поняття не маю, чому другий варіант не дає помилок.

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

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

Що стосується C #, все це сталося - близько чотирнадцяти років тому - і відповідна програма в C # виникла помилку після C # 1.0.


45
Це не додає нічого корисного. Хоча я не проти брати на себе удари в Java, він взагалі не відповів на питання ОП.
Seiyria

29
@Seiyria: Оригінальний плакат запитує "чому ні?" питання - "чому світ не такий, як я думаю, він повинен бути?" не є точним технічним питанням щодо фактичного коду , і тому це поганий питання для StackOverflow. Той факт, що правильна відповідь на розпливчасте і нетехнічне запитання є розпливчастим і нетехнічним, не дивно. Я заохочую оригінальний плакат поставити краще питання і уникати "чому б ні?" питання.
Ерік Ліпперт

18
@Seiyria: прийнята відповідь, яку я зазначаю, також не відповідає на це розпливчасте і нетехнічне запитання; питання "чому це не помилка?" і прийнята відповідь "тому що це законно". Це просто перестановка питання ; відповідаючи "чому небо не зелене?" з "тому що синій" не відповідає на питання. Але оскільки питання є поганим питанням, я взагалі не звинувачую відповідача; відповідь - цілком розумна відповідь на невдале запитання.
Ерік Ліпперт

13
Пане Еріку, це питання, яке я опублікував: "Чому int i = 1024 * 1024 * 1024 * 1024; без повідомлення про помилки при затемненні?". а відповідь аршаджі - це саме те, що я що (можливо більше). Іноді я не можу висловити жодних питань дуже точно. Я думаю, що тому деякі люди модифікують деякі розміщені запитання точніше в Stackoverflow. Я думаю, що якщо я хочу отримати відповідь "тому що це законно", я не ставлю це питання. Я постараюсь зробити все можливе, щоб опублікувати якісь "регулярні запитання", але, будь ласка, зрозумійте когось, як я, студент і не такий професійний. Дякую.
WUJ

5
@WUJ Ця відповідь IMHO надає додаткове розуміння та перспективу. Прочитавши всі відповіді, я знайшов цю відповідь такою ж достовірністю, як і інші надані відповіді. Також це підвищує усвідомлення того, що розробники - не єдині виконавці деяких програмних продуктів.
SoftwareCarpenter

12

Окрім відповіді аршаджі, я хочу ще одне:

Помилка викликає не призначення , а просто використання літералу . Коли ви намагаєтесь

long i = 2147483648;

ви помітите, що це також спричиняє помилку компіляції, оскільки права частина все ще є int-літеральною та поза діапазоном.

Таким чином, операції з int-values ​​(і це включає в себе призначення) можуть переповнюватися без помилки компіляції (і без помилки виконання), але компілятор просто не може обробити ці занадто великі літерали.


1
Правильно. Присвоєння int довгому включає неявний склад. Але цінність ніколи не може існувати як int в першу чергу, щоб отримати кастинг :)
Cruncher

4

Відповідь: Тому що це не помилка.

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

У випадку з Java, якщо результат операції зі intзначеннями не знаходиться в дозволеному діапазоні, концептуально Java додає або віднімає 2 ^ 32, поки результат не буде в допустимому діапазоні. Тож заява є повністю законною і не помиляється. Це просто не дає результату, на який ви могли сподіватися.

Ви напевно можете заперечити, чи корисна ця поведінка та чи повинен компілятор надати вам попередження. Я б особисто сказав, що попередження було б дуже корисним, але помилка була б невірною, оскільки це легальна Java.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.