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


90

Цей код:

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

Повернення -2147483648

Чи не повинен він повертати абсолютне значення як 2147483648?

Відповіді:


102

Integer.MIN_VALUEє -2147483648, але найвищим значенням, яке може містити 32-бітове ціле число, є +2147483647. Спроба представити +2147483648в 32-бітному int ефективно "перекинеться" на -2147483648. Це відбувається тому, що при використанні знакових цілих чисел, два доповнення до довічного уявлення +2147483648і -2147483648є ідентичними. Однак це не проблема, оскільки +2147483648це вважається поза зоною дії.

Щоб трохи більше прочитати цього питання, ви можете переглянути статтю Вікіпедії про доповнення Двох .


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 (перевірте це за допомогою 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раніше. Однак ми мусимо або те, і інше

    • відкинути їх назад у 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 doc говорить про 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)як виправлення, коли ви показали це як простий спосіб "побачити результат, який очікує запитувач". Вибачте.
bernard paulus

У Java 15 з новими методами фактично створено виняток.
chiperortiz

1

2147483648 неможливо зберегти в цілому числі в Java, його двійкове представлення таке ж, як -2147483648.


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.