Поведение, на которое вы указываете, действительно противоречиво.Тем не менее, это поведение, указанное в 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
s к long
раньше.Однако мы должны либо
- привести их обратно к
int
с, что не работает, потому что 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
s везде, потому что 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 .