Tại sao int i = 1024 * 1024 * 1024 * 1024 biên dịch không có lỗi?


152

Giới hạn intlà từ -2147483648 đến 2147483647.

Nếu tôi nhập

int i = 2147483648;

sau đó Eclipse sẽ nhắc một gạch dưới màu đỏ trong "2147483648".

Nhưng nếu tôi làm điều này:

int i = 1024 * 1024 * 1024 * 1024;

nó sẽ biên dịch tốt

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

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

    }
}

Có thể đó là một câu hỏi cơ bản trong Java, nhưng tôi không biết tại sao biến thể thứ hai không có lỗi.


10
Ngay cả khi trình biên dịch thường "thu gọn" tính toán thành một giá trị duy nhất dưới dạng tối ưu hóa, nó sẽ không làm như vậy nếu kết quả sẽ là tràn, vì không tối ưu hóa sẽ thay đổi hành vi của chương trình.
Licks nóng

1
Và nó không thể giải thích 2147483648: nghĩa đen này không có ý nghĩa.
Denys Séguret

1
Và Java không báo cáo tràn số nguyên - hoạt động "thất bại" âm thầm.
Licks nóng

5
@JacobKrall: C # sẽ báo cáo lỗi này bất kể có bật tính năng kiểm tra hay không; tất cả các tính toán chỉ bao gồm các biểu thức hằng được tự động kiểm tra trừ khi bên trong một vùng không được kiểm tra.
Eric Lippert

54
Tôi không khuyến khích bạn đặt câu hỏi "tại sao không" trên StackOverflow; họ rất khó trả lời. Một câu hỏi "tại sao không" giả định rằng thế giới rõ ràng phải là một cách mà nó không phải, và cần phải có một lý do chính đáng cho nó là như vậy. Giả định này gần như không bao giờ có giá trị. Một câu hỏi chính xác hơn sẽ là một cái gì đó như "phần nào của đặc tả mô tả cách tính số học nguyên không đổi?" hoặc "làm thế nào các tràn số nguyên được xử lý trong Java?"
Eric Lippert

Câu trả lời:


233

Không có gì sai với tuyên bố đó; bạn chỉ cần nhân 4 số và gán nó cho một số nguyên, sẽ xảy ra tình trạng tràn. Điều này khác với việc gán một chữ duy nhất , sẽ được kiểm tra giới hạn tại thời gian biên dịch.

Chính nghĩa đen là nguyên nhân gây ra lỗi, không phải là bài tập :

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

Ngược lại, một longnghĩa đen sẽ biên dịch tốt:

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

Lưu ý rằng, trên thực tế, kết quả vẫn tính tại thời gian biên dịch vì 1024 * 1024 * 1024 * 1024là một biểu thức hằng số :

int i = 1024 * 1024 * 1024 * 1024;

trở thành:

   0: iconst_0      
   1: istore_1      

Lưu ý rằng kết quả ( 0) chỉ được tải và lưu trữ, và không có phép nhân nào diễn ra.


Từ JLS §3.10.1 (cảm ơn @ChrisK đã đưa nó lên trong các bình luận):

Đó là lỗi thời gian biên dịch nếu một chữ số thập phân của loại intlớn hơn 2147483648(2 31 ) hoặc nếu chữ thập phân 2147483648xuất hiện ở bất kỳ nơi nào khác ngoài toán hạng của toán tử trừ đơn vị ( §15.15.4 ).


12
Và đối với phép nhân, JLS nói, nếu phép nhân số nguyên tràn, thì kết quả là các bit có thứ tự thấp của sản phẩm toán học như được biểu thị ở một số định dạng bổ sung hai đủ lớn. Kết quả là, nếu tràn xảy ra, thì dấu của kết quả có thể không giống với dấu của sản phẩm toán học của hai giá trị toán hạng.
Chris K

3
Câu trả lời tuyệt vời. Một số người dường như có ấn tượng rằng tràn là một loại lỗi hoặc thất bại, nhưng nó không phải là.
Wouter Lievens

3
@ iowatiger08 Các ngữ nghĩa ngôn ngữ được JLS phác thảo, độc lập với JVM (vì vậy không quan trọng bạn sử dụng JVM nào).
arshajii

4
@WouterLievens, tràn bình thường một "bất thường" điều kiện, nếu không phải là một điều kiện lỗi hoàn toàn. Đó là kết quả của toán học chính xác hữu hạn, điều mà hầu hết mọi người không trực giác mong đợi sẽ xảy ra khi họ làm toán. Trong một số trường hợp, như -1 + 1, nó vô hại; nhưng 1024^4nó có thể làm mù mắt mọi người với kết quả hoàn toàn bất ngờ, khác xa với những gì họ mong đợi được nhìn thấy. Tôi nghĩ nên có ít nhất một cảnh báo hoặc ghi chú cho người dùng, và không âm thầm bỏ qua nó.
Phil Perry

1
@ iowatiger08: Kích thước của int là cố định; nó không phụ thuộc vào JVM. Java không phải là C.
Martin Schröder

43

1024 * 1024 * 1024 * 10242147483648không có cùng giá trị trong Java.

Trên thực tế, 2147483648 KHÔNG NGAY CẢ GIÁ TRỊ (mặc dù 2147483648Llà) trong Java. Trình biên dịch theo nghĩa đen không biết nó là gì hoặc làm thế nào để sử dụng nó. Thế là nó rên rỉ.

1024là một int hợp lệ trong Java và một giá trị intnhân với một giá trị khác int, luôn luôn hợp lệ int. Ngay cả khi đó không phải là cùng một giá trị mà bạn mong đợi bằng trực giác vì tính toán sẽ tràn.

Thí dụ

Xem xét các mẫu mã sau:

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

Bạn có mong đợi điều này sẽ tạo ra một lỗi biên dịch không? Bây giờ nó trở nên trơn hơn một chút.
Điều gì xảy ra nếu chúng ta đặt một vòng lặp với 3 lần lặp và nhân trong vòng lặp?

Trình biên dịch được phép tối ưu hóa, nhưng nó không thể thay đổi hành vi của chương trình trong khi nó đang làm như vậy.


Một số thông tin về cách xử lý vụ việc này:

Trong Java và nhiều ngôn ngữ khác, số nguyên sẽ bao gồm một số bit cố định. Các tính toán không phù hợp với số bit đã cho sẽ tràn ra ; phép tính về cơ bản được thực hiện mô đun 2 ^ 32 trong Java, sau đó giá trị được chuyển đổi trở lại thành một số nguyên đã ký .

Các ngôn ngữ hoặc API khác sử dụng số bit động ( BigIntegertrong Java), đưa ra một ngoại lệ hoặc đặt giá trị thành giá trị ma thuật, chẳng hạn như số không.


8
Đối với tôi, tuyên bố của bạn, " 2147483648KHÔNG NGAY CẢ GIÁ TRỊ (mặc dù 2147483648Llà vậy)", thực sự đã củng cố quan điểm mà @arshajii đang cố gắng thực hiện.
kdbanman

À, xin lỗi, vâng, đó là tôi. Tôi đã bỏ lỡ khái niệm số học tràn / mô-đun trong câu trả lời của bạn. Lưu ý rằng bạn có thể quay lại nếu bạn không đồng ý với chỉnh sửa của tôi.
Maarten Bodewes

@owlstead Chỉnh sửa của bạn là thực tế chính xác. Lý do của tôi không bao gồm nó là: bất kể cách 1024 * 1024 * 1024 * 1024xử lý như thế nào tôi thực sự muốn nhấn mạnh rằng nó không giống như viết 2147473648. Có nhiều cách (và bạn đã liệt kê một vài) rằng một ngôn ngữ có khả năng đối phó với nó. Nó tách biệt hợp lý và hữu ích. Vì vậy, tôi sẽ rời khỏi nó. Rất nhiều thông tin ngày càng trở nên cần thiết khi bạn có câu trả lời được xếp hạng cao cho một câu hỏi phổ biến.
Cruncher

16

Tôi không biết tại sao biến thể thứ hai không có lỗi.

Hành vi mà bạn đề xuất - nghĩa là tạo ra thông báo chẩn đoán khi tính toán tạo ra một giá trị lớn hơn giá trị lớn nhất có thể được lưu trữ trong một số nguyên - là một tính năng . Để bạn sử dụng bất kỳ tính năng nào, tính năng này phải được nghĩ đến, được coi là một ý tưởng tốt, được thiết kế, chỉ định, thực hiện, thử nghiệm, ghi lại và chuyển đến người dùng.

Đối với Java, một hoặc nhiều điều trong danh sách đó đã không xảy ra và do đó bạn không có tính năng này. Tôi không biết cái nào; bạn phải hỏi một nhà thiết kế Java.

Đối với C #, tất cả những điều đó đã xảy ra - khoảng mười bốn năm trước - và do đó, chương trình tương ứng trong C # đã tạo ra lỗi kể từ C # 1.0.


45
Điều này không thêm bất cứ điều gì hữu ích. Mặc dù tôi không bận tâm đến việc đâm vào Java, nhưng nó không trả lời câu hỏi OP nào cả.
Seiyria

29
@Seiyria: Người đăng ban đầu đang hỏi "tại sao không?" câu hỏi - "tại sao thế giới không phải là cách tôi nghĩ nó nên được?" không phải là một câu hỏi kỹ thuật chính xác về mã thực tế và do đó đây là một câu hỏi tồi cho StackOverflow. Thực tế là câu trả lời chính xác cho một câu hỏi mơ hồ và không kỹ thuật là mơ hồ và không kỹ thuật nên không có gì đáng ngạc nhiên. Tôi khuyến khích người đăng ban đầu hỏi một câu hỏi hay hơn và tránh "tại sao không?" câu hỏi
Eric Lippert

18
@Seiyria: Câu trả lời được chấp nhận tôi lưu ý cũng không trả lời câu hỏi mơ hồ và phi kỹ thuật này; câu hỏi là "tại sao điều này không phải là một lỗi?" và câu trả lời được chấp nhận là "bởi vì nó hợp pháp". Điều này chỉ đơn giản là nghỉ ngơi câu hỏi ; trả lời "tại sao bầu trời không xanh?" với "bởi vì nó màu xanh" không trả lời câu hỏi. Nhưng vì câu hỏi là một câu hỏi tồi, tôi hoàn toàn không đổ lỗi cho người trả lời; câu trả lời là một câu trả lời hoàn toàn hợp lý cho một câu hỏi kém.
Eric Lippert

13
Ông Eric, đây là câu hỏi mà tôi đã đăng: "Tại sao int i = 1024 * 1024 * 1024 * 1024; không có báo cáo lỗi trong nhật thực?". và câu trả lời của arshajii chính xác là những gì tôi (có thể nhiều hơn). Đôi khi tôi không thể diễn đạt bất kỳ câu hỏi nào một cách rất chính xác. Tôi nghĩ đó là lý do tại sao có một số người sửa đổi một số câu hỏi được đăng chính xác hơn trong Stackoverflow. Tôi nghĩ rằng nếu tôi muốn nhận được câu trả lời "vì nó hợp pháp", tôi sẽ không đăng câu hỏi này. Tôi sẽ cố gắng hết sức để đăng một số "câu hỏi thông thường", nhưng xin vui lòng hiểu ai đó giống như tôi là một sinh viên và không quá chuyên nghiệp. Cảm ơn.
WUJ

5
@WUJ Câu trả lời này IMHO cung cấp cái nhìn sâu sắc và quan điểm bổ sung. Sau khi đọc tất cả các câu trả lời, tôi thấy câu trả lời này cung cấp nhiều giá trị như các câu trả lời khác được cung cấp. Ngoài ra, nó nâng cao nhận thức rằng các nhà phát triển không phải là người thực hiện duy nhất một số sản phẩm phần mềm.
SoftwareCarpenter

12

Ngoài câu trả lời của arshajii, tôi muốn chỉ ra một điều nữa:

Nó không phải là sự phân công gây ra lỗi mà chỉ đơn giản là việc sử dụng nghĩa đen . Khi bạn cố gắng

long i = 2147483648;

bạn sẽ nhận thấy nó cũng gây ra lỗi biên dịch vì phía bên tay phải vẫn là một inttiêu chuẩn và nằm ngoài phạm vi.

Vì vậy, các hoạt động với giá inttrị (và bao gồm cả các bài tập) có thể tràn mà không có lỗi biên dịch (và cũng không có lỗi thời gian chạy), nhưng trình biên dịch chỉ không thể xử lý các chữ quá lớn đó.


1
Đúng. Gán một int cho một bao gồm một dàn diễn viên ngầm. Nhưng giá trị không bao giờ có thể tồn tại như int ở vị trí đầu tiên được chọn :)
Cruncher

4

A: Bởi vì nó không phải là một lỗi.

Bối cảnh: Phép nhân 1024 * 1024 * 1024 * 1024sẽ dẫn đến tràn. Một tràn rất thường là một lỗi. Các ngôn ngữ lập trình khác nhau tạo ra hành vi khác nhau khi tràn xảy ra. Ví dụ, C và C ++ gọi đó là "hành vi không xác định" cho các số nguyên đã ký và hành vi được xác định là số nguyên không dấu (lấy kết quả toán học, thêm UINT_MAX + 1miễn là kết quả là âm, trừ UINT_MAX + 1khi kết quả lớn hơn UINT_MAX).

Trong trường hợp của Java, nếu kết quả của một hoạt động với intcác giá trị không nằm trong phạm vi được phép, về mặt khái niệm, Java sẽ cộng hoặc trừ 2 ^ 32 cho đến khi kết quả nằm trong phạm vi cho phép. Vì vậy, tuyên bố là hoàn toàn hợp pháp và không có lỗi. Nó chỉ không tạo ra kết quả mà bạn có thể hy vọng.

Bạn chắc chắn có thể tranh luận liệu hành vi này có hữu ích hay không và liệu trình biên dịch có đưa ra cảnh báo cho bạn hay không. Cá nhân tôi nói rằng một cảnh báo sẽ rất hữu ích, nhưng một lỗi sẽ không chính xác vì đó là Java hợp pháp.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.