¿Cómo redondear el resultado de la división entera?

335

Estoy pensando en particular en cómo mostrar los controles de paginación, cuando utilizo un lenguaje como C # o Java.

Si tengo x elementos que quiero mostrar en fragmentos de y por página, ¿cuántas páginas se necesitarán?

Ian Nelson
fuente
1
¿Me estoy perdiendo de algo? y / x + 1 funciona muy bien (siempre que sepa que el operador / siempre se redondea).
rikkit
51
@rikkit: si y y x son iguales, y / x + 1 es demasiado alto.
Ian Nelson
1
Para cualquiera que acabe de encontrar esto, esta respuesta a una pregunta fraudulenta evita la conversión innecesaria al doble y evita las preocupaciones de desbordamiento, además de proporcionar una explicación clara.
ZX9
2
@IanNelson más generalmente si xes divisible por y, y/x + 1sería demasiado alto.
Ohad Schneider
1
@ ZX9 No, no evita problemas de desbordamiento. Es exactamente la misma solución que Ian Nelson publicó aquí.
user247702

Respuestas:

478

Encontró una solución elegante:

int pageCount = (records + recordsPerPage - 1) / recordsPerPage;

Fuente: Conversión de números, Roland Backhouse, 2001

Ian Nelson
fuente
12
-1 debido al error de desbordamiento señalado por Brandon DuRette
finnw
30
El Sr. Obvious dice: Recuerde asegurarse de que recordsPerPage no sea cero
Adam Gent
77
Buen trabajo, no puedo creer que C # no tenga un techo entero.
gosukiwi
2
Sí, aquí estoy a mediados de 2017 tropezando con esta gran respuesta después de probar varios enfoques mucho más complejos.
Mifo
1
Para lenguajes con un operador de división euclidiana adecuado como Python, sería un enfoque aún más simple pageCount = -((-records) // recordsPerPage).
supercat
194

La conversión a coma flotante y viceversa parece una gran pérdida de tiempo a nivel de CPU.

La solución de Ian Nelson:

int pageCount = (records + recordsPerPage - 1) / recordsPerPage;

Se puede simplificar para:

int pageCount = (records - 1) / recordsPerPage + 1;

AFAICS, esto no tiene el error de desbordamiento que Brandon DuRette señaló, y debido a que solo lo usa una vez, no necesita almacenar los registrosPerPage especialmente si proviene de una función costosa para obtener el valor de un archivo de configuración o alguna cosa.

Es decir, esto podría ser ineficiente, si config.fetch_value utiliza una búsqueda en la base de datos o algo así:

int pageCount = (records + config.fetch_value('records per page') - 1) / config.fetch_value('records per page');

Esto crea una variable que realmente no necesita, que probablemente tenga implicaciones (menores) de memoria y simplemente escriba demasiado:

int recordsPerPage = config.fetch_value('records per page')
int pageCount = (records + recordsPerPage - 1) / recordsPerPage;

Esta es toda una línea y solo obtiene los datos una vez:

int pageCount = (records - 1) / config.fetch_value('records per page') + 1;
rjmunro
fuente
55
+1, el problema de cero registros que aún devuelve 1 pageCount es realmente útil, ya que todavía quisiera 1 página, mostrando el marcador de posición / fila falsa de "no hay registros que coincidan con sus criterios", ayuda a evitar cualquier problema de "conteo de 0 páginas" en lo que sea control de paginación que usa.
Timothy Walters
27
Tenga en cuenta que las dos soluciones no devuelven la misma pageCount para cero registros. Esta versión simplificada devolverá 1 pageCount para cero registros, mientras que la versión de Roland Backhouse devuelve 0 pageCount. Está bien si eso es lo que deseas, pero las dos ecuaciones no son equivalentes cuando las realiza C # / Java stylee integer division.
Ian Nelson
10
pequeña edición para mayor claridad para las personas que lo escanean y los bodmas faltantes al cambiar a la simplificación de la solución Nelson (¡como hice la primera vez!), la simplificación con corchetes es ... int pageCount = ((records - 1) / recordsPerPage) + 1;
Dave Heywood
1
Debe agregar paréntesis a la versión simplificada para que no dependa de un orden específico de operaciones. es decir, ((records - 1) / recordsPerPage) + 1.
Martin
1
@Ian, esta respuesta no siempre devuelve 1. Se puede devolver 0 si su recordsPerPage es "1" y hay 0 registros: -1 / 1 + 1 = 0. Si bien eso no es algo muy común, es importante tenerlo en cuenta si permite que los usuarios ajusten el tamaño de la página. Por lo tanto, no permita que los usuarios tengan un tamaño de página de 1, verifique el tamaño de la página o ambos (probablemente preferible para evitar un comportamiento inesperado).
Michael
81

Para C #, la solución es convertir los valores a un doble (como Math.Ceiling toma un doble):

int nPages = (int)Math.Ceiling((double)nItems / (double)nItemsPerPage);

En java deberías hacer lo mismo con Math.ceil ().

Huppie
fuente
44
¿Por qué esta respuesta está tan abajo cuando el operador solicita C # explícitamente?
felickz 01 de
2
También debe convertir la salida en intporque Math.Ceilingdevuelve a doubleo decimal, según los tipos de entrada.
DanM7
15
porque es extremadamente ineficiente
Zar Shardan
66
Puede ser ineficiente, pero es extremadamente fácil de entender. Dado que el cálculo de un recuento de páginas generalmente se realiza una vez por solicitud, cualquier pérdida de rendimiento no sería medible.
Jared Kells
1
Es apenas más legible que este "(dividendo + (divisor - 1)) / divisor;" También es lento y requiere la biblioteca de matemáticas.
rueda el
68

Esto debería darte lo que quieres. Definitivamente querrá x elementos divididos por y elementos por página, el problema es cuando aparecen números desiguales, por lo que si hay una página parcial también queremos agregar una página.

int x = number_of_items;
int y = items_per_page;

// with out library
int pages = x/y + (x % y > 0 ? 1 : 0)

// with library
int pages = (int)Math.Ceiling((double)x / (double)y);
Nick Berardi
fuente
55
x / y + !! (x% y) evita la bifurcación de los lenguajes tipo C. Las probabilidades son buenas, sin embargo, su compilador lo está haciendo de todos modos.
Rhys Ulerich
2
+1 por no desbordarse como las respuestas anteriores ... aunque convertir ints en dobles solo para Math.ceiling y luego nuevamente es una mala idea en el código sensible al rendimiento.
Rueda dentada
3
@RhysUlerich que no funciona en C # (no puede convertir directamente un int en un bool). La solución de rjmunro es la única forma de evitar ramificaciones, creo.
mancha
18

La solución matemática entera que proporcionó Ian es buena, pero sufre un error de desbordamiento de enteros. Suponiendo que las variables son todas int, la solución podría reescribirse para usar las longmatemáticas y evitar el error:

int pageCount = (-1L + records + recordsPerPage) / recordsPerPage;

Si recordses a long, el error permanece. La solución de módulo no tiene el error.

Brandon DuRette
fuente
44
No creo que realmente vaya a encontrar este error en el escenario presentado. 2 ^ 31 registros es bastante para tener que pasar página.
rjmunro
8
@rjmunro, ejemplo del mundo real aquí
finnw
@finnw: AFAICS, no hay un ejemplo del mundo real en esa página, solo un informe de alguien más encontrando el error en un escenario teórico.
rjmunro
55
Sí, estaba siendo pedante al señalar el error. Muchos errores pueden existir a perpetuidad sin causar ningún problema. Un error de la misma forma existió en la implementación de binarySearch por parte del JDK durante unos nueve años, antes de que alguien lo informara ( googleresearch.blogspot.com/2006/06/… ). Supongo que la pregunta es, independientemente de lo poco probable que sea que encuentre este error, ¿por qué no solucionarlo por adelantado?
Brandon DuRette
44
Además, debe tenerse en cuenta que lo importante no es solo la cantidad de elementos paginados, sino también el tamaño de la página. Entonces, si está construyendo una biblioteca y alguien elige no paginar pasando 2 ^ 31-1 (Integer.MAX_VALUE) como el tamaño de la página, entonces se activa el error.
Brandon DuRette
7

Una variante de la respuesta de Nick Berardi que evita una rama:

int q = records / recordsPerPage, r = records % recordsPerPage;
int pageCount = q - (-r >> (Integer.SIZE - 1));

Nota: (-r >> (Integer.SIZE - 1))consiste en el bit de signo de r, repetido 32 veces (gracias a la extensión de signo del >>operador). Esto se evalúa a 0 si res cero o negativo, -1 si res positivo. Entonces restarlo qtiene el efecto de sumar 1 si records % recordsPerPage > 0.

finnw
fuente
4

Para los registros == 0, la solución de rjmunro da 1. La solución correcta es 0. Dicho esto, si sabe que los registros> 0 (y estoy seguro de que todos hemos asumido recordsPerPage> 0), la solución de rjmunro da los resultados correctos y no tiene ninguno de los problemas de desbordamiento.

int pageCount = 0;
if (records > 0)
{
    pageCount = (((records - 1) / recordsPerPage) + 1);
}
// no else required

Todas las soluciones matemáticas enteras serán más eficientes que cualquiera de las soluciones de coma flotante.

Miguel
fuente
Es poco probable que este método sea un cuello de botella en el rendimiento. Y si es así, también debe considerar el costo de la sucursal.
finnw
4

Necesita un método de extensión:

    public static int DivideUp(this int dividend, int divisor)
    {
        return (dividend + (divisor - 1)) / divisor;
    }

No hay controles aquí (desbordamiento, DivideByZeroetc.), siéntase libre de agregar si lo desea. Por cierto, para aquellos preocupados por la sobrecarga de invocación de métodos, el compilador puede incluir funciones simples como esta, de modo que no creo que sea de lo que se trate. Salud.

PD: también puede ser útil tener en cuenta esto (obtiene el resto):

    int remainder; 
    int result = Math.DivRem(dividend, divisor, out remainder);
Nicholas Petersen
fuente
1
Esto es incorrecto. Por ejemplo: DivideUp(4, -2)devuelve 0 (debería ser -2). Solo es correcto para enteros no negativos que no están claros en la respuesta o en la interfaz de la función.
Thash
66
Thash, ¿por qué no haces algo útil como agregar el pequeño cheque adicional y luego si el número es negativo, en lugar de rechazar mi respuesta y hacer incorrectamente la declaración general: "Esto es incorrecto", cuando en realidad es solo una ventaja caso. Ya dejé en claro que primero debe hacer otras comprobaciones: "No hay comprobaciones aquí (desbordamiento, DivideByZero, etc.), siéntase libre de agregar si lo desea ".
Nicholas Petersen
1
La pregunta mencionaba "Estoy pensando en particular en cómo mostrar los controles de paginación ", de modo que los números negativos hubieran estado fuera de límites de todos modos. Nuevamente, solo haga algo útil y sugiera una verificación adicional si lo desea, es un hombre de esfuerzo de equipo.
Nicholas Petersen
No quise ser grosero y lo siento si lo tomas de esa manera. La pregunta era "Cómo redondear el resultado de la división de enteros". El autor mencionó la paginación, pero otras personas pueden tener diferentes necesidades. Creo que sería mejor si su función reflejara de alguna manera que no funciona para enteros negativos, ya que no está claro desde la interfaz (por ejemplo, un nombre diferente o tipos de argumento). Para trabajar con enteros negativos, puede, por ejemplo, tomar un valor absoluto del dividendo y el divisor y multiplicar el resultado por su signo.
Thash
2

Otra alternativa es usar la función mod () (o '%'). Si hay un resto distinto de cero, incremente el resultado entero de la división.

Jarod Elliott
fuente
1

Hago lo siguiente, maneja cualquier desbordamiento:

var totalPages = totalResults.IsDivisble(recordsperpage) ? totalResults/(recordsperpage) : totalResults/(recordsperpage) + 1;

Y use esta extensión si hay 0 resultados:

public static bool IsDivisble(this int x, int n)
{
           return (x%n) == 0;
}

Además, para el número de página actual (no se le preguntó pero podría ser útil):

var currentPage = (int) Math.Ceiling(recordsperpage/(double) recordsperpage) + 1;
Sam Jones
fuente
0

Alternativa para eliminar la ramificación en la prueba de cero:

int pageCount = (records + recordsPerPage - 1) / recordsPerPage * (records != 0);

No estoy seguro de si esto funcionará en C #, debería hacerlo en C / C ++.

flujo
fuente
-1

Un método genérico, cuyo resultado puede iterar puede ser de interés:

public static Object[][] chunk(Object[] src, int chunkSize) {

    int overflow = src.length%chunkSize;
    int numChunks = (src.length/chunkSize) + (overflow>0?1:0);
    Object[][] dest = new Object[numChunks][];      
    for (int i=0; i<numChunks; i++) {
        dest[i] = new Object[ (i<numChunks-1 || overflow==0) ? chunkSize : overflow ];
        System.arraycopy(src, i*chunkSize, dest[i], 0, dest[i].length); 
    }
    return dest;
}
Jeremy Hadfied
fuente
La guayaba tiene un método similar ( Lists.partition(List, int)) e irónicamente, el size()método del resultado List(a partir de r09) sufre del error de desbordamiento mencionado en la respuesta de Brandon DuRette .
finnw
-2

Tenía una necesidad similar donde necesitaba convertir minutos a horas y minutos. Lo que usé fue:

int hrs = 0; int mins = 0;

float tm = totalmins;

if ( tm > 60 ) ( hrs = (int) (tm / 60);

mins = (int) (tm - (hrs * 60));

System.out.println("Total time in Hours & Minutes = " + hrs + ":" + mins);
Richard Parsons
fuente
-2

Lo siguiente debería redondear mejor que las soluciones anteriores, pero a expensas del rendimiento (debido al cálculo de coma flotante de 0.5 * rctDenominator):

uint64_t integerDivide( const uint64_t& rctNumerator, const uint64_t& rctDenominator )
{
  // Ensure .5 upwards is rounded up (otherwise integer division just truncates - ie gives no remainder)
  return (rctDenominator == 0) ? 0 : (rctNumerator + (int)(0.5*rctDenominator)) / rctDenominator;
}
Jim Watson
fuente
-4

Querrá hacer una división de punto flotante y luego usar la función de techo para redondear el valor al siguiente entero.

Kibbee
fuente