¿Por qué i = i + me doy 0?

96

Tengo un programa simple:

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

Cuando ejecuto este programa, todo lo que veo es 0para imi salida. Hubiera esperado que la primera ronda lo hiciéramos i = 1 + 1, seguida de i = 2 + 2, seguida de, i = 4 + 4etc.

¿Se debe esto al hecho de que tan pronto como intentamos volver a declarar ien el lado izquierdo, su valor se restablece a 0?

Si alguien puede señalarme los detalles más finos de esto, sería genial.

Cambie inta longy parece estar imprimiendo números como se esperaba. ¡Me sorprende lo rápido que alcanza el valor máximo de 32 bits!

DeaIss
fuente

Respuestas:

168

El problema se debe al desbordamiento de enteros.

En aritmética de complemento a dos de 32 bits:

ide hecho, comienza con valores de potencia de dos, pero luego los comportamientos de desbordamiento comienzan una vez que llega a 2 30 :

2 30 + 2 30 = -2 31

-2 31 + -2 31 = 0

... en intaritmética, ya que es esencialmente aritmética mod 2 ^ 32.

Louis Wasserman
fuente
28
¿Podría ampliar un poco su respuesta?
DeaIss
17
@oOTesterOo Comienza a imprimir 2, 4, etc. pero alcanza muy rápidamente el valor máximo del número entero y se "envuelve" a números negativos, una vez que llega a cero permanece en cero para siempre
Richard Tingle
52
Esta respuesta ni siquiera está completa (ni siquiera menciona que el valor no estará 0en las primeras iteraciones, pero la velocidad de salida oculta ese hecho del OP). ¿Por qué se acepta?
Lightness Races in Orbit
16
Es de suponer que fue aceptado porque el OP lo consideró útil.
Joe
4
@LightnessRacesinOrbit Aunque no aborda directamente los problemas que planteó el OP en su pregunta, la respuesta proporciona suficiente información para que un programador decente pueda inferir lo que está sucediendo.
Kevin
334

Introducción

El problema es el desbordamiento de enteros. Si se desborda, vuelve al valor mínimo y continúa desde allí. Si se desborda, vuelve al valor máximo y continúa desde allí. La siguiente imagen es de un odómetro. Utilizo esto para explicar los desbordamientos. Es un desbordamiento mecánico, pero sigue siendo un buen ejemplo.

En un odómetro, el max digit = 9, yendo más allá de los medios máximos 9 + 1, que se traslada y da un 0; Sin embargo, no hay un dígito más alto para cambiar a a 1, por lo que el contador se restablece zero. Entiendes la idea: ahora me vienen a la mente los "desbordamientos de enteros".

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

El literal decimal más grande de tipo int es 2147483647 (2 31 -1). Todos los literales decimales de 0 a 2147483647 pueden aparecer en cualquier lugar donde pueda aparecer un literal int, pero el literal 2147483648 puede aparecer solo como el operando del operador de negación unario -.

Si una suma entera se desborda, entonces el resultado son los bits de orden inferior de la suma matemática representados en algún formato de complemento a dos suficientemente grande. Si se produce un desbordamiento, el signo del resultado no es el mismo que el signo de la suma matemática de los dos valores de operando.

Por lo tanto, se 2147483647 + 1desborda y se envuelve -2147483648. Por int i=2147483647 + 1lo tanto , se desbordaría, lo que no es igual a 2147483648. Además, dices "siempre imprime 0". No es así, porque http://ideone.com/WHrQIW . A continuación, estos 8 números muestran el punto en el que pivota y se desborda. Luego comienza a imprimir ceros. Además, no se sorprenda de lo rápido que calcula, las máquinas de hoy son rápidas.

268435456
536870912
1073741824
-2147483648
0
0
0
0

Por qué el desbordamiento de enteros "envuelve"

PDF original

Ali Gajani
fuente
17
He añadido la animación de "Pacman" con fines simbólicos, pero también sirve como una gran imagen de cómo se verían los "desbordamientos de enteros".
Ali Gajani
9
Esta es mi respuesta favorita en este sitio de todos los tiempos.
Lee White
2
Parece haber pasado por alto que se trataba de una secuencia de duplicación, no de añadir una.
Paŭlo Ebermann
2
Creo que la animación de pacman obtuvo esta respuesta más votos positivos que la respuesta aceptada. Ten otro voto a favor: ¡es uno de mis juegos favoritos!
Husman
3
Para cualquiera que no
entendió
46

No, no imprime solo ceros.

Cámbielo a esto y verá qué pasa.

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

Lo que sucede se llama desbordamiento.

peter.petrov
fuente
61
Una forma interesante de escribir un bucle for :)
Bernhard
17
@Bernhard Probablemente sea para mantener la estructura del programa de OP.
Taemyr
4
@Taemyr Probablemente, pero luego podría haber reemplazado truecon i<10000:)
Bernhard
7
Solo quería agregar algunas declaraciones; sin borrar / cambiar ninguna declaración. Me sorprende que haya atraído tanta atención.
peter.petrov
18
Podrías haber usado el operador oculto while(k --> 0)llamado coloquialmente "mientras kva a 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);
        }
    }

sacar:

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

when sum > Integer.MAX_INT then assign i = 0;
TrungTran05T3
fuente
4
No, simplemente funciona para que esta secuencia en particular llegue a cero. Intente comenzar con 3.
Paŭlo Ebermann
4

Como no tengo suficiente reputación, no puedo publicar la imagen de la salida para el mismo programa en C con salida controlada, puede probar usted mismo y ver que realmente se imprime 32 veces y luego, como se explica debido al desbordamiento i = 1073741824 + 1073741824 cambia a -2147483648 y una adición más está fuera del rango de int y se convierte en 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;
}
Kaify
fuente
3
Este programa, en C, en realidad desencadena un comportamiento indefinido en cada ejecución, lo que permite que el compilador reemplace todo el programa con cualquier cosa (incluso system("deltree C:"), ya que está en DOS / Windows). El desbordamiento de enteros con signo es un comportamiento indefinido en C / C ++, a diferencia de Java. Tenga mucho cuidado al utilizar este tipo de construcción.
filcab
@filcab: "reemplaza todo el programa con cualquier cosa" de qué estás hablando. He ejecutado este programa en Visual Studio 2012 y funciona perfectamente bien para ambos signed and unsignedenteros sin ningún comportamiento indefinido
Kaify
3
@Kaify: Trabajar bien es un comportamiento indefinido perfectamente válido. Sin embargo, imagine que el código lo hizo i += idurante más de 32 iteraciones, luego lo hizo if (i > 0). El compilador podría optimizar eso if(true)ya que si siempre agregamos números positivos, isiempre será mayor que 0. También podría dejar la condición en, donde no se ejecutará, debido al desbordamiento representado aquí. Debido a que el compilador podría producir dos programas igualmente válidos a partir de ese código, es un comportamiento indefinido.
3Doubloons
1
@Kaify: no es un análisis léxico, es el compilador que compila tu código y, siguiendo el estándar, puede hacer optimizaciones "extrañas". Como el bucle del que hablaban 3Doubloons. El hecho de que los compiladores que intentas siempre parecen hacer algo, no significa que el estándar garantice que tu programa siempre se ejecutará de la misma manera. Tuviste un comportamiento indefinido, es posible que se haya eliminado parte del código ya que no hay forma de llegar allí (UB lo garantiza). Estas publicaciones del blog llvm (y enlaces allí) tienen más información: blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
filcab
2
@Kaify: Lo siento por no decirlo, pero es completamente incorrecto decir "lo mantuve en secreto", especialmente cuando es el segundo resultado, en Google, para "comportamiento indefinido", que era el término específico que usé para lo que se estaba activando. .
filcab
4

El valor de ise almacena en la memoria usando una cantidad fija de dígitos binarios. Cuando un número necesita más dígitos de los disponibles, solo se almacenan los dígitos más bajos (los dígitos más altos se pierden).

Sumar ia sí mismo es lo mismo que multiplicar ipor dos. Al igual que se puede multiplicar un número por diez en notación decimal deslizando cada dígito hacia la izquierda y poniendo un cero a la derecha, la multiplicación de un número por dos en notación binaria se puede realizar de la misma manera. Esto agrega un dígito a la derecha, por lo que se pierde un dígito a la izquierda.

Aquí el valor inicial es 1, así que si usamos 8 dígitos para almacenar i(por ejemplo),

  • después de 0 iteraciones, el valor es 00000001
  • después de 1 iteración, el valor es 00000010
  • después de 2 iteraciones, el valor es 00000100

y así sucesivamente, hasta el paso final distinto de cero

  • después de 7 iteraciones, el valor es 10000000
  • después de 8 iteraciones, el valor es 00000000

No importa cuántos dígitos binarios se asignen para almacenar el número, y no importa cuál sea el valor inicial, eventualmente todos los dígitos se perderán a medida que se desplacen hacia la izquierda. Después de ese punto, continuar duplicando el número no cambiará el número; seguirá estando representado por todos los ceros.

estrella infantil
fuente
3

Es correcto, pero después de 31 iteraciones, 1073741824 + 1073741824 no se calcula correctamente y luego imprime solo 0.

Puede refactorizar para usar BigInteger, por lo que su bucle infinito funcionará correctamente.

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);
        }
    }
}
Bruno Volpato
fuente
Si utilizo long en lugar de int, parece imprimir> 0 números durante mucho tiempo. ¿Por qué no se encuentra con este problema después de 63 iteraciones?
DeaIss
1
"No calcula correctamente" es una caracterización incorrecta. El cálculo es correcto de acuerdo con lo que la especificación de Java dice que debería suceder. El problema real es que el resultado del cálculo (ideal) no se puede representar como un int.
Stephen C
@oOTesterOo - porque longpuede representar números más grandes que intpueden.
Stephen C
Long tiene un rango más amplio. El tipo BigInteger acepta cualquier valor / longitud que su JVM pueda asignar.
Bruno Volpato
Supuse que int se desborda después de 31 iteraciones porque es un número de tamaño máximo de 32 bits, y tanto tiempo, ¿cuál de los 64 bits alcanzaría su máximo después de 63? ¿Por qué no es ese el caso?
DeaIss
2

Para depurar tales casos, es bueno reducir el número de iteraciones en el ciclo. Use esto en lugar de su while(true):

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

Luego puede ver que comienza con 2 y duplica el valor hasta que causa un desbordamiento.

usuario3732069
fuente
2

Usaré un número de 8 bits como ilustración porque se puede detallar completamente en un espacio corto. Los números hexadecimales comienzan con 0x, mientras que los números binarios comienzan con 0b.

El valor máximo para un entero sin signo de 8 bits es 255 (0xFF o 0b11111111). Si agrega 1, normalmente esperaría obtener: 256 (0x100 o 0b100000000). Pero como son demasiados bits (9), está por encima del máximo, por lo que la primera parte simplemente se elimina, dejándolo con 0 de manera efectiva (0x (1) 00 o 0b (1) 00000000, pero con el 1 eliminado).

Entonces, cuando su programa se ejecuta, obtiene:

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
...
rico remer
fuente
1

El literal decimal más grande de tipo intes 2147483648 (= 2 31 ). Todos los literales decimales de 0 a 2147483647 pueden aparecer en cualquier lugar donde pueda aparecer un literal int, pero el literal 2147483648 puede aparecer solo como el operando del operador de negación unario -.

Si una suma entera se desborda, entonces el resultado son los bits de orden inferior de la suma matemática representados en algún formato de complemento a dos suficientemente grande. Si se produce un desbordamiento, el signo del resultado no es el mismo que el signo de la suma matemática de los dos valores de operando.

Scooba Doo
fuente