Math.abs возвращает неверное значение для Integer.Min_VALUE

90

Этот код:

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

Возврат -2147483648

Должен ли он не возвращать абсолютное значение как 2147483648?

user665319
источник

Ответы:

102

Integer.MIN_VALUEесть -2147483648, но максимальное значение, которое может содержать 32-битное целое число, равно +2147483647. Попытка представить +2147483648в 32-битном int фактически "перевернется" на -2147483648. Это связано с тем, что при использовании целых чисел со знаком два дополнительных двоичных представления +2147483648и -2147483648идентичны. Однако это не проблема, так как +2147483648считается выходящим за пределы допустимого диапазона.

Чтобы получить немного больше информации по этому вопросу, вы можете прочитать статью в Википедии о дополнении Two .

Джонморган
источник
6
Что ж, не проблема недооценивать влияние, это вполне может означать проблемы. Лично я предпочел бы иметь исключение или систему счисления, которая динамически растет на языке более высокого уровня.
Maarten Bodewes
40

Указанное вами поведение действительно противоречит интуиции. Однако это поведение указано в javadoc дляMath.abs(int) :

Если аргумент не отрицательный, возвращается аргумент. Если аргумент отрицательный, возвращается отрицание аргумента.

То есть Math.abs(int)должен вести себя как следующий код Java:

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

То есть в отрицательном случае -x.

Согласно разделу 15.15.4 JLS , -xравно (~x)+1, где ~- оператор поразрядного дополнения.

Чтобы проверить, правильно ли это звучит, возьмем -1 в качестве примера.

Целочисленное значение -1может быть записано как 0xFFFFFFFFшестнадцатеричное в Java (проверьте это с помощью a printlnили любого другого метода). -(-1)Таким образом, взятие дает:

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

Итак, это работает.

Попробуем сейчас с Integer.MIN_VALUE. Зная, что наименьшее целое число может быть представлено 0x80000000, то есть первым битом, установленным в 1, и 31 оставшимся битом, установленным в 0, мы имеем:

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

И поэтому Math.abs(Integer.MIN_VALUE)возвращается Integer.MIN_VALUE. Также обратите внимание, что 0x7FFFFFFFесть Integer.MAX_VALUE.

Тем не менее, как мы можем избежать проблем из-за этого нелогичного возвращаемого значения в будущем?

  • Мы могли бы, как указано в @Bombe , преобразовать наши ints в longранее. Однако мы должны либо

    • отбросьте их обратно в ints, что не работает, потому что Integer.MIN_VALUE == (int) Math.abs((long)Integer.MIN_VALUE).
    • Или продолжайте с longs, как-то надеясь, что мы никогда не будем вызывать Math.abs(long)со значением, равным Long.MIN_VALUE, поскольку у нас также естьMath.abs(Long.MIN_VALUE) == Long.MIN_VALUE .
  • Мы можем использовать BigIntegers везде, потому чтоBigInteger.abs() что действительно всегда возвращает положительное значение. Это хорошая альтернатива, хотя и немного медленнее, чем манипулирование необработанными целочисленными типами.

  • Мы можем написать нашу собственную оболочку Math.abs(int), например так:

/**
 * 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);
}
  • Используйте целое побитовое И, чтобы очистить старший бит, гарантируя, что результат будет неотрицательным: int positive = value & Integer.MAX_VALUE(по существу, переполнение от Integer.MAX_VALUEдо0 вместо Integer.MIN_VALUE)

В заключение, эта проблема, кажется, известна уже некоторое время. См., Например, эту запись о соответствующем правиле findbugs .

Бернард Пол
источник
12

Вот что говорит Java-документ для Math.abs () в javadoc :

Обратите внимание, что если аргумент равен значению Integer.MIN_VALUE, наиболее отрицательному представляемому значению int, результатом будет то же самое значение, которое является отрицательным.

мо
источник
4

Чтобы увидеть ожидаемый результат, приведите Integer.MIN_VALUEк long:

System.out.println(Math.abs((long) Integer.MIN_VALUE));
Бомба
источник
1
Действительно, возможное исправление! Однако это не решает тот факт, Math.absкоторый противоречит Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE
здравому смыслу,
1
@bernardpaulus, ну а что он должен делать, кроме кидания ArithmeticException? Кроме того, поведение четко задокументировано в документации API.
Bombe
на ваш вопрос нет хорошего ответа ... Я просто хотел указать, что это поведение, являющееся источником ошибок, не исправляется с помощью Math.abs(long). Приношу извинения за свою ошибку здесь: я думал, что вы предложили использовать Math.abs(long)в качестве исправления, когда вы показали это как простой способ «увидеть результат, которого ожидает спрашивающий». Сожалею.
Бернард Паулус
В Java 15 с новыми методами фактически выдается исключение.
chiperortiz
1

2147483648 не может быть сохранено в виде целого числа в java, его двоичное представление такое же, как -2147483648.

Ymajoros
источник
0

Но (int) 2147483648L == -2147483648 есть одно отрицательное число, которое не имеет положительного эквивалента, поэтому для него нет положительного значения. Вы увидите такое же поведение с Long.MAX_VALUE.

Питер Лоури
источник
0

Есть исправление этого в Java 15 будет методом int и long. Они будут присутствовать на занятиях

java.lang.Math and java.lang.StrictMath

Методы.

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

Если вы пройдете

Integer.MIN_VALUE

ИЛИ

Long.MIN_VALUE

Выдается исключение.

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

Я хотел бы увидеть, передается ли Long.MIN_VALUE или Integer.MIN_VALUE, будет возвращено положительное значение, а не исключение, но.

чиперортиз
источник
-1

Math.abs не всегда работает с большими числами. Я использую эту небольшую логику кода, которую я выучил, когда мне было 7 лет!

if(Num < 0){
  Num = -(Num);
} 
Дэйв
источник
Что sздесь?
aioobe
Извините, я забыл обновить это из моего исходного кода
Дэйв
Так к чему это приведет, если перед фрагментом будет Numравно Integer.MIN_VALUE?
aioobe