Saltar al contenido

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

Solución:

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

(puedes sustituir int por long para realizar las mismas comprobaciones para 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, p. Ej. long o 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 utilizar el nuevo Math#addExact() y Math#subtractExact() métodos que arrojarán un ArithmeticException en desbordamiento.

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 esconderlos en un boolean método de utilidad.

Bueno, en lo que respecta a los tipos de enteros primitivos, Java no maneja Over / Underflow en absoluto (para float y double el comportamiento es diferente, se descargará a +/- infinito tal como lo ordena IEEE-754).

Al agregar dos int, no obtendrá ninguna indicación de cuándo se produce un desbordamiento. Un método simple para verificar el desbordamiento es usar el siguiente tipo más grande para realizar la operación y verificar si el resultado aún 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 su aplicación (lanzamiento, enjuague a mínimo / máximo 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 parece que se hace referencia a esta pregunta 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 bandera V.

Es básicamente una expresión booleana que involucra el signo de ambos operandos y 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 sencillo 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, cambiar todas las declaraciones en el método anterior a long hace que funcione durante mucho tiempo.

Para tipos más pequeños, debido a la conversión implícita a int (ver 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 apropiadamente):

/**
 * 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 need for sustraer detección de desbordamiento)


Entonces, ¿cómo / por qué funcionan estas expresiones booleanas? Primero, algo de pensamiento lógico revela que un desbordamiento puede solamente ocurrir si los signos de ambos argumentos son los mismos. Porque, si un argumento es negativo y otro positivo, el resultado (de sumar) debe estar más cerca de cero, o en el caso extremo, un argumento es cero, lo mismo que el otro argumento. Dado que los argumentos por sí mismos hipocresía 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 de que ambos son positivos: agregar dos argumentos que crean una suma mayor que los tipos MAX_VALUE, siempre arrojará 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 (el caso extremo, ambos argumentos son MAX_VALUE). Para un byte (ejemplo) eso significaría 127 + 127 = 254. Al observar las representaciones de bits de todos los valores que pueden resultar de sumar 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 desbordan (0 a 127) tienen el bit 7 (superior, signo) borrado. Eso es exactamente lo que verifica la primera parte (derecha) de la expresión:

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

(~ s & ~ d & r) se convierte en verdad, sólo 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 superior (signo), que es verificado por el <0).

Ahora bien, 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 para el ejemplo de un byte) muestra que para cualquier valor en el rango (-1 a -128) el bit de signo está establecido, mientras que cualquier valor de desbordamiento posible (-129 a -256 ) tiene el bit de signo borrado. Entonces, el signo del resultado revela nuevamente la condición de desbordamiento. Eso es lo que comprueba la mitad izquierda (s & d & ~ r) en el caso de que ambos argumentos (s, d) sean negativos y un resultado positivo. La lógica es en gran parte equivalente al caso positivo; todos los patrones de bits que pueden resultar de la adición de dos valores negativos tendrán el bit de signo borrado si y solo si se produjo un desbordamiento.

De forma predeterminada, las matemáticas int y long de Java envuelven silenciosamente el desbordamiento y el subdesbordamiento. (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.Math proporciona addExact, subtractExact, multiplyExact, incrementExact, decrementExact y negateExact métodos estáticos para argumentos int y long que realizan la operación nombrada, lanzando ArithmeticException en overflow. (No hay método divideExact; tendrá que marcar el caso especial (MIN_VALUE / -1) tú mismo.)

A partir de Java 8, java.lang.Math también proporciona toIntExact para convertir un long a un int, lanzando ArithmeticException si el valor del long no cabe en un int. Esto puede ser útil para, por ejemplo, calcular la suma de entradas usando matemáticas largas sin marcar y luego usar toIntExact para lanzar a int al final (pero tenga cuidado de no dejar que su suma se desborde).

Si todavía está usando una versión anterior de Java, Google Guava proporciona métodos estáticos IntMath y LongMath para la suma, resta, multiplicación y exponenciación comprobadas (arrojar sobre el desbordamiento). Estas clases también proporcionan métodos para calcular factoriales y coeficientes binomiales que devuelven MAX_VALUE en desbordamiento (que es menos conveniente de verificar). Clases de utilidad primitivas de guayaba, SignedBytes, UnsignedBytes, Shorts y Ints, proveer checkedCast métodos para reducir tipos más grandes (lanzando IllegalArgumentException en under / overflow, no ArithmeticException), así como saturatingCast métodos que regresan MIN_VALUE o MAX_VALUE en desbordamiento.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *