Math.abs возвращает неверное значение для Integer.Min_VALUE
Этот код:
System.out.println(Math.abs(Integer.MIN_VALUE));
Возвращает -2147483648
Если оно не возвращает абсолютное значение как 2147483648
?
Ответы
Ответ 1
Integer.MIN_VALUE
-2147483648
, но наибольшее значение, которое может содержать 32-битное целое число, равно +2147483647
. Попытка представить +2147483648
в 32-битном int эффективно "перевернется" на -2147483648
. Это связано с тем, что при использовании целых чисел со знаком двоичные представления с двумя дополнениями +2147483648
и -2147483648
идентичны. Однако это не проблема, поскольку +2147483648
считается вне диапазона.
Для получения более подробной информации по этому вопросу вы можете проверить статью в Википедии о двух дополнениях.
Ответ 2
Поведение, которое вы указываете, действительно, противоречит интуиции. Однако это поведение является тем, которое указано javadoc для Math.abs(int)
:
Если аргумент не отрицательный, возвращается аргумент. Если аргумент отрицательный, возвращается отрицание аргумента.
То есть Math.abs(int)
должен вести себя как следующий код Java:
public static int abs(int x){
if (x >= 0) {
return x;
}
return -x;
}
То есть в отрицательном случае -x
.
Согласно JLS раздел 15.15.4, -x
равен (~x)+1
, где ~
является поразрядным оператора комплемента.
Чтобы проверить, правильно ли это звучит, возьмите пример -1.
Целочисленное значение -1
можно отметить как 0xFFFFFFFF
в шестнадцатеричном формате в Java (проверьте это с помощью 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, направить int
на long
раньше. Мы, однако, должны либо
- вернуть их обратно в
int
s, что не работает, потому что
Integer.MIN_VALUE == (int) Math.abs((long)Integer.MIN_VALUE)
.
- Или продолжайте с
long
каким-то образом, надеясь, что мы никогда не позвоним Math.abs(long)
со значением, равным Long.MIN_VALUE
, так как у нас также есть Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE
.
-
Мы можем использовать BigInteger
всюду, потому что 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.
Ответ 3
Вот что говорит Java-документ для Math.abs() в javadoc:
Обратите внимание, что если аргумент равен значение Integer.MIN_VALUE, наибольшая отрицательная представимая величина int, результатом является то же значение, которое отрицательно.
Ответ 4
Чтобы увидеть результат, который вы ожидаете, отбрасывайте Integer.MIN_VALUE
до long
:
System.out.println(Math.abs((long) Integer.MIN_VALUE));
Ответ 5
2147483648 не может быть сохранен в целое число в java, его двоичное представление такое же, как -2147483648.
Ответ 6
Но (int) 2147483648L == -2147483648
Существует одно отрицательное число, которое не имеет положительного эквивалента, поэтому для него нет положительного значения. Вы увидите то же поведение с Long.MAX_VALUE.