Как Java обрабатывает целочисленные потери значимости и переполнение и как Вы проверили бы на него?

Как Java обрабатывает целочисленные потери значимости и переполнение?

Вовлекая от этого, как Вы проверяли бы/тестировали бы, что это происходит?

210
задан Ivar 16 July 2019 в 02:26
поделиться

4 ответа

Если он переполняется, он возвращается к минимальному значению и продолжается оттуда. Если он не заполнен, он возвращается к максимальному значению и продолжается оттуда.

Вы можете проверить это заранее следующим образом:

public static boolean willAdditionOverflow(int left, int right) {
    if (right < 0 && right != Integer.MIN_VALUE) {
        return willSubtractionOverflow(left, -right);
    } else {
        return (~(left ^ right) & (left ^ (left + right))) < 0;
    }
}

public static boolean willSubtractionOverflow(int left, int right) {
    if (right < 0) {
        return willAdditionOverflow(left, -right);
    } else {
        return ((left ^ right) & (left ^ (left - right))) < 0;
    }
}

(вы можете заменить int на long , чтобы выполнить те же проверки для long )

Если вы думаете, что это может происходить чаще, чем часто, рассмотрите возможность использования типа данных или объекта, который может хранить большие значения, например long или, возможно, java.math.BigInteger . Последний не переполняется, практически доступная память JVM - это предел.


Если вы уже используете Java8, вы можете использовать новые методы Math # addExact () и Math # subtractExact () , которые выдадут ] ArithmeticException при переполнении.

public static boolean willAdditionOverflow(int left, int right) {
    try {
        Math.addExact(left, right);
        return false;
    } catch (ArithmeticException e) {
        return true;
    }
}

public static boolean willSubtractionOverflow(int left, int right) {
    try {
        Math.subtractExact(left, right);
        return false;
    } catch (ArithmeticException e) {
        return true;
    }
}

Исходный код можно найти здесь и здесь соответственно.

Конечно, вы также можете просто использовать их сразу, вместо того, чтобы скрывать их в логическом служебном методе.

204
ответ дан 23 November 2019 в 04:35
поделиться

Ничего не делает - просто происходит недополнение / переполнение.

Значение «-1», которое является результатом вычисления, которое привело к переполнению, не отличается от «-1», полученного в результате любой другой информации. Таким образом, вы не можете определить по статусу или просто по значению, переполнено ли оно.

Но вы можете с умом относиться к своим вычислениям, чтобы избежать переполнения, если это имеет значение, или, по крайней мере, знать, когда это произойдет. Какая у вас ситуация?

3
ответ дан 23 November 2019 в 04:35
поделиться

Что касается примитивных целочисленных типов, Java вообще не обрабатывает Over / Underflow (для float и double поведение другое, оно сбрасывается до +/- бесконечности, как того требует IEEE-754).

При добавлении двух int вы не получите индикации переполнения. Простой метод проверки переполнения - использовать следующий более крупный тип для фактического выполнения операции и проверки того, находится ли результат в диапазоне для исходного типа:

public int addWithOverflowCheck(int a, int b) {
    // the cast of a is required, to make the + work with long precision,
    // if we just added (a + b) the addition would use int precision and
    // the result would be cast to long afterwards!
    long result = ((long) a) + b;
    if (result > Integer.MAX_VALUE) {
         throw new RuntimeException("Overflow occured");
    } else if (result < Integer.MIN_VALUE) {
         throw new RuntimeException("Underflow occured");
    }
    // at this point we can safely cast back to int, we checked before
    // that the value will be withing int's limits
    return (int) result;
}

То, что вы бы сделали вместо предложений throw, зависит от вашего требования приложений (выбросить, сбросить до минимума / максимума или просто записать что угодно). Если вы хотите обнаружить переполнение при длительных операциях, вам не повезло с примитивами, используйте вместо этого BigInteger.


Редактировать (2014-05-21): Поскольку этот вопрос, кажется, упоминается довольно часто, и мне пришлось решать ту же проблему самому, довольно легко оценить состояние переполнения тем же методом, что процессор вычислит его V флаг.

Это в основном логическое выражение, которое включает знак обоих операндов, а также результат:

/**
 * Add two int's with overflow detection (r = s + d)
 */
public static int add(final int s, final int d) throws ArithmeticException {
    int r = s + d;
    if (((s & d & ~r) | (~s & ~d & r)) < 0)
        throw new ArithmeticException("int overflow add(" + s + ", " + d + ")");    
    return r;
}

В java проще применить выражение (в if) ко всем 32 битам и проверить результат с помощью <0 (это будет эффективно проверять знаковый бит). Принцип работает точно так же для всех целочисленных примитивных типов , изменение всех объявлений в приведенном выше методе на long заставляет его работать долго.

Для меньших типов из-за неявного преобразования в int (подробнее см. JLS для побитовых операций), вместо проверки <0, проверка должна явно маскировать знаковый бит (0x8000 для коротких операндов, 0x80 для байтовых операндов , соответствующим образом отрегулируйте приведение типов и объявление параметров):

/**
 * Subtract two short's with overflow detection (r = d - s)
 */
public static short sub(final short d, final short s) throws ArithmeticException {
    int r = d - s;
    if ((((~s & d & ~r) | (s & ~d & r)) & 0x8000) != 0)
        throw new ArithmeticException("short overflow sub(" + s + ", " + d + ")");
    return (short) r;
}

(Обратите внимание, что в приведенном выше примере используется выражение, необходимое для обнаружения переполнения вычитания )


Итак, как / почему работают эти логические выражения? Во-первых, некоторые логические размышления показывают, что переполнение может только произойти, если знаки обоих аргументов совпадают. Потому что, если один аргумент отрицательный, а один положительный, результат (добавления) должен быть ближе к нулю, или, в крайнем случае, один аргумент равен нулю, так же, как другой аргумент. Поскольку аргументы сами по себе не могут создать условие переполнения, их сумма также не может создать переполнение.

Что произойдет, если оба аргумента имеют одинаковый знак? Давайте посмотрим на случай, когда оба являются положительными: добавление двух аргументов, которые создают сумму, превышающую типы MAX_VALUE, всегда будет давать отрицательное значение, поэтому происходит переполнение , если arg1 + arg2> MAX_VALUE. Теперь максимальное значение, которое может получиться, будет MAX_VALUE + MAX_VALUE (в крайнем случае оба аргумента - MAX_VALUE). Для байта (пример), который будет означать 127 + 127 = 254. Глядя на битовые представления всех значений, которые могут быть получены в результате сложения двух положительных значений, можно обнаружить, что для тех, которые переполняются (от 128 до 254), у всех установлен бит 7, в то время как все, что не переполняется (от 0 до 127), имеют очищенный бит 7 (самый верхний, знак).Именно это и проверяет первая (правая) часть выражения:

if (((s & d & ~r) | (~s & ~d & r)) < 0)

(~ s & ~ d & r) становится истинным, только если , оба операнда (s, d) положительны и результат (r) отрицательно (выражение работает со всеми 32-мя битами, но единственный бит, который нас интересует, это самый верхний (знаковый) бит, по которому проверяется <0).

Теперь, если оба аргумента отрицательны, их сумма никогда не может быть ближе к нулю, чем любой из аргументов, сумма должна быть ближе к минус бесконечности. Самое экстремальное значение, которое мы можем получить, - это MIN_VALUE + MIN_VALUE, которое (снова для примера байта) показывает, что для любого значения в диапазоне (от -1 до -128) установлен знаковый бит, в то время как любое возможное значение переполнения (от -129 до -256 ) бит знака очищен. Таким образом, знак результата снова указывает на состояние переполнения. Это то, что левая половина (s & d & ~ r) проверяет на случай, когда оба аргумента (s, d) отрицательны, а результат положительный. Логика во многом эквивалентна положительному случаю; во всех битовых комбинациях, которые могут возникнуть в результате сложения двух отрицательных значений, будет очищен знаковый бит тогда и только тогда, когда произошло недополнение.

65
ответ дан 23 November 2019 в 04:35
поделиться

Это зацикливается.

например:

public class Test {

    public static void main(String[] args) {
        int i = Integer.MAX_VALUE;
        int j = Integer.MIN_VALUE;

        System.out.println(i+1);
        System.out.println(j-1);
    }
}

отпечатки

-2147483648
2147483647
6
ответ дан 23 November 2019 в 04:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: