¿Cómo maneja Java los desbordamientos y desbordamientos de enteros y cómo lo verificaría?


226

¿Cómo maneja Java los desbordamientos y desbordamientos de enteros?

A partir de eso, ¿cómo verificaría / probaría que esto está ocurriendo?


28
Es una pena que Java no proporcione acceso indirecto al indicador de desbordamiento de la CPU , como se hace en C # .
Drew Noakes

@DrewNoakes Y es una pena que C # no tenga un valor predeterminado checkedhasta donde yo sé. No veo que se use mucho, y escribir checked { code; }es casi tanto trabajo como llamar a un método.
Maarten Bodewes

2
@MaartenBodewes, puede configurarlo como predeterminado durante la compilación de un ensamblador. csc /checked ...o establezca la propiedad en el panel de propiedades del proyecto en Visual Studio.
Drew Noakes el

@DrewNoakes OK, interesante. Sin embargo, es un poco extraño que sea una configuración fuera del código. En general, me gustaría tener el mismo comportamiento de un programa independientemente de tales configuraciones (posiblemente excepto las afirmaciones).
Maarten Bodewes

@MaartenBodewes, creo que el razonamiento es que hay una sobrecarga de rendimiento no trivial para la verificación. Entonces, tal vez lo habilitaría en compilaciones de depuración, luego lo deshabilitaría en compilaciones de lanzamiento, al igual que muchos otros tipos de afirmaciones.
Drew Noakes

Respuestas:


217

Si se desborda, vuelve al valor mínimo y continúa desde allí. Si se desborda, vuelve al valor máximo y continúa desde allí.

Puede verificar eso de antemano de la siguiente manera:

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

(puede sustituir intpor longrealizar las mismas comprobaciones long)

Si cree que esto puede ocurrir con más frecuencia, considere usar un tipo de datos u objeto que pueda almacenar valores más grandes, por ejemplo, longo tal vez java.math.BigInteger. El último no se desborda, prácticamente, la memoria JVM disponible es el límite.


Si ya está en Java8, puede hacer uso de los nuevos Math#addExact()y Math#subtractExact()métodos que generarán un ArithmeticExceptiondesbordamiento.

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

El código fuente se puede encontrar aquí y aquí. respectivamente.

Por supuesto, también puede usarlos de inmediato en lugar de ocultarlos en un booleanmétodo de utilidad.


13
@dhblah, digamos que los valores máximos y mínimos que Java permite para un int son +100, -100, respectivamente. Si estuviera agregando uno a un entero Java, el proceso se vería así cuando se desbordara. 98, 99, 100, -100, -99, -98, .... ¿Eso tiene más sentido?
Austin A

66
Recomiendo usar los métodos de utilidad en lugar de usar el código de inmediato. Los métodos de utilidad son intrínsecos y serán reemplazados por código específico de la máquina. Una prueba rápida mostró que Math.addExact era un 30% más rápido que un método copiado (Java 1.8.0_40).
TilmannZ

1
@ErikE Math#addExactes la sintaxis que normalmente se usa al escribir javadocs, aunque normalmente se convertiría Math.addExact, a veces la otra forma simplemente se
queda

1
If it underflows, it goes back to the maximum value and continues from there.- parece haber confundido el desbordamiento con el desbordamiento negativo. El desbordamiento en enteros ocurre todo el tiempo (cuando el resultado es una fracción).
nave

1
en.wikipedia.org/wiki/Arithmetic_underflow dice que Underflow es una condición en un programa de computadora donde el resultado de un cálculo es un número de valor absoluto más pequeño que la computadora realmente puede representar en la memoria de su CPU. Por lo tanto, el flujo inferior no se aplica a los enteros Java. @BalusC
Jingguo Yao

66

Bueno, en lo que respecta a los tipos enteros primitivos, Java no maneja Over / Underflow en absoluto (para flotante y el doble del comportamiento es diferente, se vaciará a +/- infinito tal como lo exige IEEE-754).

Al agregar dos int, no obtendrá ninguna indicación cuando ocurra un desbordamiento. Un método simple para verificar el desbordamiento es usar el siguiente tipo más grande para realmente realizar la operación y verificar si el resultado todavía está dentro del rango para el tipo de fuente:

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

Lo que haría en lugar de las cláusulas de lanzamiento depende de los requisitos de sus aplicaciones (lanzar, vaciar a min / max o simplemente registrar lo que sea). Si desea detectar el desbordamiento en operaciones largas, no tiene suerte con las primitivas, use BigInteger en su lugar.


Editar (2014-05-21): Dado que esta pregunta parece ser mencionada con bastante frecuencia y tuve que resolver el mismo problema yo mismo, es bastante fácil evaluar la condición de desbordamiento por el mismo método que una CPU calcularía su indicador V.

Básicamente es una expresión booleana que involucra el signo de ambos operandos, así como el resultado:

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

En java es más simple aplicar la expresión (en el if) a los 32 bits completos, y verificar el resultado usando <0 (esto probará efectivamente el bit de signo). El principio funciona exactamente igual para todos los tipos primitivos enteros , cambiando todas las declaraciones en el método anterior a largo lo hace funcionar por mucho tiempo.

Para tipos más pequeños, debido a la conversión implícita a int (vea el JLS para operaciones bit a bit para más detalles), en lugar de verificar <0, la verificación necesita enmascarar el bit de signo explícitamente (0x8000 para operandos cortos, 0x80 para operandos de bytes, ajustar conversiones) y declaración de parámetros adecuadamente):

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

(Tenga en cuenta que el ejemplo anterior usa la expresión necesidad de detección de desbordamiento de resta )


Entonces, ¿cómo / por qué funcionan estas expresiones booleanas? Primero, un pensamiento lógico revela que un desbordamiento solo puede ocurrir si los signos de ambos argumentos son los mismos. Porque, si un argumento es negativo y uno positivo, el resultado (de sumar) debe estar más cerca de cero, o en el caso extremo un argumento es cero, igual que el otro argumento. Dado que los argumentos por sí mismos no pueden crear una condición de desbordamiento, su suma tampoco puede crear un desbordamiento.

Entonces, ¿qué sucede si ambos argumentos tienen el mismo signo? Echemos un vistazo al caso, ambos son positivos: agregar dos argumentos que crean una suma mayor que los tipos MAX_VALUE, siempre generará un valor negativo, por lo que se produce un desbordamiento si arg1 + arg2> MAX_VALUE. Ahora el valor máximo que podría resultar sería MAX_VALUE + MAX_VALUE (en el caso extremo, ambos argumentos son MAX_VALUE). Para un byte (ejemplo) que significaría 127 + 127 = 254. Mirando las representaciones de bits de todos los valores que pueden resultar de agregar dos valores positivos, uno encuentra que aquellos que se desbordan (128 a 254) tienen el bit 7 establecido, mientras que todos los que no se desborden (0 a 127) tienen el bit 7 (superior, signo) borrado. Eso es exactamente lo que comprueba la primera parte (derecha) de la expresión:

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

(~ s & ~ d & r) se vuelve verdadero, solo si ambos operandos (s, d) son positivos y el resultado (r) es negativo (la expresión funciona en los 32 bits, pero el único bit que nos interesa es el bit más alto (signo), que se compara con <0).

Ahora, si ambos argumentos son negativos, su suma nunca puede estar más cerca de cero que cualquiera de los argumentos, la suma debe estar más cerca de menos infinito. El valor más extremo que podemos producir es MIN_VALUE + MIN_VALUE, que (nuevamente por ejemplo de byte) muestra que para cualquier valor dentro del rango (-1 a -128) se establece el bit de signo, mientras que cualquier valor de desbordamiento posible (-129 a -256 ) tiene el bit de signo borrado. Entonces, el signo del resultado nuevamente revela la condición de desbordamiento. Eso es lo que la mitad izquierda (s & d & ~ r) verifica para el caso en que ambos argumentos (s, d) son negativos y un resultado que es positivo. La lógica es en gran medida equivalente al caso positivo; todos los patrones de bits que pueden resultar de agregar dos valores negativos tendrán el bit de signo borrado si y solo si ocurrió un flujo inferior.


1
Puede verificarlo con operadores bit a bit, también betterlogic.com/roger/2011/05/…
rogerdpack

1
Esto funcionará, pero supongo que tendrá un impacto desagradable en el rendimiento.
chessofnerd

33

Por defecto, las matemáticas int y largas de Java se ajustan silenciosamente al desbordamiento y al desbordamiento. (Las operaciones de enteros en otros tipos de enteros se realizan promoviendo primero los operandos a int o long, según JLS 4.2.2 .)

A partir de Java 8, java.lang.Mathproporciona addExact, subtractExact, multiplyExact, incrementExact, decrementExacty negateExactlos métodos estáticos, tanto para int y largas discusiones que llevan a cabo la operación de llamada, lanzando ArithmeticException en caso de desbordamiento. (No existe un método divideExact: deberá verificar el caso especial ( MIN_VALUE / -1) usted mismo).

A partir de Java 8, java.lang.Math también proporciona toIntExactconvertir un largo a un int, lanzando ArithmeticException si el valor del largo no cabe en un int. Esto puede ser útil para, por ejemplo, calcular la suma de entradas usando cálculos matemáticos largos no verificados, luego usar toIntExactpara convertir a int al final (pero tenga cuidado de no dejar que su suma se desborde).

Si todavía está utilizando una versión anterior de Java, Google Guava proporciona métodos estáticos IntMath y LongMath para sumar, restar, multiplicar y exponenciar marcados (lanzar desbordamientos). Estas clases también proporcionan métodos para calcular factores factoriales y coeficientes binomiales que retornan MAX_VALUEen el desbordamiento (que es menos conveniente verificar). Clases de utilidad primitivas de guayaba, SignedBytes, UnsignedBytes, Shortsy Ints, proporcionan checkedCastmétodos para la reducción de los tipos más grandes (que lanzan IllegalArgumentException en bajo / desbordamiento, no ArithmeticException), así como saturatingCastmétodos que devuelven MIN_VALUEo MAX_VALUEen desbordamiento.


32

Java no hace nada con desbordamiento de enteros para los tipos primitivos int o long e ignora el desbordamiento con enteros positivos y negativos.

Esta respuesta describe primero el desbordamiento de enteros, da un ejemplo de cómo puede suceder, incluso con valores intermedios en la evaluación de expresiones, y luego proporciona enlaces a recursos que proporcionan técnicas detalladas para prevenir y detectar el desbordamiento de enteros.

La aritmética entera y las expresiones que resultan en un desbordamiento inesperado o no detectado son un error de programación común. El desbordamiento de enteros inesperado o no detectado también es un problema de seguridad explotable bien conocido, especialmente porque afecta a los objetos de matriz, pila y lista.

El desbordamiento puede ocurrir en una dirección positiva o negativa donde el valor positivo o negativo estaría más allá de los valores máximos o mínimos para el tipo primitivo en cuestión. El desbordamiento puede ocurrir en un valor intermedio durante la evaluación de la expresión u operación y afectar el resultado de una expresión u operación donde se esperaría que el valor final esté dentro del rango.

A veces, el desbordamiento negativo se denomina erróneamente desbordamiento. Underflow es lo que sucede cuando un valor estaría más cerca de cero de lo que permite la representación. El subflujo se produce en aritmética de enteros y se espera. El desbordamiento de enteros ocurre cuando una evaluación de enteros estaría entre -1 y 0 o 0 y 1. Lo que sería un resultado fraccionario se trunca a 0. Esto es normal y esperado con aritmética de enteros y no se considera un error. Sin embargo, puede provocar que el código arroje una excepción. Un ejemplo es una excepción "ArithmeticException: / by zero" si el resultado del desbordamiento de enteros se usa como divisor en una expresión.

Considere el siguiente código:

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

lo que da como resultado que a x se le asigne 0 y la evaluación posterior de bigValue / x arroja una excepción, "ArithmeticException: / by zero" (es decir, dividir por cero), en lugar de y se le asigna el valor 2.

El resultado esperado para x sería 858,993,458, que es menor que el valor int máximo de 2,147,483,647. Sin embargo, el resultado intermedio de evaluar Integer.MAX_Value * 2, sería 4,294,967,294, que excede el valor int máximo y es -2 de acuerdo con las representaciones enteras del complemento 2s. La evaluación posterior de -2 / 5 evalúa a 0, que se asigna a x.

Reorganizando la expresión para calcular x a una expresión que, cuando se evalúa, divide antes de multiplicar el siguiente código:

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

da como resultado que x se asigne 858,993,458 e y se asigne 2, lo cual se espera.

El resultado intermedio de bigValue / 5 es 429,496,729 que no excede el valor máximo para un int. La evaluación posterior de 429,496,729 * 2 no excede el valor máximo para un int y el resultado esperado se asigna a x. La evaluación para y luego no se divide por cero. Las evaluaciones para x e y funcionan como se esperaba.

Los valores enteros de Java se almacenan como y se comportan de acuerdo con las representaciones enteras con signo del complemento 2s. Cuando un valor resultante sería mayor o menor que los valores enteros máximos o mínimos, en su lugar se obtiene un valor entero complementario de 2. En situaciones no diseñadas expresamente para utilizar el comportamiento del complemento 2s, que es la aritmética de enteros más común, el valor del complemento 2s resultante causará una lógica de programación o un error de cálculo como se mostró en el ejemplo anterior. Un excelente artículo de Wikipedia describe los enteros binarios complementarios 2s aquí: complemento de dos - Wikipedia

Existen técnicas para evitar el desbordamiento entero involuntario. Las Techinques pueden clasificarse según el uso de pruebas de condición previa, transmisión y BigInteger.

La prueba previa a la condición comprende examinar los valores que entran en una operación o expresión aritmética para garantizar que no se produzca un desbordamiento con esos valores. La programación y el diseño deberán crear pruebas que garanticen que los valores de entrada no causen desbordamiento y luego determinar qué hacer si se producen valores de entrada que causen desbordamiento.

La conversión ascendente comprende el uso de un tipo primitivo más grande para realizar la operación o expresión aritmética y luego determinar si el valor resultante está más allá de los valores máximos o mínimos para un número entero. Incluso con la conversión ascendente, aún es posible que el valor o algún valor intermedio en una operación o expresión supere los valores máximos o mínimos para el tipo de conversión ascendente y provoque un desbordamiento, que tampoco se detectará y causará resultados inesperados y no deseados. A través del análisis o las condiciones previas, puede ser posible evitar el desbordamiento con la transmisión cuando la prevención sin transmisión no es posible o práctica. Si los enteros en cuestión ya son tipos primitivos largos, entonces la conversión ascendente no es posible con tipos primitivos en Java.

La técnica BigInteger comprende el uso de BigInteger para la operación o expresión aritmética utilizando métodos de biblioteca que usan BigInteger. BigInteger no se desborda. Utilizará toda la memoria disponible, si es necesario. Sus métodos aritméticos son normalmente solo un poco menos eficientes que las operaciones de enteros. Todavía es posible que un resultado usando BigInteger pueda estar más allá de los valores máximos o mínimos para un entero, sin embargo, no se producirá un desbordamiento en la aritmética que conduce al resultado. La programación y el diseño aún deberán determinar qué hacer si un resultado de BigInteger está más allá de los valores máximos o mínimos para el tipo de resultado primitivo deseado, por ejemplo, int o long.

El programa CERT del Instituto de Ingeniería de Software Carnegie Mellon y Oracle han creado un conjunto de estándares para la programación segura de Java. Las normas incluyen técnicas para prevenir y detectar el desbordamiento de enteros. El estándar se publica como un recurso en línea de libre acceso aquí: El estándar de codificación segura Oracle CERT para Java

La sección del estándar que describe y contiene ejemplos prácticos de técnicas de codificación para prevenir o detectar el desbordamiento de enteros está aquí: NUM00-J. Detectar o prevenir el desbordamiento de enteros

También están disponibles el formulario de libro y el formulario PDF de CERT Oracle Secure Coding Standard para Java.


esta es la mejor respuesta aquí, ya que establece claramente lo que es corriente inferior (la respuesta aceptada no lo hace) y también enumera las técnicas para tratar con desbordamiento / subdesbordamiento
nave

12

Habiendo tenido este problema, esta es mi solución (tanto para la multiplicación como para la suma):

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

siéntase libre de corregir si está mal o si se puede simplificar. He realizado algunas pruebas con el método de multiplicación, en su mayoría casos extremos, pero aún podría estar equivocado.


Es probable que la división sea lenta en relación con la multiplicación. Porque int*int, creo que simplemente echar un vistazo longy ver si el resultado encaja intsería el enfoque más rápido. Porque long*long, si uno normaliza los operandos para que sean positivos, se puede dividir cada uno en mitades superior e inferior de 32 bits, promover cada mitad a largo (¡tenga cuidado con las extensiones de signos!) Y luego calcular dos productos parciales [una de las mitades superiores debería ser cero].
supercat

Cuando dices "Durante mucho tiempo * tiempo, si uno normaliza los operandos para que sean positivos ...", ¿cómo harías para normalizar Long.MIN_VALUE?
fragorl

Estos métodos pueden ser interesantes si es necesario probar si algo se desborda antes de realizar el cálculo. Podría ser bueno para probar, por ejemplo, la entrada del usuario que se utiliza para tales cálculos, en lugar de detectar la excepción cuando sucede.
Maarten Bodewes

8

Hay bibliotecas que proporcionan operaciones aritméticas seguras, que verifican el desbordamiento / desbordamiento de enteros. Por ejemplo, IntMath.checkedAdd (int a, int b) de Guava devuelve la suma de ay b, siempre que no se desborde, y arroja ArithmeticExceptionsi se a + bdesborda en intaritmética con signo .


Sí, es una buena idea, a menos que seas Java 8 o superior, en cuyo caso la Mathclase contiene un código similar.
Maarten Bodewes

6

Se envuelve.

p.ej:

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

huellas dactilares

-2147483648
2147483647

¡Bien! Y ahora, ¿puedes responder cómo detectarlo en un cálculo complejo?
Aubin

5

Creo que deberías usar algo como esto y se llama 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;
}

Puede leer más aquí: Detecte o evite el desbordamiento de enteros

Es una fuente bastante confiable.


3

No hace nada: el desbordamiento / desbordamiento simplemente sucede.

Un "-1" que es el resultado de un cálculo que se desbordó no es diferente del "-1" que resultó de cualquier otra información. Por lo tanto, no puede saber a través de algún estado o inspeccionando solo un valor si se desbordó.

Pero puede ser inteligente con sus cálculos para evitar el desbordamiento, si es importante, o al menos saber cuándo sucederá. Cual es tu situacion


No es realmente una situación, solo algo en lo que tengo curiosidad y me hizo pensar. Si necesita un ejemplo de caso de uso, aquí hay uno: tengo una clase con su propia variable interna llamada 'segundos'. Tengo dos métodos que toman un número entero como parámetro y aumentarán o disminuirán (respectivamente) los 'segundos' en esa cantidad. ¿Cómo probaría la unidad que se está produciendo un desbordamiento / desbordamiento y cómo evitaría que ocurriera?
KushalP

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

2
Esto maneja las pruebas. Aunque no explica cómo Java maneja los desbordamientos y desbordamientos de enteros (agregue algo de texto para explicar).
Spencer Wieczorek

1

Creo que esto debería estar bien.

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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.