ทำไมฉัน = i + ฉันให้ 0


96

ฉันมีโปรแกรมง่ายๆ:

public class Mathz {
    static int i = 1;
    public static void main(String[] args) {    
        while (true){
            i = i + i;
            System.out.println(i);
        }
    }
}

เมื่อฉันเรียกใช้โปรแกรมนี้สิ่งที่ผมเห็นคือ0สำหรับiในการส่งออกของฉัน ฉันคาดหวังว่ารอบแรกเราจะมีi = 1 + 1ตามด้วยi = 2 + 2ตามด้วยi = 4 + 4ฯลฯ

นี่เป็นเพราะทันทีที่เราพยายามประกาศใหม่iทางด้านซ้ายมือค่าของมันจะถูกรีเซ็ตเป็น0?

หากใครสามารถชี้ให้ฉันเห็นรายละเอียดปลีกย่อยของสิ่งนี้จะดีมาก

เปลี่ยนintไปlongและดูเหมือนว่าจะได้รับการพิมพ์ตัวเลขตามที่คาดไว้ ฉันประหลาดใจที่ความเร็วสูงสุดถึงค่าสูงสุด 32 บิต!

คำตอบ:


168

ปัญหาเกิดจากจำนวนเต็มล้น

ในการคำนวณทางคณิตศาสตร์เสริมสอง 32 บิต:

iเริ่มต้นจากการมีค่ากำลังสองค่า แต่พฤติกรรมล้นเริ่มต้นเมื่อคุณได้รับ 2 30 :

2 30 + 2 30 = -2 31

-2 31 + -2 31 = 0

... ในintทางคณิตศาสตร์เนื่องจากเป็นวิชาคณิตศาสตร์โดยพื้นฐานแล้ว 2 ^ 32


28
คุณช่วยขยายคำตอบของคุณหน่อยได้ไหม
DeaIss

17
@oOTesterOo มันเริ่มพิมพ์ 2, 4 เป็นต้น แต่มันไปถึงค่าสูงสุดของจำนวนเต็มอย่างรวดเร็วและมันจะ "ล้อมรอบ" เป็นจำนวนลบเมื่อถึงศูนย์มันจะอยู่ที่ศูนย์ตลอดไป
Richard Tingle

52
คำตอบนี้ยังไม่สมบูรณ์ (ไม่ได้พูดถึงว่าค่าจะไม่อยู่0ในการทำซ้ำสองสามครั้งแรก แต่ความเร็วของเอาต์พุตบดบังข้อเท็จจริงนั้นจาก OP) ทำไมถึงได้รับการยอมรับ?
Lightness Races ใน Orbit

16
ให้สันนิษฐานไว้ก่อนเพราะถือว่า OP เป็นประโยชน์
โจ

4
@LightnessRacesinOrbit แม้ว่าจะไม่ได้กล่าวถึงปัญหาที่ OP ใส่ไว้ในคำถามโดยตรง แต่คำตอบก็ให้ข้อมูลเพียงพอที่โปรแกรมเมอร์ที่ดีควรจะสามารถสรุปได้ว่าเกิดอะไรขึ้น
Kevin

334

บทนำ

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

ในมาตรวัดระยะทางmax digit = 9ดังนั้นจะไปไกลกว่าค่าเฉลี่ยสูงสุด9 + 1ซึ่งนำไปและให้0; แต่มีหลักไม่สูงมีการเปลี่ยนแปลงไปดังนั้นรีเซ็ตเคาน์เตอร์เพื่อ1 zeroคุณจะได้แนวคิด - "จำนวนเต็มล้น" ในใจตอนนี้

ป้อนคำอธิบายภาพที่นี่ ป้อนคำอธิบายภาพที่นี่

ตัวหนังสือทศนิยมที่ใหญ่ที่สุดของชนิด int เป็น 2147483647 (2 วันที่ 31 -1) ลิเทอรัลทศนิยมทั้งหมดตั้งแต่ 0 ถึง 2147483647 อาจปรากฏขึ้นที่ใดก็ได้ที่อินเทอรัลอาจปรากฏขึ้น แต่ลิเทอรัล 2147483648 อาจปรากฏเป็นตัวถูกดำเนินการของโอเปอเรเตอร์เอกพจน์เท่านั้น -

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

ดังนั้นล้นและล้อมรอบไป2147483647 + 1 -2147483648ดังนั้นจะล้นซึ่งไม่เท่ากับint i=2147483647 + 1 2147483648นอกจากนี้คุณยังพูดว่า "มันพิมพ์ 0 เสมอ" มันไม่ได้เพราะhttp://ideone.com/WHrQIW ด้านล่างตัวเลข 8 ตัวนี้แสดงจุดที่หมุนและล้น จากนั้นจะเริ่มพิมพ์ 0 วินาที นอกจากนี้อย่าแปลกใจว่ามันคำนวณได้เร็วแค่ไหนเครื่องจักรในปัจจุบันนั้นรวดเร็ว

268435456
536870912
1073741824
-2147483648
0
0
0
0

เหตุใดจำนวนเต็มจึงล้น "ล้อมรอบ"

PDF ต้นฉบับ


17
ฉันได้เพิ่มแอนิเมชั่นสำหรับ "Pacman" เพื่อจุดประสงค์เชิงสัญลักษณ์ แต่ยังเป็นภาพที่ยอดเยี่ยมว่าจะเห็นว่า "จำนวนเต็มล้น"
Ali Gajani

9
นี่คือคำตอบที่ฉันชอบที่สุดในไซต์นี้ตลอดกาล
Lee White

2
ดูเหมือนคุณจะพลาดไปแล้วว่านี่เป็นลำดับที่เพิ่มขึ้นเป็นสองเท่าไม่ใช่เพิ่ม
Paŭlo Ebermann

2
ฉันคิดว่าแอนิเมชั่น Pacman ได้คำตอบนี้มากกว่าคำตอบที่ยอมรับ เพิ่มคะแนนโหวตให้ฉันอีกเกมหนึ่งในเกมโปรดของฉัน!
Husman

3
สำหรับใครก็ตามที่ไม่ได้รับสัญลักษณ์: en.wikipedia.org/wiki/Kill_screen#Pac-Man
wei2912

46

ไม่มันไม่ได้พิมพ์เฉพาะเลขศูนย์

เปลี่ยนเป็นสิ่งนี้แล้วคุณจะเห็นว่าเกิดอะไรขึ้น

    int k = 50;
    while (true){
        i = i + i;
        System.out.println(i);
        k--;
        if (k<0) break;
    }

สิ่งที่เกิดขึ้นเรียกว่าล้น


61
วิธีที่น่าสนใจในการเขียน for loop :)
Bernhard

17
@Bernhard อาจจะคงโครงสร้างของโปรแกรมของ OP ไว้
Taemyr

4
@Taemyr น่าจะ แต่แล้วเขาก็สามารถแทนที่trueด้วยi<10000:)
Bernhard

7
ฉันแค่อยากจะเพิ่มข้อความเล็กน้อย โดยไม่ต้องลบ / เปลี่ยนแปลงข้อความใด ๆ ฉันประหลาดใจที่ได้รับความสนใจอย่างกว้างขวาง
peter.petrov

18
คุณสามารถใช้ตัวดำเนินการที่ซ่อนอยู่ในwhile(k --> 0)ชื่อ "while kไปที่0";)
Laurent LA RIZZA

15
static int i = 1;
    public static void main(String[] args) throws InterruptedException {
        while (true){
            i = i + i;
            System.out.println(i);
            Thread.sleep(100);
        }
    }

ใส่ออก:

2
4
8
16
32
64
...
1073741824
-2147483648
0
0

when sum > Integer.MAX_INT then assign i = 0;

4
อืมไม่มันใช้ได้กับลำดับเฉพาะนี้เพื่อให้ได้ศูนย์ ลองเริ่มด้วย 3
Paŭlo Ebermann

4

เนื่องจากฉันมีชื่อเสียงไม่เพียงพอฉันจึงไม่สามารถโพสต์รูปภาพของผลลัพธ์สำหรับโปรแกรมเดียวกันใน C ที่มีเอาต์พุตที่ควบคุมได้คุณสามารถลองด้วยตัวเองและดูว่ามันพิมพ์ได้จริง 32 ครั้งและตามที่อธิบายไว้เนื่องจากล้นi = 1073741824 + 1073741824การเปลี่ยนแปลง -2147483648และนอกจากนี้ต่อไปอีกจะออกในช่วงของ int Zeroและเปลี่ยนไป

#include<stdio.h>
#include<conio.h>

int main()
{
static int i = 1;

    while (true){
        i = i + i;
      printf("\n%d",i);
      _getch();
    }
      return 0;
}

3
โปรแกรมนี้ในภาษา C จะทริกเกอร์พฤติกรรมที่ไม่ได้กำหนดในทุกการดำเนินการซึ่งทำให้คอมไพเลอร์สามารถแทนที่โปรแกรมทั้งหมดด้วยอะไรก็ได้ (แม้ว่าsystem("deltree C:")คุณจะอยู่ใน DOS / Windows) การล้นจำนวนเต็มที่ลงชื่อเป็นพฤติกรรมที่ไม่ได้กำหนดใน C / C ++ ซึ่งแตกต่างจาก Java ระมัดระวังอย่างมากเมื่อใช้โครงสร้างประเภทนี้
filcab

@filcab: "แทนที่โปรแกรมทั้งหมดด้วยอะไรก็ได้"คุณกำลังพูดถึงอะไร ฉันได้เรียกใช้โปรแกรมนี้ใน Visual Studio 2012 และมันจะทำงานได้อย่างสมบูรณ์แบบที่ดีสำหรับทั้งsigned and unsignedจำนวนเต็มโดยไม่ต้องมีพฤติกรรมที่ไม่ได้กำหนด
Kaify

3
@Kaify: การทำงานได้ดีเป็นพฤติกรรมที่ไม่ได้กำหนดไว้อย่างสมบูรณ์แบบ ลองนึกภาพอย่างไรว่ารหัสไม่ได้i += iสำหรับ 32 + if (i > 0)ซ้ำแล้วมี คอมไพเลอร์สามารถปรับให้เหมาะสมได้if(true)เนื่องจากถ้าเราบวกจำนวนบวกiเสมอจะมีค่ามากกว่า 0 เสมอนอกจากนี้ยังสามารถปล่อยให้เงื่อนไขอยู่ในที่ซึ่งจะไม่ถูกดำเนินการเนื่องจากมีการแสดงส่วนเกิน เนื่องจากคอมไพเลอร์สามารถสร้างโปรแกรมที่ถูกต้องเท่า ๆ กันสองโปรแกรมจากโค้ดนั้นจึงเป็นพฤติกรรมที่ไม่ได้กำหนดไว้
3Doubloons

1
@Kaify: ไม่ใช่การวิเคราะห์ศัพท์ แต่เป็นคอมไพเลอร์ที่รวบรวมโค้ดของคุณและทำตามมาตรฐานสามารถทำการเพิ่มประสิทธิภาพได้ "แปลก ๆ " เช่นเดียวกับลูปที่ 3Doubloons พูดถึง เพียงเพราะคอมไพเลอร์ที่คุณพยายามทำบางอย่างอยู่เสมอไม่ได้หมายความว่ามาตรฐานรับประกันว่าโปรแกรมของคุณจะทำงานในลักษณะเดียวกันเสมอไป คุณมีพฤติกรรมที่ไม่ได้กำหนดรหัสบางอย่างอาจถูกตัดออกเนื่องจากไม่มีทางไปที่นั่นได้ (UB รับประกันเช่นนั้น) โพสต์เหล่านี้จากบล็อก llvm (และลิงก์ในนั้น) มีข้อมูลเพิ่มเติม: blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
filcab

2
@Kaify: ขออภัยที่ไม่ได้ใส่มันผิดอย่างสิ้นเชิงที่จะพูดว่า "เก็บไว้เป็นความลับ" โดยเฉพาะอย่างยิ่งเมื่อเป็นผลลัพธ์ที่สองใน Google สำหรับ "พฤติกรรมที่ไม่ได้กำหนด" ซึ่งเป็นคำเฉพาะที่ฉันใช้สำหรับสิ่งที่ถูกกระตุ้น .
filcab

4

ค่าของiจะถูกเก็บไว้ในหน่วยความจำโดยใช้จำนวนเลขฐานสองคงที่ เมื่อตัวเลขต้องการตัวเลขมากกว่าที่มีอยู่ระบบจะจัดเก็บเฉพาะตัวเลขที่ต่ำที่สุดเท่านั้น (ตัวเลขสูงสุดจะหายไป)

การเพิ่มiให้ตัวเองก็เหมือนกับการคูณiด้วยสอง เช่นเดียวกับการคูณตัวเลขด้วยสิบในสัญกรณ์ฐานสิบสามารถทำได้โดยการเลื่อนแต่ละหลักไปทางซ้ายและใส่ศูนย์ทางขวาการคูณตัวเลขด้วยสองในสัญกรณ์ไบนารีสามารถทำได้ในลักษณะเดียวกัน สิ่งนี้จะเพิ่มหนึ่งหลักทางด้านขวาดังนั้นตัวเลขจึงหายไปทางด้านซ้าย

ที่นี่ค่าเริ่มต้นคือ 1 ดังนั้นหากเราใช้ 8 หลักในการจัดเก็บi(ตัวอย่าง)

  • หลังจาก 0 การทำซ้ำค่าคือ 00000001
  • หลังจากการทำซ้ำ 1 ครั้งค่าคือ 00000010
  • หลังจากทำซ้ำ 2 ครั้งค่าคือ 00000100

ไปเรื่อย ๆ จนถึงขั้นตอนสุดท้ายที่ไม่ใช่ศูนย์

  • หลังจากทำซ้ำ 7 ครั้งค่าคือ 10000000
  • หลังจากทำซ้ำ 8 ครั้งค่าคือ 00000000

ไม่ว่าจะมีการจัดสรรเลขฐานสองจำนวนเท่าใดเพื่อจัดเก็บตัวเลขและไม่ว่าค่าเริ่มต้นจะเป็นเท่าใดก็ตามในที่สุดตัวเลขทั้งหมดจะหายไปเมื่อถูกผลักออกไปทางซ้าย หลังจากนั้นการดำเนินการต่อเพื่อเพิ่มจำนวนเป็นสองเท่าจะไม่เปลี่ยนจำนวน - จะยังคงแสดงด้วยศูนย์ทั้งหมด


3

ถูกต้อง แต่หลังจากทำซ้ำ 31 ครั้ง 1073741824 + 1073741824 คำนวณไม่ถูกต้องและหลังจากนั้นจะพิมพ์เพียง 0

คุณสามารถ refactor เพื่อใช้ BigInteger ได้ดังนั้นการวนซ้ำที่ไม่มีที่สิ้นสุดของคุณจะทำงานได้อย่างถูกต้อง

public class Mathz {
    static BigInteger i = new BigInteger("1");

    public static void main(String[] args) {    

        while (true){
            i = i.add(i);
            System.out.println(i);
        }
    }
}

ถ้าฉันใช้ long แทน int ดูเหมือนว่าจะพิมพ์> 0 ตัวเลขเป็นเวลานาน เหตุใดจึงไม่พบปัญหานี้หลังจากทำซ้ำ 63 ครั้ง
DeaIss

1
"คำนวณไม่ถูกต้อง" เป็นลักษณะที่ไม่ถูกต้อง การคำนวณถูกต้องตามสิ่งที่ Java ระบุว่าควรเกิดขึ้น ปัญหาที่แท้จริงคือผลลัพธ์ของการคำนวณ (อุดมคติ) ไม่สามารถแสดงเป็นint.
Stephen C

@oOTesterOo - เนื่องจากlongสามารถแทนตัวเลขที่ใหญ่กว่าintได้
Stephen C

Long มีช่วงที่ใหญ่กว่า ประเภท BigInteger ยอมรับค่า / ความยาวที่ JVM ของคุณสามารถจัดสรรได้
Bruno Volpato

ฉันคิดว่า int ล้นหลังจากการทำซ้ำ 31 ครั้งเนื่องจากเป็นจำนวนขนาดสูงสุด 32 บิตและนานเท่าใดที่ 64 บิตจะถึงค่าสูงสุดหลังจาก 63 ทำไมถึงไม่เป็นเช่นนั้น?
DeaIss

2

สำหรับการดีบักกรณีดังกล่าวเป็นการดีที่จะลดจำนวนการวนซ้ำในลูป ใช้สิ่งนี้แทนwhile(true):

for(int r = 0; r<100; r++)

จากนั้นคุณจะเห็นว่ามันเริ่มต้นด้วย 2 และเพิ่มค่าเป็นสองเท่าจนกว่าจะทำให้เกิดการล้น


2

ฉันจะใช้ตัวเลข 8 บิตเป็นภาพประกอบเพราะมันสามารถระบุรายละเอียดได้อย่างสมบูรณ์ในช่องว่างสั้น เลขฐานสิบหกเริ่มต้นด้วย 0x ในขณะที่เลขฐานสองเริ่มต้นด้วย 0b

ค่าสูงสุดสำหรับจำนวนเต็ม 8 บิตที่ไม่ได้ลงชื่อคือ 255 (0xFF หรือ 0b11111111) หากคุณเพิ่ม 1 โดยทั่วไปคุณจะได้รับ: 256 (0x100 หรือ 0b100000000) แต่เนื่องจากมีบิตมากเกินไป (9) จึงเกินค่าสูงสุดดังนั้นส่วนแรกจึงหลุดออกไปทำให้คุณมี 0 อย่างมีประสิทธิภาพ (0x (1) 00 หรือ 0b (1) 00000000 แต่มี 1 ดร็อป)

ดังนั้นเมื่อโปรแกรมของคุณทำงานคุณจะได้รับ:

1 = 0x01 = 0b1
2 = 0x02 = 0b10
4 = 0x04 = 0b100
8 = 0x08 = 0b1000
16 = 0x10 = 0b10000
32 = 0x20 = 0b100000
64 = 0x40 = 0b1000000
128 = 0x80 = 0b10000000
256 = 0x00 = 0b00000000 (wraps to 0)
0 + 0 = 0 = 0x00 = 0b00000000
0 + 0 = 0 = 0x00 = 0b00000000
0 + 0 = 0 = 0x00 = 0b00000000
...

1

ลิเทอรัลทศนิยมที่ใหญ่ที่สุดintคือ2147483648 (= 2 31 ) ลิเทอรัลทศนิยมทั้งหมดตั้งแต่0 ถึง 2147483647อาจปรากฏขึ้นที่ใดก็ได้ที่อินเทอรัลอาจปรากฏขึ้น แต่ลิเทอรัล 2147483648อาจปรากฏเป็นตัวถูกดำเนินการของโอเปอเรเตอร์เอกพจน์เท่านั้น -

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

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