Math.abs devuelve un valor incorrecto para Integer.Min_VALUE

90

Este código:

System.out.println(Math.abs(Integer.MIN_VALUE));

Devoluciones -2147483648

¿No debería devolver el valor absoluto como 2147483648?

usuario665319
fuente

Respuestas:

102

Integer.MIN_VALUEes -2147483648, pero el valor más alto que puede contener un entero de 32 bits es +2147483647. Intentar representar +2147483648en un int de 32 bits efectivamente "pasará" a -2147483648. Esto se debe a que, al usar enteros con signo, las representaciones binarias en complemento a dos de +2147483648y -2147483648son idénticas. Sin embargo, esto no es un problema, ya que +2147483648se considera fuera de rango.

Para leer un poco más sobre este tema, es posible que desee consultar el artículo de Wikipedia sobre el complemento de dos .

Jonmorgan
fuente
6
Bueno, no es un problema subestimar el impacto, bien podría significar problemas. Personalmente, preferiría tener una excepción o un sistema numérico que crezca dinámicamente en un lenguaje de nivel superior.
Maarten Bodewes
40

El comportamiento que señala es, de hecho, contrario a la intuición. Sin embargo, este comportamiento es el especificado por el javadoc paraMath.abs(int) :

Si el argumento no es negativo, se devuelve el argumento. Si el argumento es negativo, se devuelve la negación del argumento.

Es decir, Math.abs(int)debería comportarse como el siguiente código Java:

public static int abs(int x){
    if (x >= 0) {
        return x;
    }
    return -x;
}

Es decir, en caso negativo, -x.

De acuerdo con la sección 15.15.4 de JLS , -xes igual a (~x)+1, donde ~es el operador de complemento a nivel de bits.

Para comprobar si esto suena bien, tomemos -1 como ejemplo.

El valor entero -1se puede anotar como 0xFFFFFFFFen hexadecimal en Java (verifique esto con uno printlno cualquier otro método). Tomando -(-1)así da:

-(-1) = (~(0xFFFFFFFF)) + 1 = 0x00000000 + 1 = 0x00000001 = 1

Entonces funciona.

Probemos ahora con Integer.MIN_VALUE. Sabiendo que el entero más bajo puede ser representado por 0x80000000, es decir, el primer bit puesto a 1 y los 31 bits restantes puestos a 0, tenemos:

-(Integer.MIN_VALUE) = (~(0x80000000)) + 1 = 0x7FFFFFFF + 1 
                     = 0x80000000 = Integer.MIN_VALUE

Y es por eso que Math.abs(Integer.MIN_VALUE)vuelve Integer.MIN_VALUE. También tenga en cuenta que 0x7FFFFFFFes Integer.MAX_VALUE.

Dicho esto, ¿cómo podemos evitar problemas debido a este valor de retorno contraintuitivo en el futuro?

  • Podríamos, como salir en punta por @Bombe , echar las ints que longantes. Nosotros, sin embargo, debemos

    • volver a convertirlos en ints, lo que no funciona porque Integer.MIN_VALUE == (int) Math.abs((long)Integer.MIN_VALUE).
    • O continúe con longs de alguna manera esperando que nunca llamemos Math.abs(long)con un valor igual a Long.MIN_VALUE, ya que también lo hemos hecho Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE.
  • Podemos usar BigIntegers en todas partes, porque de BigInteger.abs()hecho siempre devuelve un valor positivo. Esta es una buena alternativa, aunque un poco más lenta que manipular tipos enteros sin procesar.

  • Podemos escribir nuestro propio contenedor para Math.abs(int), así:

/**
 * Fail-fast wrapper for {@link Math#abs(int)}
 * @param x
 * @return the absolute value of x
 * @throws ArithmeticException when a negative value would have been returned by {@link Math#abs(int)}
 */
public static int abs(int x) throws ArithmeticException {
    if (x == Integer.MIN_VALUE) {
        // fail instead of returning Integer.MAX_VALUE
        // to prevent the occurrence of incorrect results in later computations
        throw new ArithmeticException("Math.abs(Integer.MIN_VALUE)");
    }
    return Math.abs(x);
}
  • Use un entero bit a bit AND para borrar el bit alto, asegurándose de que el resultado no sea negativo: int positive = value & Integer.MAX_VALUE(esencialmente desbordando de Integer.MAX_VALUEa en 0lugar de Integer.MIN_VALUE)

Como nota final, este problema parece conocerse desde hace algún tiempo. Vea, por ejemplo, esta entrada sobre la regla correspondiente de findbugs .

bernard paulus
fuente
12

Esto es lo que dice Java doc para Math.abs () en javadoc :

Tenga en cuenta que si el argumento es igual al valor de Integer.MIN_VALUE, el valor int representable más negativo, el resultado es el mismo valor, que es negativo.

moe
fuente
4

Para ver el resultado que espera, envíe Integer.MIN_VALUEa long:

System.out.println(Math.abs((long) Integer.MIN_VALUE));
Bombe
fuente
1
¡Una posible solución, de hecho! Sin embargo, esto no resuelve el hecho de que Math.absestá siendo contradictorio al devolver un número negativo:Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE
bernard paulus
1
@bernardpaulus, bueno, ¿qué se supone que debe hacer, además de lanzar un ArithmeticException? Además, el comportamiento está claramente documentado en la documentación de la API.
Bombe
no hay una buena respuesta a su pregunta ... Solo quería señalar que este comportamiento, que es una fuente de errores, no se corrige con el uso de Math.abs(long). Pido disculpas por mi error aquí: pensé que propusiste el uso de Math.abs(long)como solución, cuando lo mostraste como una forma sencilla de "ver el resultado que espera el autor de la pregunta". Lo siento.
bernard paulus
En Java 15 con los nuevos métodos, de hecho, se lanza una excepción.
chiperortiz
1

2147483648 no se puede almacenar en un número entero en Java, su representación binaria es la misma que -2147483648.

ymajoros
fuente
0

Pero (int) 2147483648L == -2147483648 hay un número negativo que no tiene equivalente positivo, por lo que no tiene un valor positivo. Verá el mismo comportamiento con Long.MAX_VALUE.

Peter Lawrey
fuente
0

Hay una solución para esto en Java 15 será un método para int y long. Estarán presentes en las clases.

java.lang.Math and java.lang.StrictMath

Los métodos.

public static int absExact(int a)
public static long absExact(long a)

Si pasa

Integer.MIN_VALUE

O

Long.MIN_VALUE

Se lanza una excepción.

https://bugs.openjdk.java.net/browse/JDK-8241805

Me gustaría ver si se pasa Long.MIN_VALUE o Integer.MIN_VALUE, se devolvería un valor positivo y no una excepción, pero.

chiperortiz
fuente
-1

Math.abs no funciona todo el tiempo con números grandes. ¡Utilizo esta pequeña lógica de código que aprendí cuando tenía 7 años!

if(Num < 0){
  Num = -(Num);
} 
Dave
fuente
¿Qué hay saquí?
aioobe
Lo siento, olvidé actualizar eso de mi código original
Dave
Entonces, ¿en qué resulta esto si Numes igual Integer.MIN_VALUEantes del fragmento?
aioobe