¿Cómo puedo comprobar si multiplicar dos números en Java provocará un desbordamiento?

101

Quiero manejar el caso especial en el que la multiplicación de dos números provoca un desbordamiento. El código se parece a esto:

int a = 20;
long b = 30;

// if a or b are big enough, this result will silently overflow
long c = a * b;

Esa es una versión simplificada. En el programa real ay bse obtienen en otro lugar en tiempo de ejecución. Lo que quiero lograr es algo como esto:

long c;
if (a * b will overflow) {
    c = Long.MAX_VALUE;
} else {
    c = a * b;
}

¿Cómo sugieres que codifique mejor esto?

Actualización: ay bsiempre son no negativos en mi escenario.

Steve McLeod
fuente
6
Es una lástima que Java no proporcione acceso indirecto al indicador de desbordamiento de la CPU , como se hace en C # .
Drew Noakes

Respuestas:

92

Java 8 tiene Math.multiplyExact, Math.addExactetc. para ints y long. Éstos arrojan un ArithmeticExceptiondesbordamiento sin control .

bcoughlan
fuente
59

Si ay bambos son positivos, puede usar:

if (a != 0 && b > Long.MAX_VALUE / a) {
    // Overflow
}

Si necesita lidiar con números positivos y negativos, entonces es más complicado:

long maximum = Long.signum(a) == Long.signum(b) ? Long.MAX_VALUE : Long.MIN_VALUE;

if (a != 0 && (b > 0 && b > maximum / a ||
               b < 0 && b < maximum / a))
{
    // Overflow
}

Aquí hay una pequeña mesa que preparé para verificar esto, fingiendo que el desbordamiento ocurre en -10 o +10:

a =  5   b =  2     2 >  10 /  5
a =  2   b =  5     5 >  10 /  2
a = -5   b =  2     2 > -10 / -5
a = -2   b =  5     5 > -10 / -2
a =  5   b = -2    -2 < -10 /  5
a =  2   b = -5    -5 < -10 /  2
a = -5   b = -2    -2 <  10 / -5
a = -2   b = -5    -5 <  10 / -2
John Kugelman
fuente
Debo mencionar que ayb son siempre no negativos en mi escenario, lo que simplificaría un poco este enfoque.
Steve McLeod
3
Creo que esto puede fallar en un caso: a = -1 y b = 10. La expresión máxima / a da como resultado Integer.MIN_VALUE y detecta el desbordamiento cuando no existía ninguno
Kyle
Esto es realmente bueno. Para aquellos que se preguntan, la razón por la que esto funciona es que para integer n, n > xes lo mismo que n > floor(x). Para enteros positivos, la división tiene un piso implícito. (Para números negativos, se redondea hacia arriba)
Thomas Ahle
Para abordar el problema a = -1y b = 10, consulte mi respuesta a continuación.
Jim Pivarski
17

Hay bibliotecas de Java que proporcionan operaciones aritméticas seguras, que verifican el desbordamiento / subdesbordamiento largos. Por ejemplo, LongMath.checkedMultiply de Guava (long a, long b) devuelve el producto de ay b, siempre que no se desborde, y arroja ArithmeticExceptionsi se a * bdesborda en longaritmética con signo .

reprogramador
fuente
4
Esta es la mejor respuesta: use una biblioteca que fue implementada por personas que realmente entienden la aritmética de máquinas en Java y que ha sido probada por muchas personas. ¡No intente escribir el suyo propio ni usar ninguno de los códigos no probados a medias publicados en las otras respuestas!
Rich
@Enerccio - No entiendo tu comentario. ¿Estás diciendo que Guava no funcionará en todos los sistemas? Puedo asegurarles que funcionará en todos los lugares donde lo haga Java. ¿Estás diciendo que reutilizar el código es una mala idea en general? No estoy de acuerdo si es así.
Rich
2
@Rich Estoy diciendo que incluir una biblioteca enorme para que pueda usar una función es una mala idea.
Enerccio
¿Por qué? Si está escribiendo una aplicación grande, por ejemplo para una empresa, entonces un JAR adicional en la ruta de clase no hará ningún daño y Guava tiene un montón de código muy útil. Es mucho mejor reutilizar su código cuidadosamente probado que intentar escribir su propia versión del mismo (que supongo que es lo que recomienda). Si está escribiendo en un entorno donde un JAR adicional será muy costoso (¿dónde? ¿Java incrustado?), Entonces quizás debería extraer solo esta clase de Guava. ¿Copiar una respuesta no probada de StackOverflow es mejor que copiar el código cuidadosamente probado de Guava?
Rich
¿No es un poco exagerado lanzar una excepción para algo que un if-then condicional puede manejar?
Victor
6

En su lugar, podría usar java.math.BigInteger y verificar el tamaño del resultado (no he probado el código):

BigInteger bigC = BigInteger.valueOf(a) * multiply(BigInteger.valueOf(b));
if(bigC.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) {
  c = Long.MAX_VALUE;
} else {
  c = bigC.longValue()
}
Ulf Lindback
fuente
7
Encuentro esta solución bastante lenta
nothrow
Sin embargo, probablemente sea la mejor manera de hacerlo. Supuse que se trataba de una aplicación numérica, por lo que no la recomendé de inmediato, pero esta es probablemente la mejor manera de resolver este problema.
Stefan Kendall
2
No estoy seguro de que pueda usar el operador '>' con BigInteger. debe utilizarse el método compareTo.
Pierre
cambiado a compareTo, y la velocidad puede ser importante o no, depende de las circunstancias en las que se utilizará el código.
Ulf Lindback
5

Utilice logaritmos para comprobar el tamaño del resultado.

Marca de alto rendimiento
fuente
¿quieres decir ceil(log(a)) + ceil(log(b)) > log(Long.MAX)?
Thomas Jung
1
Yo lo revisé. Para valores pequeños es un 20% más rápido que BigInteger y para valores cercanos a MAX es casi lo mismo (5% más rápido). El código de Yossarian es el más rápido (95% y 75% más rápido que BigInteger).
Thomas Jung
Más bien sospecho que puede fallar en algunos casos.
Tom Hawtin - tackline
Recuerde que un registro de enteros está contando efectivamente el número de ceros iniciales, y puede optimizar algunos casos comunes (por ejemplo, si ((a | b) & 0xffffffff00000000L) == 0) sabe que está seguro). Por otro lado, a menos que pueda reducir su optimización a 30/40 ciclos de reloj para los casos más comunes, el método de John Kugelman probablemente funcionará mejor (una división entera es c. 2 bits / ciclo de reloj, según recuerdo).
Neil Coffey
PD Sorr, creo que necesito un poco más en la máscara AND (0xffffffff80000000L) - es un poco tarde, pero entiendes la idea ...
Neil Coffey
4

¿Java tiene algo como int.MaxValue? Si es así, intente

if (b != 0 && Math.abs(a) > Math.abs(Long.MAX_VALUE / b))
{
 // it will overflow
}

editar: visto Long.MAX_VALUE en cuestión

nothrow
fuente
No voté en contra, pero Math.Abs(a)no funciona si aes así Long.MIN_VALUE.
John Kugelman
@John - ayb son> 0. Creo que el enfoque de Yossarian (b! = 0 && a> Long.MAX_VALUE / b) es el mejor.
Thomas Jung
@Thomas, ayb son> = 0, es decir, no negativos.
Steve McLeod
2
Correcto, pero en ese caso no hay necesidad de Abs. Si se permiten números negativos, esto falla al menos en un caso extremo. Eso es todo lo que digo, solo ser quisquilloso.
John Kugelman
en Java debe usar Math.abs, no Math.Abs ​​(¿C # chico?)
dfa
4

Esta es la forma más sencilla que se me ocurre

int a = 20;
long b = 30;
long c = a * b;

if(c / b == a) {
   // Everything fine.....no overflow
} else {
   // Overflow case, because in case of overflow "c/b" can't equal "a"
}
Naren
fuente
3

Robado de jruby

    long result = a * b;
    if (a != 0 && result / a != b) {
       // overflow
    }

ACTUALIZACIÓN: este código es corto y funciona bien; sin embargo, falla para a = -1, b = Long.MIN_VALUE.

Una posible mejora:

long result = a * b;
if( (Math.signum(a) * Math.signum(b) != Math.signum(result)) || 
    (a != 0L && result / a != b)) {
    // overflow
}

Tenga en cuenta que esto detectará algunos desbordamientos sin ninguna división.

Rogerdpack
fuente
Puede usar Long.signum en lugar de Math.signum
aditsu sale porque SE es MAL
3

Como se ha señalado, Java 8 tiene métodos Math.xxxExact que arrojan excepciones al desbordamiento.

Si no está utilizando Java 8 para su proyecto, aún puede "tomar prestadas" sus implementaciones, que son bastante compactas.

Aquí hay algunos enlaces a estas implementaciones en el repositorio de código fuente de JDK, no hay garantía de que sigan siendo válidas, pero en cualquier caso, debería poder descargar la fuente de JDK y ver cómo hacen su magia dentro de la java.lang.Mathclase.

Math.multiplyExact(long, long) http://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/java.base/share/classes/java/lang/Math.java#l925

Math.addExact(long, long) http://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/java.base/share/classes/java/lang/Math.java#l830

etcétera etcétera.

ACTUALIZADO: se cambiaron los enlaces no válidos a sitios web de terceros por enlaces a los repositorios Mercurial de Open JDK.

StFS
fuente
2

No estoy seguro de por qué nadie está buscando una solución como:

if (Long.MAX_VALUE/a > b) {
     // overflows
} 

Elija a para que sea mayor de los dos números.

fastcodejava
fuente
2
No creo que importe si aes más grande o más pequeño.
Thomas Ahle
2

Me gustaría aprovechar la respuesta de John Kugelman sin reemplazarla editándola directamente. Funciona para su caso de prueba ( MIN_VALUE = -10, MAX_VALUE = 10) debido a la simetría de MIN_VALUE == -MAX_VALUE, que no es el caso de los enteros en complemento a dos. En la actualidad, MIN_VALUE == -MAX_VALUE - 1.

scala> (java.lang.Integer.MIN_VALUE, java.lang.Integer.MAX_VALUE)
res0: (Int, Int) = (-2147483648,2147483647)

scala> (java.lang.Long.MIN_VALUE, java.lang.Long.MAX_VALUE)
res1: (Long, Long) = (-9223372036854775808,9223372036854775807)

Cuando se aplica al verdadero MIN_VALUEy MAX_VALUE, la respuesta de John Kugelman produce un caso de desbordamiento cuando a == -1y b ==cualquier otra cosa (punto planteado por primera vez por Kyle). He aquí una forma de solucionarlo:

long maximum = Long.signum(a) == Long.signum(b) ? Long.MAX_VALUE : Long.MIN_VALUE;

if ((a == -1 && b == Long.MIN_VALUE) ||
    (a != -1 && a != 0 && ((b > 0 && b > maximum / a) ||
                           (b < 0 && b < maximum / a))))
{
    // Overflow
}

No es una solución general para cualquier MIN_VALUEe MAX_VALUE, pero es general para Java Longy Integery cualquier valor de ay b.

Jim Pivarski
fuente
Solo pensé que lo complicaría innecesariamente, ya que esta solución solo funciona si MIN_VALUE = -MAX_VALUE - 1, no en cualquier otro caso (incluido su caso de prueba de ejemplo). Tendría que cambiar mucho.
Jim Pivarski
1
Por razones que van más allá de las necesidades del cartel original; para personas como yo que encontraron esta página porque necesitaban una solución para un caso más general (manejo de números negativos y no estrictamente Java 8). De hecho, dado que esta solución no involucra ninguna función más allá de la aritmética y la lógica pura, también podría usarse para C u otros lenguajes.
Jim Pivarski
1

Tal vez:

if(b!= 0 && a * b / b != a) //overflow

No estoy seguro de esta "solución".

Editar: Se agregó b! = 0.

Antes de votar en contra : a * b / b no se optimizará. Este sería un error del compilador. Todavía no veo un caso en el que se pueda enmascarar el error de desbordamiento.

Thomas Jung
fuente
También falla cuando el desbordamiento causa un bucle perfecto.
Stefan Kendall
¿Tiene un ejemplo de lo que quiso decir?
Thomas Jung
Acabo de escribir una pequeña prueba: usar BigInteger es 6 veces más lento que usar este enfoque de división. Por lo tanto, supongo que vale la pena realizar comprobaciones adicionales para los casos de esquina.
mhaller
No sé mucho sobre compiladores de Java, pero a * b / bes probable que una expresión como se optimice solo aen muchos otros contextos.
SingleNegationElimination
TokenMacGuy: no se puede optimizar así si hay peligro de desbordamiento.
Tom Hawtin - tackline
1

tal vez esto te ayude:

/**
 * @throws ArithmeticException on integer overflow
 */
static long multiply(long a, long b) {
    double c = (double) a * b;
    long d = a * b;

    if ((long) c != d) {
        throw new ArithmeticException("int overflow");
    } else {
        return d;
    }
}
dfa
fuente
No ayudará como lo es uno de los operandos long.
Tom Hawtin - tackline
1
¿Incluso probaste esto? Para cualquier valor grande pero no desbordante de a & b, esto fallará debido a errores de redondeo en la versión doble de la multiplicación (intente, por ejemplo, 123456789123L y 74709314L). Si no comprende la aritmética de las máquinas, adivinar una respuesta a este tipo de pregunta precisa es peor que no responder, ya que engañará a la gente.
Rich
-1

c / c ++ (largo * largo):

const int64_ w = (int64_) a * (int64_) b;    
if ((long) (w >> sizeof(long) * 8) != (long) w >> (sizeof(long) * 8 - 1))
    // overflow

java (int * int, lo siento, no encontré int64 en java):

const long w = (long) a * (long) b;    
int bits = 32; // int is 32bits in java    
if ( (int) (w >> bits) != (int) (w >> (bits - 1))) {
   // overflow
}

1.Guarde el resultado en tipo grande (int * int pone el resultado en long, long * long puesto en int64)

2.cmp resultado >> bits y resultado >> (bits - 1)

xuan
fuente