Quiero asegurarme de que una división de enteros siempre se redondea si es necesario. ¿Hay una mejor manera que esta? Hay mucho casting en marcha. :-)
(int)Math.Ceiling((double)myInt1 / myInt2)
Quiero asegurarme de que una división de enteros siempre se redondea si es necesario. ¿Hay una mejor manera que esta? Hay mucho casting en marcha. :-)
(int)Math.Ceiling((double)myInt1 / myInt2)
Respuestas:
ACTUALIZACIÓN: Esta pregunta fue el tema de mi blog en enero de 2013 . Gracias por la gran pregunta!
Obtener aritmética de enteros correcto es difícil. Como se ha demostrado ampliamente hasta el momento, en el momento en que intentas hacer un truco "inteligente", es muy probable que hayas cometido un error. Y cuando se encuentra una falla, cambiar el código para corregir la falla sin considerar si la reparación rompe algo más no es una buena técnica para resolver problemas. Hasta ahora hemos tenido, creo que publicaron cinco soluciones aritméticas enteras incorrectas diferentes para este problema completamente no particularmente difícil.
La forma correcta de abordar los problemas aritméticos de enteros, es decir, la forma en que aumenta la probabilidad de obtener la respuesta correcta la primera vez, es abordar el problema con cuidado, resolverlo paso a paso y utilizar buenos principios de ingeniería al hacer entonces.
Comience leyendo la especificación de lo que está tratando de reemplazar. La especificación para la división de enteros establece claramente:
La división redondea el resultado hacia cero.
El resultado es cero o positivo cuando los dos operandos tienen el mismo signo y cero o negativo cuando los dos operandos tienen signos opuestos.
Si el operando izquierdo es el int representable más pequeño y el operando derecho es –1, se produce un desbordamiento. [...] está definido por la implementación en cuanto a si se lanza [una Excepción Aritmética] o si el desbordamiento no se informa con el valor resultante que es el del operando izquierdo.
Si el valor del operando correcto es cero, se genera una excepción System.DivideByZeroException.
Lo que queremos es una función de división entera que calcule el cociente pero redondea el resultado siempre hacia arriba , no siempre hacia cero .
Entonces escriba una especificación para esa función. Nuestra función int DivRoundUp(int dividend, int divisor)
debe tener un comportamiento definido para cada entrada posible. Ese comportamiento indefinido es profundamente preocupante, así que eliminémoslo. Diremos que nuestra operación tiene esta especificación:
la operación arroja si el divisor es cero
la operación arroja si el dividendo es int.minval y el divisor es -1
Si no hay resto - la división es 'par' - entonces el valor de retorno es el cociente integral
De lo contrario, devuelve el entero más pequeño que es mayor que el cociente, es decir, siempre se redondea.
Ahora tenemos una especificación, por lo que sabemos que podemos llegar a un diseño comprobable . Supongamos que agregamos un criterio de diseño adicional para que el problema se resuelva únicamente con la aritmética de enteros, en lugar de calcular el cociente como un doble, ya que la solución "doble" se ha rechazado explícitamente en la declaración del problema.
Entonces, ¿qué debemos calcular? Claramente, para cumplir con nuestras especificaciones mientras permanecemos únicamente en aritmética de enteros, necesitamos conocer tres hechos. Primero, ¿cuál fue el cociente entero? Segundo, ¿fue la división libre de residuos? Y tercero, si no, ¿se calculó el cociente entero redondeando hacia arriba o hacia abajo?
Ahora que tenemos una especificación y un diseño, podemos comenzar a escribir código.
public static int DivRoundUp(int dividend, int divisor)
{
if (divisor == 0 ) throw ...
if (divisor == -1 && dividend == Int32.MinValue) throw ...
int roundedTowardsZeroQuotient = dividend / divisor;
bool dividedEvenly = (dividend % divisor) == 0;
if (dividedEvenly)
return roundedTowardsZeroQuotient;
// At this point we know that divisor was not zero
// (because we would have thrown) and we know that
// dividend was not zero (because there would have been no remainder)
// Therefore both are non-zero. Either they are of the same sign,
// or opposite signs. If they're of opposite sign then we rounded
// UP towards zero so we're done. If they're of the same sign then
// we rounded DOWN towards zero, so we need to add one.
bool wasRoundedDown = ((divisor > 0) == (dividend > 0));
if (wasRoundedDown)
return roundedTowardsZeroQuotient + 1;
else
return roundedTowardsZeroQuotient;
}
¿Es esto inteligente? ¿No Hermosa? No. corto? No. ¿Correcto según la especificación? Creo que sí, pero no lo he probado completamente. Sin embargo, se ve bastante bien.
Somos profesionales aquí; use buenas prácticas de ingeniería. Investigue sus herramientas, especifique el comportamiento deseado, considere primero los casos de error y escriba el código para enfatizar su corrección obvia. Y cuando encuentre un error, considere si su algoritmo es profundamente defectuoso para comenzar antes de comenzar a intercambiar aleatoriamente las direcciones de las comparaciones y romper cosas que ya funcionan.
Todas las respuestas aquí hasta ahora parecen demasiado complicadas.
En C # y Java, para dividendos y divisores positivos, simplemente debe hacer:
( dividend + divisor - 1 ) / divisor
((13-1)%3)+1)
da 1 como resultado. Tomar el tipo correcto de división, 1+(dividend - 1)/divisor
da el mismo resultado que la respuesta para el dividendo positivo y el divisor. Además, no hay problemas de desbordamiento, por muy artificiales que puedan ser.
Para enteros con signo:
int div = a / b;
if (((a ^ b) >= 0) && (a % b != 0))
div++;
Para enteros sin signo:
int div = a / b;
if (a % b != 0)
div++;
La división de enteros ' /
' se define para redondear hacia cero (7.7.2 de la especificación), pero queremos redondear. Esto significa que las respuestas negativas ya están redondeadas correctamente, pero las respuestas positivas deben ajustarse.
Las respuestas positivas que no son cero son fáciles de detectar, pero la respuesta cero es un poco más complicada, ya que puede ser el redondeo de un valor negativo o el redondeo de uno positivo.
La apuesta más segura es detectar cuándo la respuesta debe ser positiva al verificar que los signos de ambos enteros sean idénticos. El operador entero xor ' ^
' en los dos valores dará como resultado un bit de signo 0 cuando este sea el caso, lo que significa un resultado no negativo, por lo que la comprobación (a ^ b) >= 0
determina que el resultado debería haber sido positivo antes del redondeo. También tenga en cuenta que para enteros sin signo, cada respuesta es obviamente positiva, por lo que esta verificación se puede omitir.
La única comprobación restante es si se ha producido algún redondeo, para lo cual a % b != 0
hará el trabajo.
La aritmética (entera o no) no es tan simple como parece. Pensando cuidadosamente requiere en todo momento.
Además, aunque mi respuesta final tal vez no sea tan 'simple' o 'obvia' o tal vez incluso 'rápida' como las respuestas de coma flotante, tiene una cualidad redentora muy fuerte para mí; Ahora he razonado a través de la respuesta, así que estoy realmente seguro de que es correcta (hasta que alguien más inteligente me diga lo contrario, una mirada furtiva en la dirección de Eric ).
Para tener la misma sensación de certeza sobre la respuesta de punto flotante, tendría que pensar más (y posiblemente más complicado) si hay alguna condición bajo la cual la precisión del punto flotante podría interferir, y si Math.Ceiling
tal vez algo indeseable en las entradas 'correctas'.
Reemplazar (tenga en cuenta que reemplacé el segundo myInt1
con myInt2
, asumiendo que eso era lo que quiso decir):
(int)Math.Ceiling((double)myInt1 / myInt2)
con:
(myInt1 - 1 + myInt2) / myInt2
La única advertencia es que si se myInt1 - 1 + myInt2
desborda el tipo entero que está utilizando, es posible que no obtenga lo que espera.
Razón por la que esto está mal : -1000000 y 3999 deberían dar -250, esto da -249
EDITAR:
Teniendo en cuenta que esto tiene el mismo error que la otra solución entera para myInt1
valores negativos , podría ser más fácil hacer algo como:
int rem;
int div = Math.DivRem(myInt1, myInt2, out rem);
if (rem > 0)
div++;
Eso debería dar el resultado correcto al div
usar solo operaciones enteras.
Razón por la que esto está mal : -1 y -5 deberían dar 1, esto da 0
EDITAR (una vez más, con sentimiento):
el operador de división se redondea hacia cero; para resultados negativos esto es exactamente correcto, por lo que solo los resultados no negativos necesitan un ajuste. También teniendo en cuenta que DivRem
solo hace ay /
a de %
todos modos, omita la llamada (y comencemos con la comparación fácil para evitar el cálculo del módulo cuando no sea necesario):
int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
div++;
Razón por la que esto está mal : -1 y 5 deberían dar 0, esto da 1
(En mi propia defensa del último intento, nunca debería haber intentado una respuesta razonada mientras mi mente me decía que llegaba 2 horas tarde para dormir)
Posibilidad perfecta de usar un método de extensión:
public static class Int32Methods
{
public static int DivideByAndRoundUp(this int number, int divideBy)
{
return (int)Math.Ceiling((float)number / (float)divideBy);
}
}
Esto también hace que su código sea súper legible:
int result = myInt.DivideByAndRoundUp(4);
Podrías escribir un ayudante.
static int DivideRoundUp(int p1, int p2) {
return (int)Math.Ceiling((double)p1 / p2);
}
Podrías usar algo como lo siguiente.
a / b + ((Math.Sign(a) * Math.Sign(b) > 0) && (a % b != 0)) ? 1 : 0)
Algunas de las respuestas anteriores usan flotadores, esto es ineficiente y realmente no es necesario. Para entradas sin signo, esta es una respuesta eficiente para int1 / int2:
(int1 == 0) ? 0 : (int1 - 1) / int2 + 1;
Para entradas firmadas, esto no será correcto
El problema con todas las soluciones aquí es que necesitan un yeso o que tienen un problema numérico. Lanzar para flotar o duplicar siempre es una opción, pero podemos hacerlo mejor.
Cuando usas el código de la respuesta de @jerryjvl
int div = myInt1 / myInt2;
if ((div >= 0) && (myInt1 % myInt2 != 0))
div++;
Hay un error de redondeo. 1/5 se redondearía hacia arriba, porque 1% 5! = 0. Pero esto está mal, porque el redondeo solo ocurrirá si reemplaza el 1 con un 3, por lo que el resultado es 0.6. Necesitamos encontrar una manera de redondear cuando el cálculo nos da un valor mayor o igual a 0.5. El resultado del operador de módulo en el ejemplo superior tiene un rango de 0 a myInt2-1. El redondeo solo ocurrirá si el resto es mayor al 50% del divisor. Entonces el código ajustado se ve así:
int div = myInt1 / myInt2;
if (myInt1 % myInt2 >= myInt2 / 2)
div++;
Por supuesto, también tenemos un problema de redondeo en myInt2 / 2, pero este resultado le dará una mejor solución de redondeo que las otras en este sitio.