Как Java обрабатывает целочисленные переполнения и переполнения и как бы вы это проверили? - PullRequest
203 голосов
/ 09 июня 2010

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

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

Ответы [ 12 ]

201 голосов
/ 09 июня 2010

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

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

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;
    }
}

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

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

64 голосов
/ 09 июня 2010

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

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

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, зависит от требований вашего приложения (throw, flush to min / max или просто записывать что угодно). Если вы хотите обнаружить переполнение при длительных операциях, вам не повезло с примитивами, используйте вместо этого BigInteger.


Edit (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 (это будет эффективно проверять знаковый бит). Этот принцип работает точно так же для <em>всех целочисленных примитивных типов , если изменить все объявления в вышеприведенном методе на long, он будет работать долго.

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

/**
 * 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). </p>

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

31 голосов
/ 14 октября 2014

По умолчанию int и long math в Java молча перебираются при переполнении и потере памяти.(Целочисленные операции над другими целочисленными типами выполняются, сначала переводя операнды в int или long, для JLS 4.2.2 .)

Начиная с Java 8, java.lang.Math обеспечивает addExact, subtractExact, multiplyExact, incrementExact, decrementExact и negateExact статические методы как для int, так и для длинных аргументов, которые выполняют именованную операцию, вызывая ArithmeticException при переполнении.(Там нет метода DivideExact - вам придется проверить один особый случай (MIN_VALUE / -1) самостоятельно.)

Начиная с Java 8, java.lang.Math также предоставляет toIntExact для приведения long к int, выбрасывая ArithmeticException, если значение long не помещается в int.Это может быть полезно, например, для вычисления суммы целых с использованием непроверенной длинной математики, а затем использования toIntExact для приведения к int в конце (но будьте осторожны, чтобы не допустить переполнения вашей суммы).

Если выВсе еще используя старую версию Java, Google Guava предоставляет IntMath и LongMath статические методы для проверенного сложения, вычитания, умножения и возведения в степень (исключая переполнение).Эти классы также предоставляют методы для вычисления факториалов и биномиальных коэффициентов, которые возвращают MAX_VALUE при переполнении (что менее удобно проверять).Примитивные служебные классы Гуавы, SignedBytes, UnsignedBytes, Shorts и Ints, предоставляют checkedCast методы для сужения больших типов (бросая IllegalArgumentException в under / overflow, not ArithmeticException), а такжекак saturatingCast методы, которые возвращают MIN_VALUE или MAX_VALUE при переполнении.

31 голосов
/ 19 июля 2012

Java ничего не делает с целочисленным переполнением ни для типов int, ни для длинных примитивов, и игнорирует переполнение положительными и отрицательными целыми числами.

В этом ответе сначала описывается целочисленное переполнение, дается пример того, как это может произойти даже с промежуточными значениями при вычислении выражений, а затем даются ссылки на ресурсы, которые предоставляют подробные методы предотвращения и обнаружения целочисленного переполнения.

Целочисленная арифметика и выражения, приводящие к неожиданному или необнаруженному переполнению, являются распространенной ошибкой программирования.Неожиданное или необнаруженное целочисленное переполнение также является хорошо известной уязвимостью, которую можно использовать, особенно когда она затрагивает объекты массива, стека и списка.

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

Иногда отрицательное переполнение ошибочно называют недостаточным.Underflow - это то, что происходит, когда значение будет ближе к нулю, чем позволяет представление.Недостаток происходит в целочисленной арифметике и ожидается.Потеря целочисленного значения происходит, когда целочисленная оценка будет между -1 и 0 или 0 и 1. То, что будет дробным результатом, усекается до 0. Это нормально и ожидается с целочисленной арифметикой и не считается ошибкой.Однако это может привести к исключению кода.Одним из примеров является исключение «ArithmeticException: / by zero», если результат целочисленного занижения используется в качестве делителя в выражении.

Рассмотрим следующий код:

int bigValue = Integer.MAX_VALUE;
int x = bigValue * 2 / 5;
int y = bigValue / x;

, в результате которого x присваивается 0, а последующая оценка bigValue / x вызывает исключение «ArithmeticException: / by zero» (т.е. делится нанулю), вместо y присваивается значение 2.

Ожидаемый результат для x будет 858,993,458, что меньше максимального значения int в 2,147,483,647.Однако промежуточный результат оценки Integer.MAX_Value * 2 будет равен 4 294 967 294, что превышает максимальное значение int и равно -2 в соответствии с целочисленными представлениями дополнения 2s.Последующая оценка -2 / 5 оценивается в 0, что присваивается х.

Перестановка выражения для вычисления x в выражение, которое при вычислении делит перед умножением следующий код:

int bigValue = Integer.MAX_VALUE;
int x = bigValue / 5 * 2;
int y = bigValue / x;

приводит к тому, что x назначается 858,993,458, а y назначается 2, чтоожидается.

Промежуточный результат от bigValue / 5 равен 429 496 729, что не превышает максимального значения для целого числа.Последующая оценка 429 496 729 * 2 не превышает максимальное значение для int, и ожидаемый результат присваивается x.Оценка для y тогда не делится на ноль.Оценки для x и y работают как положено.

Целочисленные значения Java хранятся как и ведут себя в соответствии с дополнением 2s к знаковым целочисленным представлениям.Когда результирующее значение будет больше или меньше, чем максимальное или минимальное целочисленные значения, вместо этого получается целочисленное значение дополнения 2.В ситуациях, специально не предназначенных для использования поведения дополнения 2s, которое является наиболее обычными целочисленными арифметическими ситуациями, результирующее значение дополнения 2s вызовет логику программирования или ошибку вычисления, как было показано в примере выше.Отличная статья в Википедии описывает двоичные целые числа с комплиментами 2s здесь: Дополнение к двум - Википедия

Существуют методы, позволяющие избежать непреднамеренного целочисленного переполнения.Techinques могут быть классифицированы как использование предварительного тестирования, апкастинга и BigInteger.

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

Upcasting включает использование более крупного примитивного типа для выполнения арифметической операции или выражения, а затем определение, превышает ли результирующее значение максимальное или минимальное значения для целого числа.Даже при использовании восходящего вещания все еще возможно, что значение или некоторое промежуточное значение в операции или выражении будут выходить за пределы максимального или минимального значения для типа восходящего вещания и вызывать переполнение, которое также не будет обнаружено и приведет к неожиданным и нежелательным результатам.Посредством анализа или предварительных условий может быть возможно предотвратить переполнение с помощью апкастинга, когда предотвращение без апскейтинга невозможно или практически невозможно.Если рассматриваемые целые числа уже являются длинными примитивными типами, то в Java примитивные типы невозможны.

Метод BigInteger включает использование BigInteger для арифметической операции или выражения с использованием библиотечных методов, использующих BigInteger.BigInteger не переполняется.Он будет использовать всю доступную память, если это необходимо.Его арифметические методы обычно лишь немного менее эффективны, чем целочисленные операции.Все еще возможно, что результат, использующий BigInteger, может быть выше максимального или минимального значения для целого числа, однако переполнение не произойдет в арифметике, приводящей к результату.Программирование и проектирование все еще должны определить, что делать, если результат BigInteger превышает максимальные или минимальные значения для желаемого примитивного типа результата, например, int или long.

Программа CERT Института разработки программного обеспечения Карнеги-Меллона и Oracle разработали набор стандартов для безопасного программирования на Java.В стандарты включены методы предотвращения и обнаружения целочисленного переполнения.Стандарт опубликован в виде свободно доступного онлайн-ресурса здесь: Стандарт безопасного кодирования Oracle CERT для Java

Раздел стандарта, который описывает и содержит практические примеры методов кодирования для предотвращения или обнаружения целочисленного переполненияздесь: NUM00-J.Обнаружение или предотвращение целочисленного переполнения

Книжная форма и PDF-форма CERT Oracle Secure Coding Standard для Java также доступны.

12 голосов
/ 27 ноября 2011

Я сам как раз столкнулся с этой проблемой, вот мое решение (как для умножения, так и для сложения):

static boolean wouldOverflowOccurwhenMultiplying(int a, int b) {
    // If either a or b are Integer.MIN_VALUE, then multiplying by anything other than 0 or 1 will result in overflow
    if (a == 0 || b == 0) {
        return false;
    } else if (a > 0 && b > 0) { // both positive, non zero
        return a > Integer.MAX_VALUE / b;
    } else if (b < 0 && a < 0) { // both negative, non zero
        return a < Integer.MAX_VALUE / b;
    } else { // exactly one of a,b is negative and one is positive, neither are zero
        if (b > 0) { // this last if statements protects against Integer.MIN_VALUE / -1, which in itself causes overflow.
            return a < Integer.MIN_VALUE / b;
        } else { // a > 0
            return b < Integer.MIN_VALUE / a;
        }
    }
}

boolean wouldOverflowOccurWhenAdding(int a, int b) {
    if (a > 0 && b > 0) {
        return a > Integer.MAX_VALUE - b;
    } else if (a < 0 && b < 0) {
        return a < Integer.MIN_VALUE - b;
    }
    return false;
}

Не стесняйтесь исправлять, если ошиблись или могут быть упрощены. Я провел некоторое тестирование с помощью метода умножения, в основном это были крайние случаи, но это все равно могло быть неправильно.

8 голосов
/ 03 декабря 2012

Существуют библиотеки, которые обеспечивают безопасные арифметические операции, которые проверяют целочисленное переполнение / недополнение. Например, IntMath.checkedAdd (int a, int b) Guava возвращает сумму a и b, если она не переполняется, и выдает ArithmeticException, если a + b переполняется в подписанном int арифметика.

6 голосов
/ 09 июня 2010

Оборачивается.

например:

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
5 голосов
/ 17 июля 2011

Я думаю, что вы должны использовать что-то вроде этого, и это называется Upcasting:

public int multiplyBy2(int x) throws ArithmeticException {
    long result = 2 * (long) x;    
    if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE){
        throw new ArithmeticException("Integer overflow");
    }
    return (int) result;
}

Вы можете прочитать здесь: Обнаружить или предотвратить целочисленное переполнение

Этодовольно надежный источник.

3 голосов
/ 09 июня 2010

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

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

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

1 голос
/ 28 августа 2016

Я думаю, что это должно быть хорошо.

static boolean addWillOverFlow(int a, int b) {
    return (Integer.signum(a) == Integer.signum(b)) && 
            (Integer.signum(a) != Integer.signum(a+b)); 
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...