Math.abs retorna um valor errado para Integer.Min_VALUE

90

Este código:

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

Devoluções -2147483648

Não deveria retornar o valor absoluto como 2147483648?

user665319
fonte

Respostas:

102

Integer.MIN_VALUEé -2147483648, mas o valor mais alto que um inteiro de 32 bits pode conter é +2147483647. A tentativa de representar +2147483648em um int de 32 bits efetivamente "rolará" para -2147483648. Isso ocorre porque, ao usar inteiros com sinal, as representações binárias do complemento de dois de +2147483648e -2147483648são idênticas. Isso não é um problema, pois +2147483648é considerado fora do intervalo.

Para ler um pouco mais sobre este assunto, você pode querer verificar o artigo da Wikipedia sobre o complemento de Dois .

Jonmorgan
fonte
6
Bem, nenhum problema é subestimar o impacto, pode muito bem significar problemas. Pessoalmente, prefiro ter uma exceção ou um sistema numérico que cresça dinamicamente em uma linguagem de nível superior.
Maarten Bodewes
40

O comportamento que você aponta é, de fato, contra-intuitivo. No entanto, esse comportamento é o especificado pelo javadoc paraMath.abs(int) :

Se o argumento não for negativo, o argumento será retornado. Se o argumento for negativo, a negação do argumento é retornada.

Ou seja, Math.abs(int)deve se comportar como o seguinte código Java:

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

Ou seja, em caso negativo -x,.

De acordo com a seção 15.15.4 do JLS , -xé igual a (~x)+1, onde ~é o operador de complemento bit a bit.

Para verificar se isso está certo, vamos pegar -1 como exemplo.

O valor inteiro -1pode ser anotado como 0xFFFFFFFFem hexadecimal em Java (verifique isso com um printlnou qualquer outro método). Tomando -(-1)assim dá:

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

Então, funciona.

Vamos tentar agora com Integer.MIN_VALUE. Sabendo que o menor inteiro pode ser representado por 0x80000000, ou seja, o primeiro bit definido como 1 e os 31 bits restantes definidos como 0, temos:

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

E é por isso que Math.abs(Integer.MIN_VALUE)retorna Integer.MIN_VALUE. Observe também que 0x7FFFFFFFé Integer.MAX_VALUE.

Dito isso, como podemos evitar problemas devido a esse valor de retorno contra-intuitivo no futuro?

  • Poderíamos, como apontado por @Bombe , lançar nossos ints para longantes. Nós, no entanto, devemos

    • lance-os de volta em ints, o que não funciona porque Integer.MIN_VALUE == (int) Math.abs((long)Integer.MIN_VALUE).
    • Ou continue com longs, de alguma forma, esperando que nunca paguemos Math.abs(long)com um valor igual a Long.MIN_VALUE, já que também fizemos Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE.
  • Podemos usar BigIntegers em qualquer lugar, porque de BigInteger.abs()fato sempre retorna um valor positivo. Esta é uma boa alternativa, embora um pouco mais lenta do que manipular tipos inteiros brutos.

  • Podemos escrever nosso próprio wrapper para Math.abs(int), assim:

/**
 * 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 um inteiro bit a bit AND para limpar o bit alto, garantindo que o resultado não seja negativo: int positive = value & Integer.MAX_VALUE(essencialmente transbordando de Integer.MAX_VALUEpara em 0vez de Integer.MIN_VALUE)

Como nota final, este problema parece ser conhecido há algum tempo. Veja, por exemplo, esta entrada sobre a regra de findbugs correspondente .

bernard paulus
fonte
12

Aqui está o que o documento Java diz para Math.abs () em javadoc :

Observe que se o argumento for igual ao valor de Integer.MIN_VALUE, o valor int representável mais negativo, o resultado é o mesmo valor, que é negativo.

moe
fonte
4

Para ver o resultado que você espera, lance Integer.MIN_VALUEpara long:

System.out.println(Math.abs((long) Integer.MIN_VALUE));
Bombe
fonte
1
Uma possível correção, de fato! No entanto, isso não resolve o fato de que Math.absestá sendo contra-intuitivo ao retornar um número negativo:Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE
bernard paulus
1
@bernardpaulus, bem, o que ele deve fazer, além de lançar um ArithmeticException? Além disso, o comportamento está claramente documentado na documentação da API.
Bombe
não há uma boa resposta para sua pergunta ... Eu só queria ressaltar que esse comportamento, que é uma fonte de bugs, não é corrigido pelo uso de Math.abs(long). Peço desculpas pelo meu erro aqui: achei que você propôs o uso de Math.abs(long)como uma correção, quando o mostrou como uma forma simples de "ver o resultado que o autor da pergunta está esperando". Desculpe.
bernard paulus
No Java 15, com os novos métodos, na verdade, uma exceção é lançada.
chiperortiz
1

2147483648 não pode ser armazenado em um inteiro em java, sua representação binária é igual a -2147483648.

ymajoros
fonte
0

Mas (int) 2147483648L == -2147483648 há um número negativo que não possui equivalente positivo, portanto não há valor positivo para ele. Você verá o mesmo comportamento com Long.MAX_VALUE.

Peter Lawrey
fonte
0

Existe uma correção para isso em Java 15 será um método para int e long. Eles estarão presentes nas aulas

java.lang.Math and java.lang.StrictMath

Os métodos.

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

Se você passar

Integer.MIN_VALUE

OU

Long.MIN_VALUE

Uma exceção é lançada.

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

Gostaria de ver se Long.MIN_VALUE ou Integer.MIN_VALUE é passado, um valor positivo seria return e não uma exceção, mas.

chiperortiz
fonte
-1

Math.abs não funciona o tempo todo com números grandes. Eu uso essa pequena lógica de código que aprendi quando tinha 7 anos!

if(Num < 0){
  Num = -(Num);
} 
Dave
fonte
O que está saqui?
aioobe
Desculpe, esqueci de atualizar meu código original
Dave
Então, o que isso resulta em se for Numigual Integer.MIN_VALUEantes do snippet?
aioobe