왜 나는 = 나는 + 나는 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입니까?

누구든지 이것에 대한 자세한 내용을 알려줄 수 있다면 좋을 것입니다.

변경 intlong그것은 예상대로 번호를 인쇄 할 것으로 보인다. 최대 32 비트 값에 도달하는 속도에 놀랐습니다!

답변:


168

이 문제는 정수 오버플로 때문입니다.

32 비트 2- 보완 산술 :

i실제로 2의 거듭 제곱 값을 갖는 것으로 시작하지만 2 30에 도달하면 오버플로 동작이 시작됩니다 .

2 30 + 2 30 = -2 31

-2 31 + -2 31 = 0

... int산술에서, 본질적으로 산술 mod 2 ^ 32이기 때문입니다.


28
답변을 조금 더 확장 해 주시겠습니까?
DeaIss 2014-06-11

17
@oOTesterOo 2, 4 등을 인쇄하기 시작하지만 정수의 최대 값에 매우 빠르게 도달하고 음수로 "둘러싸고"0에 도달하면 영원히 0으로 유지됩니다.
리처드 설렘

52
이 답변 (그것도하지 않는 경우에도 완료되지 않은 언급 값이 것을 하지0처음 몇 반복에 있지만, 출력 속도가 OP에서 그 사실을 왜곡한다). 왜 허용됩니까?
궤도의 가벼움 레이스

16
아마도 OP에서 도움이되었다고 생각했기 때문에 받아 들여졌을 것입니다.

4
@LightnessRacesinOrbit OP가 질문에 제기 한 문제를 직접 해결하지는 않지만 대답은 괜찮은 프로그래머가 무슨 일이 일어나고 있는지 추론 할 수있는 충분한 정보를 제공합니다.
Kevin

334

소개

문제는 정수 오버플로입니다. 오버플로되면 최소값으로 돌아가서 계속됩니다. 언더 플로하면 최대 값으로 돌아가서 계속됩니다. 아래 이미지는 주행 거리계입니다. 나는 이것을 사용하여 오버플로를 설명합니다. 기계적 오버플로이지만 여전히 좋은 예입니다.

주행 거리계에서 max digit = 9, 그래서 최대 수단을 넘어서는 9 + 1, 이것은 전달하고 제공합니다 0; 그러나으로 변경할 더 높은 숫자가 없으므로 1카운터가로 재설정됩니다 zero. "정수 오버플로"라는 아이디어가 떠 오릅니다.

여기에 이미지 설명 입력 여기에 이미지 설명 입력

int 유형의 가장 큰 10 진수 리터럴은 2147483647 (2 31 -1)입니다. 0부터 2147483647까지의 모든 10 진수 리터럴은 int 리터럴이 나타날 수있는 모든 곳에 나타날 수 있지만 리터럴 2147483648은 단항 부정 연산자-의 피연산자로만 나타날 수 있습니다.

정수 덧셈이 오버플로되면 결과는 충분히 큰 2의 보수 형식으로 표현 된 수학적 합의 하위 비트입니다. 오버플로가 발생하면 결과의 부호가 두 피연산자 값의 수학적 합계 부호와 동일하지 않습니다.

따라서 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 2014-06-12

9
이것은 항상이 사이트에서 내가 가장 좋아하는 답변입니다.
Lee White

2
당신은 이것이 하나를 추가하는 것이 아니라 배가되는 시퀀스라는 것을 놓친 것 같습니다.
Paŭlo Ebermann 2014-06-12

2
나는 pacman 애니메이션이 수락 된 답변 보다이 답변을 더 많이 받았다고 생각합니다. 나에게 또 다른 찬성 투표를하세요-내가 가장 좋아하는 게임 중 하나입니다!
Husman 2014-06-13

3
: 누군가를 위해 상징하지 못한 en.wikipedia.org/wiki/Kill_screen#Pac-Man
wei2912

46

아니요, 0 만 인쇄하지 않습니다.

이것을 이것으로 변경하면 무슨 일이 일어나는지 볼 수 있습니다.

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

일어나는 일을 오버플로라고합니다.


61
:) 루프를 작성하는 재미있는 방법
베른 하르트

17
@Bernhard 아마도 OP 프로그램의 구조를 유지하는 것 같습니다.
Taemyr

4
@Taemyr 아마,하지만 그는 교체 한 수 truei<10000:)
베른하르트

7
몇 가지 진술을 추가하고 싶었습니다. 문을 삭제 / 변경하지 않고. 이렇게 많은 주목을 받아 놀랐습니다.
peter.petrov 2014-06-12

18
당신은 숨겨진 연산자를 사용할 수도 while(k --> 0)"하면서 구어체 이름 k으로 이동을 0";)
로랑 LA 리자에게

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
음, 아니요,이 특정 시퀀스가 ​​0이되도록 작동합니다. 3으로 시작해보세요.
Paŭlo Ebermann 2014-06-12

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에 있기 때문에). 부호있는 정수 오버플로는 Java와 달리 C / C ++에서 정의되지 않은 동작입니다. 이러한 종류의 구성을 사용할 때는 매우주의하십시오.
filcab 2014-06-12

@filcab : "전체 프로그램을 다른 것으로 대체" 하는 것입니다. 이 프로그램을 Visual studio 2012에서 실행 signed and unsigned
했으며

3
@Kaify : 잘 작동하는 것은 완벽하게 유효한 정의되지 않은 동작입니다. 그러나 코드가 i += i32 회 이상 반복 한 다음 if (i > 0). 컴파일러 if(true)는 항상 양수를 추가 i하면 항상 0보다 크므로이를 최적화 할 수 있습니다. 또한 여기에 표시된 오버플로 때문에 실행되지 않는 조건을 그대로 둘 수 있습니다. 컴파일러가 해당 코드에서 똑같이 유효한 두 프로그램을 생성 할 수 있기 때문에 정의되지 않은 동작입니다.
3Doubloons 2014-06-13

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자신을 더하는 것은 i2 를 곱하는 것과 같습니다 . 10 진수 표기법에서 숫자에 10을 곱하는 것과 마찬가지로 각 숫자를 왼쪽으로 밀고 오른쪽에 0을 두어 수행 할 수 있으며, 이진 표기법으로 숫자에 2를 곱하는 것도 같은 방식으로 수행 할 수 있습니다. 이렇게하면 오른쪽에 한 자리가 추가되므로 왼쪽에서 한 자리가 손실됩니다.

여기에서 시작 값은 1이므로 8 자리 숫자를 사용하여 저장 i하면

  • 0 회 반복 후 값은 00000001
  • 1 회 반복 후 값은 다음과 같습니다. 00000010
  • 2 회 반복 후 값은 00000100

0이 아닌 마지막 단계까지

  • 7 회 반복 후 값은 10000000
  • 8 회 반복 후 값은 00000000

숫자를 저장하기 위해 할당 된 이진수 수와 시작 값에 관계없이 결국 모든 숫자가 왼쪽으로 밀려나 가면서 손실됩니다. 그 이후에도 계속해서 숫자를 두 배로 늘려도 숫자는 변경되지 않으며 여전히 모두 0으로 표시됩니다.


3

정확하지만 31 회 반복 후 1073741824 + 1073741824가 올바르게 계산되지 않고 그 후에는 0 만 인쇄됩니다.

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);
        }
    }
}

int 대신 long을 사용하면 오랫동안 0보다 큰 숫자가 인쇄되는 것 같습니다. 63 번의 반복 후에도이 문제가 발생하지 않는 이유는 무엇입니까?
DeaIss 2014-06-11

1
"올바르게 계산하지 않음"은 잘못된 특성입니다. 계산은 Java 스펙이 발생해야한다고 말하는 것에 따라 정확합니다. 진짜 문제는 (이상적인) 계산의 결과를 int.
Stephen C

@oOTesterOo- long할 수있는 것보다 더 큰 수를 나타낼 수 있기 때문 int입니다.
Stephen C

Long은 범위가 더 큽니다. BigInteger 유형은 JVM이 할당 할 수있는 모든 값 / 길이를 허용합니다.
Bruno Volpato 2014-06-11

32 비트 최대 크기의 숫자이기 때문에 31 번의 반복 후에 int 오버플로가 발생한다고 가정했는데 64 비트가 63 번 이후에 최대 값에 도달하는 것이 너무 길까요? 왜 그렇지 않습니까?
DeaIss 2014-06-11

2

이러한 경우를 디버깅하려면 루프의 반복 횟수를 줄이는 것이 좋습니다. 대신 이것을 사용하십시오 while(true):

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

그런 다음 2로 시작하고 오버플로가 발생할 때까지 값을 두 배로 늘리는 것을 볼 수 있습니다.


2

8 비트 숫자는 짧은 공간에서 완전히 상세하게 표현할 수 있기 때문에 설명을 위해 사용하겠습니다. 16 진수는 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

유형의 가장 큰 10 진수 리터럴 int2147483648 (= 2 31 )입니다. 0부터 2147483647 까지의 모든 10 진수 리터럴은 int 리터럴이 나타날 수있는 모든 곳에 나타날 수 있지만 리터럴 2147483648 은 단항 부정 연산자-의 피연산자로만 나타날 수 있습니다.

정수 덧셈이 오버플로되면 결과는 충분히 큰 2의 보수 형식으로 표현 된 수학적 합의 하위 비트입니다. 오버플로가 발생하면 결과의 부호가 두 피연산자 값의 수학적 합계 부호와 동일하지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.