java - Math.abs devuelve un valor incorrecto para Integer.Min_VALUE
java math (6)
Este código:
System.out.println(Math.abs(Integer.MIN_VALUE));
Devuelve -2147483648
¿No debería devolver el valor absoluto como 2147483648
?
2147483648 no se puede almacenar en un entero en Java, su representación binaria es la misma que -2147483648.
El comportamiento que señalas es, de hecho, contra-intuitivo. Sin embargo, este comportamiento es el especificado por el javadoc para Math.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 de Java:
public static int abs(int x){
if (x >= 0) {
return x;
}
return -x;
}
Es decir, en el caso negativo, -x
.
De acuerdo con la sección 15.15.4 de JLS , -x
es igual a (~x)+1
, donde ~
es el operador de complemento bit a bit.
Para comprobar si esto suena bien, tomemos -1 como ejemplo.
El valor entero -1
se puede notar como 0xFFFFFFFF
en hexadecimal en Java (verifique esto con println
o cualquier otro método). Tomar -(-1)
da así:
-(-1) = (~(0xFFFFFFFF)) + 1 = 0x00000000 + 1 = 0x00000001 = 1
Entonces, funciona
Permítanos probar ahora con Integer.MIN_VALUE
. Sabiendo que el número entero más bajo se puede representar por 0x80000000
, es decir, el primer bit establecido en 1 y los 31 bits restantes en 0, tenemos:
-(Integer.MIN_VALUE) = (~(0x80000000)) + 1 = 0x7FFFFFFF + 1
= 0x80000000 = Integer.MIN_VALUE
Y esta es la razón Math.abs(Integer.MIN_VALUE)
cual Math.abs(Integer.MIN_VALUE)
devuelve Integer.MIN_VALUE
. También tenga en cuenta que 0x7FFFFFFF
es Integer.MAX_VALUE
.
Dicho esto, ¿cómo podemos evitar problemas debido a este valor de retorno contraintuitivo en el futuro?
Podríamos, como lo señaló @Bombe , emitir nuestras
int
slong
antes. Nosotros, sin embargo, debemos- devolverlos a
int
s, que no funciona porqueInteger.MIN_VALUE == (int) Math.abs((long)Integer.MIN_VALUE)
. - O continúe con
long
s de alguna manera esperando que nunca llamemosMath.abs(long)
con un valor igual aLong.MIN_VALUE
, ya que también tenemosMath.abs(Long.MIN_VALUE) == Long.MIN_VALUE
.
- devolverlos a
Podemos utilizar
BigInteger
todas partes, porqueBigInteger.abs()
siempre devuelve un valor positivo. Esta es una buena alternativa, difícil un poco más lenta que la manipulación de tipos enteros sin procesar.Podemos escribir nuestro propio contenedor para
Math.abs(int)
, como este:
/**
* 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);
}
- Utilice un número entero Y para borrar el bit alto, asegurándose de que el resultado sea no negativo:
int positive = value & Integer.MAX_VALUE
(esencialmente desbordamiento deInteger.MAX_VALUE
a0
lugar deInteger.MIN_VALUE
)
Como nota final, este problema parece conocerse desde hace tiempo. Vea, por ejemplo, esta entrada sobre la regla correspondiente de findbugs .
Esto es lo que Java doc dice 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.
Para ver el resultado que esperas, Integer.MIN_VALUE
en long
:
System.out.println(Math.abs((long) Integer.MIN_VALUE));
Pero (int) 2147483648L == -2147483648
Hay un número negativo que no tiene un equivalente positivo, por lo que no tiene un valor positivo. Verá el mismo comportamiento con Long.MAX_VALUE.
Integer.MIN_VALUE
es -2147483648
, pero el valor más alto que un entero de 32 bits puede contener es +2147483647
. Intentar representar +2147483648
en un int de 32 bits se "transferirá" de manera -2147483648
a -2147483648
. Esto se debe a que, cuando se utilizan enteros con signo, las representaciones binarias del complemento de dos de +2147483648
y -2147483648
son idénticas. Sin embargo, esto no es un problema, ya que el +2147483648
se considera fuera de rango.
Para leer un poco más sobre este tema, es posible que desee consultar el artículo de Wikipedia sobre complemento de Two .