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


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

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

12 182

12 ответов:

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

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

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 by 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 служебный метод.

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

для маленьких типы, из-за неявного преобразования в int (см. JLS для побитовых операций для деталей), вместо проверки

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

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


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

так что произойдет, если оба аргумента имеют тот же знак? Давайте посмотрим на случай, когда оба положительны: добавление двух аргументов, которые создают сумму больше, чем типы 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 битах, но единственный бит, который нас интересует, - это самый верхний (знаковый) бит, который проверяется the

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

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

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

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

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

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

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. Однако промежуточный результат от вычисления целого числа.MAX_Value * 2, будет 4,294,967,294, что превышает максимальное значение int и составляет -2 в соответствии с 2S дополняют целочисленные представления. Последующая оценка -2 / 5 оценивается в 0, который присваивается x.

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

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, что не превышает максимального значения для int. Последующая оценка 429,496,729 * 2 не превышает максимальное значение для int и ожидаемый результат присваивается x. оценка для y тогда не делится на ноль. Оценки для x и y работают, как и ожидалось.

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

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

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

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

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

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

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

Книжная форма и PDF-форма стандарта безопасного кодирования CERT Oracle для Java также доступны.

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

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

начиная с Java 8, java.ленг.Математика также обеспечивает toIntExact чтобы привести long к int, бросая ArithmeticException, если значение long не вписывается в int. Это может быть полезно, например, для вычисления суммы ints, используя непроверенную длинную математику, а затем используя toIntExact для приведения к int в конец (но будьте осторожны, чтобы не допустить переполнения суммы).

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

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

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

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

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

Я думаю, вы должны использовать что-то вроде этого и это называется 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;
}

вы можете прочитать дальше здесь: обнаружение или предотвращение переполнения целых чисел

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

он обтекает.

например:

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

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

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

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

static final int safeAdd(int left, int right)
                 throws ArithmeticException {
  if (right > 0 ? left > Integer.MAX_VALUE - right
                : left < Integer.MIN_VALUE - right) {
    throw new ArithmeticException("Integer overflow");
  }
  return left + right;
}

static final int safeSubtract(int left, int right)
                 throws ArithmeticException {
  if (right > 0 ? left < Integer.MIN_VALUE + right
                : left > Integer.MAX_VALUE + right) {
    throw new ArithmeticException("Integer overflow");
  }
  return left - right;
}

static final int safeMultiply(int left, int right)
                 throws ArithmeticException {
  if (right > 0 ? left > Integer.MAX_VALUE/right
                  || left < Integer.MIN_VALUE/right
                : (right < -1 ? left > Integer.MIN_VALUE/right
                                || left < Integer.MAX_VALUE/right
                              : right == -1
                                && left == Integer.MIN_VALUE) ) {
    throw new ArithmeticException("Integer overflow");
  }
  return left * right;
}

static final int safeDivide(int left, int right)
                 throws ArithmeticException {
  if ((left == Integer.MIN_VALUE) && (right == -1)) {
    throw new ArithmeticException("Integer overflow");
  }
  return left / right;
}

static final int safeNegate(int a) throws ArithmeticException {
  if (a == Integer.MIN_VALUE) {
    throw new ArithmeticException("Integer overflow");
  }
  return -a;
}
static final int safeAbs(int a) throws ArithmeticException {
  if (a == Integer.MIN_VALUE) {
    throw new ArithmeticException("Integer overflow");
  }
  return Math.abs(a);
}

есть один случай, который здесь не указан:

int res = 1;
while (res != 0) {
    res *= 2;

}
System.out.println(res);

будет:

0

этот случай обсуждался здесь: переполнение целого числа приводит к нулю.

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

static boolean addWillOverFlow(int a, int b) {
    return (Integer.signum(a) == Integer.signum(b)) && 
            (Integer.signum(a) != Integer.signum(a+b)); 
}