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?

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 .


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 .


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.


4

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

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

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.


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.


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.


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

¿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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.