なぜi = i + 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です。

誰かが私にこれのより細かい詳細を指摘できるとしたら、それは素晴らしいことです。

をに変更する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年

17
@oOTesterOo 2、4などの出力を開始しますが、すぐに整数の最大値に達し、負の数値に「折り返し」、一度ゼロに達すると、永遠にゼロに留まります
Richard Tingle

52
この答えは完全ではありません(値が最初の数回の反復にはないことは述べられていませんが、出力の速度がOPからその事実を覆い隠しています)。なぜそれが受け入れられるのですか?0
オービットの軽さのレース

16
多分それはOPによって有用であると考えられたので受け入れられました。
Joe

4
@LightnessRacesinOrbitそれはOPが彼らの質問に置いた問題に直接対処しませんが、答えは、まともなプログラマーが何が起こっているのかを推測できるはずの十分な情報を提供します。
Kevin

334

前書き

問題は整数オーバーフローです。オーバーフローすると、最小値に戻り、そこから継続します。アンダーフローすると、最大値に戻り、そこから継続します。下の画像は走行距離計です。これを使用してオーバーフローを説明します。これは機械的なオーバーフローですが、良い例です。

走行距離計で、max digit = 9最大手段越えそう9 + 1引き継がと与えます0。ただし、上位の桁がaに変わることはない1ため、カウンタはにリセットされzeroます。あなたはアイデアを得る-「整数のオーバーフロー」が今頭に浮かぶ。

ここに画像の説明を入力してください ここに画像の説明を入力してください

int型の最大の10進リテラルは2147483647(2 31 -1)です。0から2147483647までのすべての10進リテラルは、intリテラルが出現する可能性のある場所に表示されますが、リテラル2147483648は単項否定演算子-のオペランドとしてのみ表示されます。

整数の加算がオーバーフローする場合、結果は、十分に大きい2の補数形式で表される数学的合計の下位ビットになります。オーバーフローが発生した場合、結果の符号は、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
私は象徴的な目的で「パックマン」のアニメーションを追加しましたが、「整数のオーバーフロー」がどのように見えるかを示す優れたビジュアルとしても機能します。
Ali Gajani 2014年

9
これは、このサイトでこれまでにない私のお気に入りの答えです。
リーホワイト

2
これを追加するのではなく、2倍のシーケンスであることを見逃しているようです。
–PaŭloEbermann 2014

2
パックマンアニメーションでは、この回答が受け入れられた回答よりも多く投票されたと思います。私に別の賛成投票をお願いします-これは私のお気に入りのゲームの1つです!
Husman 2014年

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ループを書く興味深い方法:)
Bernhard

17
@Bernhard OPのプログラムの構造を維持することでしょう。
Taemyr

4
@Taemyrはおそらく、その後、彼は交換している可能性がtruei<10000:)
ベルンハルト

7
いくつかのステートメントを追加したかっただけです。ステートメントを削除/変更せずに。こんなに注目されているのには驚きました。
peter.petrov 14年

18
あなたは隠された演算子を使用している可能性がwhile(k --> 0)口語的に「しばらくは、名前付きkに行くと0」;)
ローラン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ŭloEbermann 2014

4

十分な評判がないため、Cの同じプログラムの出力の画像を制御された出力で投稿することはできないので、uを試してみて、実際に32回印刷され、オーバーフローが原因で説明されているように見えることがわかりますi = 1073741824 + 1073741824 -2147483648に変更され、さらに1つ追加すると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 14年

@filcab:「プログラム全体を何かに置き換えます何を話しているのか。私はVisual Studio 2012でこのプログラムを実行しましたがsigned and unsigned未定義の動作
Kaify

3
@Kaify:正常に動作することは、完全に有効な未定義の動作です。コードがやっていることが想像i += i32+回の反復のために、その後、持っていましたif (i > 0)if(true)常に正の数を追加する場合は常にi0よりも大きくなるため、コンパイラーはこれを最適化できます。また、ここに示されているオーバーフローのために、条件が実行されないままになる場合もあります。コンパイラーはそのコードから2つの同等に有効なプログラムを生成できるため、動作は未定義です。
ダブロン2014年

1
@Kaify:字句解析ではなく、コードをコンパイルするコンパイラであり、標準に従って、「奇妙な」最適化を実行できます。3Doubloonsが話していたループのように。コンパイラが常に何かを行うように見えるからといって、標準がプログラムが常に同じように実行されることを保証しているわけではありません。未定義の動作があり、そこに到達する方法がないために一部のコードが削除された可能性があります(UBが保証します)。llvmブログからのこれらの投稿(およびそこにあるリンク)には詳細情報があります:blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
filcab

2
@Kaify:入れなかったことを申し訳ありませんが、「秘密にしておいた」と言うのは完全に間違っています。特に、Googleで「未定義の動作」について2番目の結果である場合、これはトリガーされたものに使用した特定の用語でした。
filcab 2014年

4

の値はi、固定量の2進数を使用してメモリに格納されます。数が利用可能なよりも多くの桁を必要とする場合、最下位の桁のみが保存されます(最上位の桁は失われます)。

iそれ自身に追加することは、i2 を乗算することと同じです。10進数表記で数値を10倍することは、各桁を左にスライドして右側にゼロを置くことによって実行できるのと同じように、2進数表記で数値を2倍することも同じように実行できます。これにより、右側に数字が1つ追加されるため、左側の数字が失われます。

ここでは、開始値は1なのでi、たとえば8桁を使用して保存すると、

  • 反復が0の場合、値は 00000001
  • 1回の反復後、値は 00000010
  • 2回の反復後、値は 00000100

など、最後のゼロ以外のステップまで

  • 7回の反復後、値は 10000000
  • 8回の反復後、値は 00000000

数値を格納するために2進数がいくつ割り当てられていても、また開始値が何であっても、最終的にすべての桁が左に押し出されると失われます。その後、数値を2倍にしても数値は変わりません。数値はすべてゼロで表されます。


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年

1
「正しく計算されない」は不正な特性です。計算は、Java仕様で発生するはずであると述べているとおりに正確です。実際の問題は、(理想的な)計算の結果をとして表現できないことintです。
スティーブンC

@oOTesterOo- longできるよりも大きい数を表すことができるためint
スティーブンC

ロングは範囲が広くなります。BigIntegerタイプは、JVMが割り当てることができる任意の値/長さを受け入れます。
Bruno Volpato 2014年

32ビットの最大サイズの数値であるため、31回の反復後にintがオーバーフローすると想定しました。64ビットであるので、63の後に最大値に到達します。なぜそうではないのですか?
DeaIss 2014年

2

このような場合のデバッグには、ループの反復回数を減らすことをお勧めします。あなたの代わりにこれを使用してくださいwhile(true)

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

次に、2で始まり、オーバーフローが発生するまで値を2倍にしていることがわかります。


2

短いスペースで完全に詳細化できるため、説明には8ビットの数値を使用します。16進数は0xで始まり、2進数は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の補数形式で表される数学的合計の下位ビットになります。オーバーフローが発生した場合、結果の符号は、2つのオペランド値の数学的合計の符号と同じではありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.