Neden i = i + ben bana 0 veriyor?


96

Basit bir programım var:

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

Bu programı çalıştırdığınızda, tek gördüğüm olduğu 0için ibenim çıkışında. İlk turumuzu bekliyordum i = 1 + 1, ardından onu takip ediyor i = 2 + 2, i = 4 + 4vb.

Bunun nedeni i, sol tarafta yeniden beyan etmeye çalıştığımız anda değerinin sıfırlanması mı 0?

Biri beni bunun daha ince detaylarına yönlendirebilirse bu harika olur.

Değişim intiçin longve beklendiği gibi numaralar baskı gibi görünüyor. Maksimum 32 bit değerine ne kadar hızlı ulaştığına şaşırdım!

Yanıtlar:


168

Sorun tamsayı taşmasından kaynaklanıyor.

32 bitlik iki tamamlayıcı aritmetiğinde:

igerçekten de ikinin gücüne sahip olmaya başlar, ancak daha sonra 2 30'a ulaştığınızda taşma davranışları başlar :

2 30 + 2 30 = -2 31

-2 31 + -2 31 = 0

... intaritmetikte, çünkü aslında mod 2 ^ 32 aritmetiktir.


28
Cevabınızı biraz açabilir misiniz?
DeaIss

17
@oOTesterOo O 2, 4 vs baskı başlar ama sonsuza kadar sıfır kalır sıfır ulaştığında çok çabuk, negatif sayılara "etrafına sarıldığı için" maksimum tamsayı değerini ve ulaştığı
Richard Tingle

52
Bu cevap (hatta olmasa bile tam değil söz değer olacağı değil olması 0ilk birkaç tekrarlamalar, aynı çıkış hızı OP gerçeği yüzünden kapandığı). Neden kabul ediliyor?
Orbit'te Hafiflik Yarışları

16
Muhtemelen OP tarafından yararlı olduğu için kabul edildi.
Joe

4
@LightnessRacesinOrbit OP'nin sorusuna koyduğu sorunları doğrudan ele almasa da, cevap, iyi bir programcının neler olup bittiğini anlayabilmesi için yeterli bilgi veriyor.
Kevin

334

Giriş

Sorun tamsayı taşmasıdır. Eğer taşarsa minimum değere geri döner ve oradan devam eder. Altından taşarsa maksimum değere geri döner ve oradan devam eder. Aşağıdaki resim bir Kilometre sayacına aittir. Bunu taşmaları açıklamak için kullanıyorum. Bu mekanik bir taşma ama yine de iyi bir örnek.

Bir Kilometre Sayacında, bu max digit = 9yüzden maksimum araçların ötesine geçen 9 + 1ve a'yı taşıyan ve veren 0; Bununla birlikte, a'ya değiştirilecek daha yüksek bir basamak yoktur 1, bu nedenle sayaç, olarak sıfırlanır zero. Şimdi aklınıza "tam sayı taşmaları" fikri geliyor.

görüntü açıklamasını buraya girin görüntü açıklamasını buraya girin

İnt türünün en büyük ondalık değişmez değeri 2147483647'dir (2 31 -1). 0'dan 2147483647'ye kadar olan tüm ondalık değişmez değerler, bir int değişmezinin görünebileceği herhangi bir yerde görünebilir, ancak değişmez 2147483648 yalnızca tekli olumsuzlama operatörünün işleneni olarak görünebilir.

Bir tamsayı toplama taşması durumunda, sonuç, yeterince büyük bir ikinin tamamlayıcı biçiminde temsil edildiği gibi matematiksel toplamın düşük sıralı bitleridir. Taşma meydana gelirse, sonucun işareti iki işlenen değerin matematiksel toplamının işareti ile aynı değildir.

Böylece 2147483647 + 1taşar ve etrafına sarılır -2147483648. Dolayısıyla int i=2147483647 + 1, eşit olmayan taşmış olur 2147483648. Ayrıca, "her zaman 0 yazdırır" diyorsunuz. Değil, çünkü http://ideone.com/WHrQIW . Aşağıda, bu 8 sayı, döndüğü ve taştığı noktayı göstermektedir. Daha sonra 0s yazdırmaya başlar. Ayrıca, ne kadar hızlı hesapladığına şaşırmayın, bugünün makineleri hızlı.

268435456
536870912
1073741824
-2147483648
0
0
0
0

Neden tam sayı taşması "etrafını sarar"?

Orijinal PDF


17
"Pacman" animasyonunu sembolik amaçlar için ekledim ama aynı zamanda bir kişinin "tam sayı taşmalarını" nasıl göreceğine dair harika bir görsel olarak hizmet ediyor.
Ali Gajani

9
Bu, her zaman bu sitedeki en sevdiğim cevap.
Lee White

2
Görünüşe göre, bunun ikiye katlanan bir dizi olduğunu, bir ekleme değil.
Paŭlo Ebermann

2
Sanırım pacman animasyonu bu cevabı kabul edilen cevaptan daha fazla olumlu oy aldı. Bana bir olumlu oy daha verin - bu en sevdiğim oyunlardan biri!
Husman

3
Sembolizmi
anlamayanlar için

46

Hayır, yalnızca sıfır basmaz.

Bunu buna değiştirin ve ne olacağını göreceksiniz.

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

Olana taşma denir.


61
Bir for döngüsü yazmanın ilginç yolu :)
Bernhard

17
@Bernhard Muhtemelen OP'nin programının yapısını korumaktır.
Taemyr

4
@Taemyr Muhtemelen, ama sonra yerini almış trueolan i<10000:)
Bernhard

7
Sadece birkaç ifade eklemek istedim; herhangi bir ifadeyi silmeden / değiştirmeden. Bu kadar geniş bir ilgi görmesine şaşırdım.
peter.petrov

18
Sen gizli operatörü kullanmış olabilir while(k --> 0)"ise halk dilinde adında kgider 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);
        }
    }

çıktı:

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

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

4
Hayır, sadece bu belirli dizinin sıfıra gelmesi için işe yarıyor. 3 ile başlamayı deneyin.
Paŭlo Ebermann

4

Yeterli itibarım olmadığından, aynı programın çıktısının resmini kontrollü çıktıyla C'ye gönderemiyorum, kendinizi deneyebilir ve bunun aslında 32 kez ve sonra da taşma nedeniyle açıklandığı gibi yazdırdığını görebilirsiniz i = 1073741824 + 1073741824 -2147483648 olarak değişir ve bir ekleme daha int aralığının dışında kalır ve olarak değişir 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'deki bu program aslında her yürütmede tanımsız davranışı tetikler, bu da derleyicinin tüm programı herhangi bir şeyle değiştirmesine izin verir (hatta system("deltree C:")DOS / Windows'da olduğunuz için). İşaretli tamsayı taşması, Java'nın aksine C / C ++ 'da tanımsız bir davranıştır. Bu tür bir yapı kullanırken çok dikkatli olun.
filcab

@filcab: "tüm programı herhangi bir şeyle değiştirin" , neden bahsediyorsunuz. Bu programı Visual studio 2012'de çalıştırdım ve tanımlanmamışsigned and unsigned herhangi bir davranış
Kaify

3
@Kaify: İyi çalışmak, tamamen geçerli, tanımlanmamış bir davranıştır. Bununla birlikte, kodun i += i32+ yineleme için yaptığını, sonra sahip olduğunu düşünün if (i > 0). Derleyici, if(true)her zaman pozitif sayılar eklersek, iher zaman 0'dan büyük olacağı için bunu optimize edebilir. Ayrıca, burada gösterilen taşma nedeniyle koşulu çalıştırılmayacağı yerde bırakabilir. Derleyici bu koddan eşit derecede geçerli iki program üretebildiğinden, bu tanımsız bir davranıştır.
3Doubloons

1
@Kaify: Sözcüksel analiz değil, kodunuzu derleyen derleyicidir ve standardı takip ederek "tuhaf" optimizasyonlar yapabilmektir. 3Doubloons'un bahsettiği döngü gibi. Sadece denediğiniz derleyiciler her zaman bir şeyler yapıyor gibi göründüğü için, standardın programınızın her zaman aynı şekilde çalışacağını garanti ettiği anlamına gelmez. Tanımlanmamış bir davranışınız vardı, oraya ulaşmanın bir yolu olmadığı için bazı kodlar kaldırılmış olabilir (UB bunu garanti eder). Llvm blogundaki bu gönderiler (ve oradaki bağlantılar) daha fazla bilgi içeriyor
filcab

2
@Kaify: Söylemediğim için üzgünüm, ancak özellikle Google'da "tanımlanmamış davranış" için ikinci sonuç olduğunda "sır olarak sakladım" demek tamamen yanlış. .
filcab

4

Değeri, isabit miktarda ikili rakam kullanılarak bellekte saklanır. Bir numara mevcut olandan daha fazla basamağa ihtiyaç duyduğunda, yalnızca en düşük basamaklar saklanır (en yüksek basamaklar kaybolur).

Ekleme ikendine çoğalan aynıdır iiki ile. Tıpkı ondalık gösterimde bir sayıyı on ile çarpmanın her basamağı sola kaydırıp sağa sıfır koyarak yapılabilmesi gibi, ikili gösterimde bir sayıyı ikiyle çarpmak da aynı şekilde yapılabilir. Bu, sağa bir rakam ekler, böylece solda bir rakam kaybolur.

Burada başlangıç ​​değeri 1'dir, yani saklamak iiçin 8 basamak kullanırsak (örneğin),

  • 0 yinelemeden sonra değer 00000001
  • 1 yinelemeden sonra değer 00000010
  • 2 yinelemeden sonra değer 00000100

ve bu, sıfır olmayan son adıma kadar

  • 7 yinelemeden sonra değer 10000000
  • 8 yinelemeden sonra değer 00000000

Numarayı saklamak için kaç tane ikili hane ayrılmış olursa olsun ve başlangıç ​​değeri ne olursa olsun, sol tarafa doğru itildiklerinde sonunda tüm rakamlar kaybolacaktır. Bu noktadan sonra, sayıyı ikiye katlamaya devam etmek sayıyı değiştirmez - yine de tüm sıfırlarla temsil edilecektir.


3

Doğru, ancak 31 yinelemeden sonra 1073741824 + 1073741824 doğru hesaplamıyor ve bundan sonra yalnızca 0 yazdırıyor.

BigInteger'ı kullanmak için yeniden düzenleme yapabilirsiniz, böylece sonsuz döngünüz doğru çalışacaktır.

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

İnt yerine long kullanırsam, uzun bir süre için> 0 sayıları basılmış gibi görünür. 63 yinelemeden sonra neden bu sorunla karşılaşmıyor?
DeaIss

1
"Doğru hesaplamıyor" yanlış bir karakterizasyondur. Hesaplama, Java spesifikasyonunun olması gerektiğini söylediği şeye göre doğrudur. Asıl mesele, (ideal) hesaplamanın sonucunun bir int.
Stephen C

@oOTesterOo - çünkü longyapabileceğinden daha büyük sayıları temsil intedebilir.
Stephen C

Long'un daha geniş bir aralığı vardır. BigInteger türü, JVM'nizin ayırabileceği herhangi bir değeri / uzunluğu kabul eder.
Bruno Volpato

31 yinelemeden sonra int taşması olduğunu varsaydım çünkü 32-bit maksimum boyutlu bir sayı ve o kadar uzun olan 64-bit, 63'ten sonra maksimumuna ulaşır? Neden durum böyle değil?
DeaIss

2

Bu tür durumlarda hata ayıklamak için döngüdeki yineleme sayısını azaltmak iyidir. Bunu sizin yerine kullanın while(true):

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

Daha sonra 2 ile başladığını ve bir taşmaya neden olana kadar değeri ikiye katladığını görebilirsiniz.


2

Örnek olarak 8 bitlik bir sayı kullanacağım çünkü kısa bir alanda tamamen detaylandırılabilir. Onaltılık sayılar 0x ile başlarken, ikili sayılar 0b ile başlar.

8 bitlik işaretsiz tamsayı için maksimum değer 255'tir (0xFF veya 0b11111111). 1 eklerseniz, genellikle şunu elde etmeyi beklersiniz: 256 (0x100 veya 0b100000000). Fakat bu çok fazla bit (9) olduğu için, bu maksimumun üzerindedir, bu nedenle ilk kısım düşer ve sizi etkili bir şekilde 0 ile bırakır (0x (1) 00 veya 0b (1) 00000000, ancak 1 düşmüştür).

Böylece programınız çalıştığında şunları elde edersiniz:

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

Büyük ondalık hazır bilgisinin intolduğunu 2147483648 (= 2 31 ). 0'dan 2147483647'ye kadar olan tüm ondalık değişmez değerler, bir int değişmezinin görünebileceği herhangi bir yerde görünebilir, ancak değişmez 2147483648 yalnızca tekli olumsuzlama operatörünün işleneni olarak görünebilir -.

Bir tamsayı toplama taşması durumunda, sonuç, yeterince büyük bir ikinin tamamlayıcı biçiminde temsil edildiği gibi matematiksel toplamın düşük sıralı bitleridir. Taşma meydana gelirse, sonucun işareti iki işlenen değerin matematiksel toplamının işareti ile aynı değildir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.