ทำไม 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
แม้ว่าโดยทั่วไปคอมไพเลอร์จะ "ยุบ" การคำนวณเป็นค่าเดียวเป็นการเพิ่มประสิทธิภาพ แต่ก็จะไม่ทำเช่นนั้นหากผลลัพธ์นั้นเป็นโอเวอร์โฟลว์เนื่องจากไม่มีการปรับให้เหมาะสมควรเปลี่ยนพฤติกรรมของโปรแกรม
Hot Licks

1
และมันไม่สามารถตีความได้2147483648 : ตัวอักษรนี้ไม่สมเหตุสมผล
Denys Séguret

1
และ Java จะไม่รายงานจำนวนเต็มมากเกินไป - การดำเนินการ "ล้มเหลว" อย่างเงียบ ๆ
Hot Licks

5
@JacobKrall: C # จะรายงานสิ่งนี้ว่ามีข้อบกพร่องไม่ว่าจะเปิดใช้งานการตรวจสอบหรือไม่ การคำนวณทั้งหมดที่ประกอบด้วยนิพจน์คงที่เท่านั้นจะถูกตรวจสอบโดยอัตโนมัติเว้นแต่อยู่ในขอบเขตที่ไม่ได้ตรวจสอบ
Eric Lippert

54
ฉันไม่สนับสนุนให้คุณถามคำถาม "ทำไมไม่" ใน StackOverflow พวกเขาตอบยาก คำถาม "ทำไมไม่" สันนิษฐานว่าโลกควรจะเห็นได้ชัดว่าเป็นวิธีที่มันไม่ได้และว่าจะต้องมีเหตุผลที่ดีสำหรับมันที่จะเป็นอย่างนั้น สมมติฐานนี้แทบไม่ถูกต้อง คำถามที่แม่นยำยิ่งขึ้นจะเป็นเช่น "ส่วนใดของข้อกำหนดที่อธิบายถึงวิธีการคำนวณเลขจำนวนเต็มคงที่" หรือ "การจัดการล้นของจำนวนเต็มใน Java เป็นอย่างไร"
Eric Lippert

คำตอบ:


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ปรากฏขึ้นที่อื่นนอกเหนือจากตัวถูกดำเนินการของผู้ประกอบการ unary ลบ ( §15.15.4 )


12
และสำหรับการคูณ JLS บอกว่าถ้าการคูณจำนวนเต็มล้นแล้วผลที่ได้คือบิตที่มีลำดับต่ำของผลิตภัณฑ์ทางคณิตศาสตร์ตามที่แสดงในรูปแบบสองเสริมที่มีขนาดใหญ่พอสมควร ดังนั้นหากเกิดโอเวอร์โฟลสัญญาณของผลลัพธ์อาจไม่เหมือนกันกับเครื่องหมายของผลิตภัณฑ์ทางคณิตศาสตร์ของค่าตัวถูกดำเนินการทั้งสอง
Chris K

3
คำตอบที่ยอดเยี่ยม บางคนดูเหมือนจะรู้สึกว่าล้นเป็นข้อผิดพลาดหรือความล้มเหลว แต่ไม่ใช่
Wouter Lievens

3
@ iowatiger08 ความหมายของภาษาได้รับการสรุปโดย JLS ซึ่งเป็นอิสระจาก JVM (ดังนั้นจึงไม่สำคัญว่า JVM ใดที่คุณใช้)
arshajii

4
@WouterLievens การโอเวอร์โฟลว์เป็นเงื่อนไขปกติ "ผิดปกติ" หากไม่ใช่เงื่อนไขข้อผิดพลาดทันที มันเป็นผลมาจากคณิตศาสตร์ที่มีความแม่นยำ จำกัด ซึ่งคนส่วนใหญ่ไม่คาดหวังว่าจะเกิดขึ้นเมื่อพวกเขาทำคณิตศาสตร์ ในบางกรณี-1 + 1มันไม่เป็นอันตราย แต่1024^4มันสามารถทำให้คนตาบอดได้รับผลลัพธ์ที่คาดไม่ถึงโดยสิ้นเชิง ฉันคิดว่าอย่างน้อยควรมีคำเตือนหรือข้อความถึงผู้ใช้และอย่าเพิกเฉยต่อความเงียบ
Phil Perry

1
@ iowatiger08: ขนาดของ int ได้รับการแก้ไข มันไม่ได้ขึ้นอยู่กับ JVM Java ไม่ใช่ C.
Martin Schröder

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) เพิ่มข้อยกเว้นหรือตั้งค่าเป็นค่าเวทย์มนตร์เช่นไม่ใช่ตัวเลข


8
สำหรับฉันคำพูดของคุณ " 2147483648ไม่คุ้มค่าเลย (แม้ว่าจะ2147483648Lเป็น)" จริง ๆ แล้วเชื่อมโยงประเด็นที่ @arshajii พยายามทำ
kdbanman

ขอโทษด้วยใช่นั่นคือฉัน ฉันพลาดความคิดล้น / เลขคณิตแบบแยกส่วนในคำตอบของคุณ โปรดทราบว่าคุณสามารถย้อนกลับหากคุณไม่เห็นด้วยกับการแก้ไขของฉัน
Maarten Bodewes

@owlead การแก้ไขของคุณถูกต้องตามจริง เหตุผลของฉันสำหรับการไม่รวมมันก็ว่าไม่คำนึงถึงวิธีการจัดการที่ผมอยากจะเน้นว่ามันไม่ได้เป็นสิ่งเดียวกับการเขียน1024 * 1024 * 1024 * 1024 2147473648มีหลายวิธี (และคุณได้ระบุไว้สองสามข้อ) ว่าภาษาอาจจัดการกับมันได้ มันแยกออกจากกันอย่างสมเหตุสมผลและมีประโยชน์ ดังนั้นฉันจะทิ้งมันไป ข้อมูลจำนวนมากมีความจำเป็นมากขึ้นเมื่อคุณมีคำตอบที่มีอันดับสูงสำหรับคำถามยอดนิยม
Cruncher

16

ฉันไม่รู้ว่าทำไมตัวแปรตัวที่สองจึงไม่เกิดข้อผิดพลาด

ลักษณะการทำงานที่คุณแนะนำ - นั่นคือการผลิตของข้อความวินิจฉัยเมื่อคำนวณมูลค่าการผลิตที่มีขนาดใหญ่กว่าค่าที่มากที่สุดที่สามารถเก็บไว้ในจำนวนเต็ม - เป็นคุณลักษณะ เพื่อให้คุณใช้งานฟีเจอร์ใด ๆ ฟีเจอร์นั้นต้องคำนึงถึงว่าเป็นความคิดที่ดีออกแบบระบุดำเนินการทดสอบจัดทำเอกสารและจัดส่งไปยังผู้ใช้

สำหรับ Java สิ่งหนึ่งอย่างหรือมากกว่านั้นในรายการนั้นไม่ได้เกิดขึ้นดังนั้นคุณจึงไม่มีคุณสมบัติ ฉันไม่รู้ว่าอันไหน; คุณต้องถามนักออกแบบ Java

สำหรับ C # ทุกสิ่งเหล่านี้เกิดขึ้น - เมื่อประมาณสิบสี่ปีก่อน - และดังนั้นโปรแกรมที่เกี่ยวข้องใน C # ได้สร้างข้อผิดพลาดตั้งแต่ C # 1.0


45
สิ่งนี้ไม่ได้เพิ่มประโยชน์อะไรเลย ในขณะที่ฉันไม่รังเกียจที่จะถูกแทงที่ Java แต่ก็ไม่ได้ตอบคำถามของ OP เลย
เซย์เรีย

29
@Seiyria: โปสเตอร์ต้นฉบับถามว่า "ทำไมไม่" คำถาม - "ทำไมโลกถึงไม่เป็นอย่างที่ฉันคิดว่ามันควรจะเป็น" ไม่ใช่คำถามทางเทคนิคที่แม่นยำเกี่ยวกับรหัสจริงและนี่เป็นคำถามที่ไม่ดีสำหรับ StackOverflow ความจริงที่ว่าคำตอบที่ถูกต้องสำหรับคำถามที่คลุมเครือและไม่ใช่ทางด้านเทคนิคนั้นเป็นสิ่งที่คลุมเครือและไม่ใช่ทางด้านเทคนิคที่ควรจะแปลกใจ ฉันขอแนะนำให้ผู้โพสต์ดั้งเดิมถามคำถามที่ดีกว่าและหลีกเลี่ยง "ทำไมไม่" คำถาม
Eric Lippert

18
@Seiyria: คำตอบที่ยอมรับได้ฉันทราบว่ายังไม่ตอบคำถามที่คลุมเครือและไม่ใช่ทางเทคนิค; คำถามคือ "ทำไมนี่ไม่ใช่ข้อผิดพลาด?" และคำตอบที่ยอมรับคือ "เพราะถูกกฎหมาย" นี้เป็นเพียงงบการเงินเฉพาะกิจคำถาม ; ตอบ "ทำไมท้องฟ้าจึงไม่เขียว" ด้วย "เพราะมันเป็นสีน้ำเงิน" ไม่ตอบคำถาม แต่เนื่องจากคำถามนี้เป็นคำถามที่ไม่ดีเลยฉันไม่โทษผู้ตอบคำถามเลย คำตอบคือคำตอบที่สมเหตุสมผลอย่างสมบูรณ์แบบสำหรับคำถามที่ไม่ดี
Eric Lippert

13
นายเอริคนี่เป็นคำถามที่ฉันโพสต์: "ทำไม int i = 1024 * 1024 * 1024 * 1024; โดยไม่มีรายงานข้อผิดพลาดใน eclipse" และคำตอบของ arshajii คือสิ่งที่ฉัน (อาจจะมากกว่า) บางครั้งฉันไม่สามารถแสดงคำถามใด ๆ ในวิธีที่แม่นยำมาก ฉันคิดว่านั่นเป็นสาเหตุที่บางคนแก้ไขคำถามที่โพสต์ได้ถูกต้องมากขึ้นใน Stackoverflow ฉันคิดว่าถ้าฉันต้องการคำตอบ "เพราะถูกกฎหมาย" ฉันจะไม่โพสต์คำถามนี้ ฉันจะพยายามอย่างดีที่สุดเพื่อโพสต์ "คำถามปกติ" แต่โปรดเข้าใจคนที่เหมือนกับฉันซึ่งเป็นนักเรียนและไม่ใช่มืออาชีพ ขอบคุณ
WUJ

5
@WUJ คำตอบนี้ IMHO ให้ข้อมูลเชิงลึกและมุมมองเพิ่มเติม หลังจากอ่านคำตอบทั้งหมดฉันพบคำตอบนี้เพื่อให้มีความถูกต้องมากเท่ากับคำตอบอื่น ๆ ที่ให้ไว้ นอกจากนี้ยังเพิ่มการรับรู้ว่านักพัฒนาไม่ได้เป็นเพียงผู้ดำเนินการของผลิตภัณฑ์ซอฟต์แวร์บางตัว
SoftwareCarpenter

12

นอกจากคำตอบของ arshajii ฉันต้องการแสดงอีกอย่าง:

มันไม่ได้เป็นที่ได้รับมอบหมายที่ทำให้เกิดข้อผิดพลาด แต่เพียงการใช้งานของตัวอักษร เมื่อคุณลอง

long i = 2147483648;

คุณจะสังเกตเห็นว่ามันยังทำให้เกิดข้อผิดพลาดในการคอมไพล์เนื่องจากทางด้านขวายังคงเป็นint-literal และอยู่นอกช่วง

ดังนั้นการดำเนินการด้วย - intค่า (และที่รวมถึงการมอบหมาย) อาจล้นโดยไม่มีข้อผิดพลาดในการคอมไพล์ (และไม่มีข้อผิดพลาดรันไทม์เช่นกัน) แต่คอมไพเลอร์ก็ไม่สามารถจัดการตัวอักษรที่มีขนาดใหญ่เกินไป


1
ขวา. การกำหนด int ถึง long จะรวมการร่ายโดยนัย แต่ค่าที่ไม่สามารถอยู่เป็น int ในสถานที่แรกที่ได้รับการหล่อ :)
Cruncher

4

ตอบ:ไม่ใช่ข้อผิดพลาด

ความเป็นมา:การคูณ1024 * 1024 * 1024 * 1024จะนำไปสู่การล้น การโอเวอร์โฟลว์มักเป็นจุดบกพร่อง ภาษาการเขียนโปรแกรมที่แตกต่างกันจะมีพฤติกรรมที่แตกต่างกัน ตัวอย่างเช่น C และ C ++ เรียกมันว่า "พฤติกรรมที่ไม่ได้กำหนด" สำหรับจำนวนเต็มที่ลงนามและพฤติกรรมนั้นถูกกำหนดไว้เป็นจำนวนเต็มที่ไม่ได้ลงชื่อ (รับผลทางคณิตศาสตร์เพิ่มUINT_MAX + 1ตราบใดที่ผลเป็นลบลบUINT_MAX + 1ตราบใดที่ผลมีค่ามากกว่าUINT_MAX)

ในกรณีของ Java หากผลลัพธ์ของการดำเนินการที่มีintค่าไม่อยู่ในช่วงที่อนุญาต Java จะเพิ่มหรือลบแนวคิด 2 ^ 32 จนกว่าผลลัพธ์จะอยู่ในช่วงที่อนุญาต ดังนั้นคำสั่งนั้นถูกกฎหมายอย่างสมบูรณ์และไม่ผิดพลาด มันไม่ได้สร้างผลลัพธ์ที่คุณคาดหวังไว้

คุณสามารถยืนยันได้ว่าพฤติกรรมนี้มีประโยชน์หรือไม่และผู้รวบรวมควรให้คำเตือนหรือไม่ ฉันจะบอกเป็นการส่วนตัวว่าคำเตือนจะมีประโยชน์มาก แต่ข้อผิดพลาดจะไม่ถูกต้องเนื่องจากเป็นจาวาที่ถูกกฎหมาย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.