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

Как делает Java ручкой целое число потерь значимости и переполнения?

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

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


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

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

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

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

иногда отрицательное переполнение ошибочно называется underflow. Потеря точности происходит, когда значение будет ближе к нулю, чем представление позволяет. Потеря точности происходит в целочисленной арифметике и ожидается. Integer underflow происходит, когда целочисленная оценка будет между -1 и 0 или 0 и 1. Что было бы дробным результатом усекает до 0. Это нормально и ожидалось с целочисленной арифметикой и не считалось ошибкой. Однако это может привести к тому, что код выдаст исключение. Одним из примеров является исключение" ArithmeticException: / by zero", если результат целочисленного 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. Однако промежуточный результат от оценки Integer.MAX_Value * 2, будет 4,294,967,294, что превышает максимальное значение int и равно -2 в соответствии с целочисленными представлениями дополнения 2s. Последующая оценка -2 / 5 оценивается в 0, которая присваивается x.

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

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

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

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

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

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

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

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

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


по умолчанию Java int и long math молча оборачиваются при переполнении и подаче. (Целочисленные операции над другими целочисленными типами выполняются путем первого продвижения операндов в 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.ленг.Math также предоставляет toIntExact для приведения long к int, бросая ArithmeticException если значение long не вписывается в int. Это может быть полезно, например, для вычисления суммы ints, используя непроверенную длинную математику, а затем используя toIntExact бросить в int на end (но будьте осторожны, чтобы не допустить переполнения суммы).

если вы все еще используете более старую версию Java, Google Guava предоставляет IntMath и LongMath статические методы для сложения, вычитания, умножения и возведения в степень (бросая на переполнение). Эти классы также предоставляют методы для вычисления факториалов и биномиальных коэффициентов, которые возвращают MAX_VALUE on overflow (что менее удобно для проверки). Примитивные классы полезности гуавы,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 переполняет подписанный 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)); 
}