Come arrotondare il risultato della divisione intera?


335

Sto pensando in particolare a come visualizzare i controlli di impaginazione, quando si utilizza un linguaggio come C # o Java.

Se ho x elementi che voglio visualizzare in blocchi di y per pagina, quante pagine saranno necessarie?


1
Mi sto perdendo qualcosa? y / x + 1 funziona alla grande (purché tu sappia che l'operatore / arrotonda sempre per difetto).
rikkit,

51
@rikkit - se y e x sono uguali, y / x + 1 è uno troppo alto.
Ian Nelson,

1
Per chiunque abbia appena trovato questo, questa risposta a una domanda doppia evita inutili conversioni in doppio ed evita preoccupazioni di overflow oltre a fornire una spiegazione chiara.
ZX9,

2
@IanNelson più in generale se xè divisibile per y, y/x + 1sarebbe uno troppo alto.
Ohad Schneider,

1
@ ZX9 No, non evita preoccupazioni di overflow. È esattamente la stessa soluzione di Ian Nelson pubblicata qui.
user247702

Risposte:


478

Trovato una soluzione elegante:

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

Fonte: conversione numerica, Roland Backhouse, 2001


12
-1 a causa del bug di overflow segnalato da Brandon DuRette
finnw

30
Il signor Ovvio dice: Ricordati di assicurarti che recordsPerPage non sia zero
Adam Gent,

7
Ottimo lavoro, non riesco a credere che C # non abbia un numero intero.
gosukiwi,

2
Sì, eccomi qui a metà 2017, incappando in questa grande risposta dopo aver provato diversi approcci molto più complessi.
Mifo,

1
Per le lingue con un vero operatore della divisione euclidea come Python, sarebbe un approccio ancora più semplice pageCount = -((-records) // recordsPerPage).
supercat

194

La conversione in virgola mobile e ritorno sembra una grande perdita di tempo a livello di CPU.

La soluzione di Ian Nelson:

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

Può essere semplificato per:

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

AFAICS, questo non ha il bug di overflow che Brandon DuRette ha sottolineato e poiché lo utilizza solo una volta, non è necessario archiviare recordsPerPage specialmente se proviene da una funzione costosa per recuperare il valore da un file di configurazione o qualcosa.

Vale a dire questo potrebbe essere inefficiente, se config.fetch_value utilizzava una ricerca nel database o qualcosa del genere:

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

Questo crea una variabile di cui non hai davvero bisogno, che probabilmente ha implicazioni di memoria (minori) ed è semplicemente troppa battitura:

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

Questa è tutta una riga e recupera i dati una sola volta:

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

5
+1, il problema di zero record che restituiscono ancora 1 pageCount è effettivamente utile, dal momento che vorrei ancora 1 pagina, mostrando il segnaposto / riga falsa di "nessun record corrisponde ai tuoi criteri", aiuta a evitare qualsiasi problema di "0 page count" in qualunque controllo di impaginazione che usi.
Timothy Walters,

27
Tenere presente che le due soluzioni non restituiscono lo stesso pageCount per zero record. Questa versione semplificata restituirà 1 pageCount per zero record, mentre la versione di Roland Backhouse restituisce 0 pageCount. Va bene se è quello che desideri, ma le due equazioni non sono equivalenti quando eseguite dalla divisione intera in stile C # / Java.
Ian Nelson,

10
minuscola modifica per chiarezza per le persone che la scansionano e mancano bodmas quando si passa alla semplificazione dalla soluzione Nelson (come ho fatto la prima volta!), la semplificazione con parentesi è ... int pageCount = ((records - 1) / recordsPerPage) + 1;
Dave Heywood,

1
È necessario aggiungere parentesi alla versione semplificata in modo che non si basi su un ordine specifico di operazioni. cioè, ((records - 1) / recordsPerPage) + 1.
Martin

1
@Ian, questa risposta non restituisce sempre 1. Si può restituire 0 se il recordsPerPage è "1" e ci sono 0 record: -1 / 1 + 1 = 0. Anche se questo non è un evento molto comune, è importante tenere a mente se si consente agli utenti di regolare le dimensioni della pagina. Quindi, o non consentire agli utenti di avere una dimensione di pagina 1, fare un controllo sulla dimensione della pagina o entrambi (probabilmente preferibile per evitare comportamenti imprevisti).
Michael,

81

Per C # la soluzione è quella di lanciare i valori in un doppio (come Math.Ceiling prende un doppio):

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

In java dovresti fare lo stesso con Math.ceil ().


4
perché questa risposta è così in basso quando l'op richiede esplicitamente C #!
Felickz,

2
È inoltre necessario eseguire il cast dell'output intperché Math.Ceilingrestituisce a doubleo decimal, a seconda dei tipi di input.
DanM7,

15
perché è estremamente inefficiente
Zar Shardan,

6
Potrebbe essere inefficiente ma è estremamente facile da capire. Dato che il calcolo del conteggio delle pagine viene di solito eseguito una volta per richiesta, qualsiasi perdita di rendimento non sarebbe misurabile.
Jared Kells,

1
È appena più leggibile di questo "(dividendo + (divisore - 1)) / divisore;" inoltre è lento e richiede la libreria matematica.
tira il

68

Questo dovrebbe darti quello che vuoi. Avrai sicuramente bisogno di x elementi divisi per y articoli per pagina, il problema è quando si presentano numeri irregolari, quindi se c'è una pagina parziale vogliamo anche aggiungere una pagina.

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

5
x / y + !! (x% y) evita il ramo per linguaggi simili a C. Le probabilità sono buone, tuttavia, il tuo compilatore lo sta facendo comunque.
Rhys Ulerich,

2
+1 per non traboccare come le risposte sopra ... anche se convertire ints in double solo per Math.ceiling e poi di nuovo è una cattiva idea nel codice sensibile alle prestazioni.
Ruota dentata

3
@RhysUlerich che non funziona in c # (impossibile convertire direttamente un int in un bool). Penso che la soluzione di rjmunro sia l'unico modo per evitare la ramificazione.
smead

18

La soluzione matematica integra fornita da Ian è buona, ma soffre di un bug di overflow dell'intero. Supponendo che le variabili siano tutte int, la soluzione potrebbe essere riscritta per usare la longmatematica ed evitare il bug:

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

Se recordsè un long, il bug rimane. La soluzione del modulo non ha il bug.


4
Non penso che realisticamente colpirai questo bug nello scenario presentato. 2 ^ 31 registrazioni sono molte da sfogliare.
rjmunro,


@finnw: AFAICS, non c'è un esempio del mondo reale su quella pagina, solo un rapporto di qualcun altro che trova il bug in uno scenario teorico.
rjmunro,

5
Sì, ero pedante nel sottolineare il bug. Molti bug possono esistere per sempre senza mai causare problemi. Un bug della stessa forma esisteva nell'implementazione di binarySearch da parte di JDK per circa nove anni, prima che qualcuno lo segnalasse ( googleresearch.blogspot.com/2006/06/… ). Immagino che la domanda sia, indipendentemente da quanto improbabile si verifichi questo errore, perché non risolverlo in anticipo?
Brandon DuRette,

4
Inoltre, va notato che non è solo il numero di elementi paginati che conta, è anche la dimensione della pagina. Quindi, se stai costruendo una libreria e qualcuno sceglie di non eseguire la pagina passando 2 ^ 31-1 (Integer.MAX_VALUE) come dimensione della pagina, il bug viene attivato.
Brandon DuRette,

7

Una variante della risposta di Nick Berardi che evita un ramo:

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

Nota: è (-r >> (Integer.SIZE - 1))costituito dal bit di segno di r, ripetuto 32 volte (grazie all'estensione di segno >>dell'operatore). rViene valutato 0 se è zero o negativo, -1 se rè positivo. Quindi sottraendolo da qha l'effetto di aggiungere 1 se records % recordsPerPage > 0.


4

Per records == 0, la soluzione di rjmunro fornisce 1. La soluzione corretta è 0. Detto questo, se sai che records> 0 (e sono sicuro che tutti abbiamo assunto recordsPerPage> 0), allora la soluzione rjmunro fornisce risultati corretti e non presenta alcun problema di overflow.

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

Tutte le soluzioni matematiche intere saranno più efficienti di qualsiasi soluzione a virgola mobile.


È improbabile che questo metodo rappresenti un collo di bottiglia nelle prestazioni. E se lo è, dovresti anche considerare il costo della filiale.
finnw,

4

Necessario un metodo di estensione:

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

Nessun controllo qui (overflow, DivideByZeroecc.), Sentiti libero di aggiungere se vuoi. A proposito, per coloro che sono preoccupati per il sovraccarico di invocazione del metodo, funzioni semplici come questa potrebbero essere delineate dal compilatore, quindi non credo che sia questo il motivo. Saluti.

PS potresti trovare utile essere a conoscenza anche di questo (ottiene il resto):

    int remainder; 
    int result = Math.DivRem(dividend, divisor, out remainder);

1
Questo non è corretto Ad esempio: DivideUp(4, -2)restituisce 0 (dovrebbe essere -2). È corretto solo per numeri interi non negativi che non sono chiari dalla risposta o dall'interfaccia della funzione.
Thash

6
Ecco, perché non fai qualcosa di utile come aggiungere il piccolo controllo extra, quindi se il numero è negativo, invece di votare la mia risposta verso il basso e fare in modo errato l'affermazione generale: "Questo non è corretto", quando in realtà è solo un vantaggio Astuccio. Ho già chiarito che dovresti prima fare altri controlli: "Nessun controllo qui (overflow, DivideByZero, ecc.), Sentiti libero di aggiungere se vuoi ."
Nicholas Petersen,

1
La domanda menzionava "Sto pensando in particolare a come visualizzare i controlli di impaginazione ", quindi i numeri negativi sarebbero stati fuori dai limiti comunque. Ancora una volta, fai qualcosa di utile e suggerisci un controllo aggiuntivo se vuoi, è un uomo di sforzo di squadra.
Nicholas Petersen,

Non intendevo essere scortese e mi dispiace se la prendi così. La domanda era "Come arrotondare il risultato della divisione di numeri interi". L'autore ha menzionato l'impaginazione ma altre persone potrebbero avere esigenze diverse. Penso che sarebbe meglio se la tua funzione riflettesse in qualche modo che non funziona per numeri interi negativi poiché non è chiara dall'interfaccia (ad esempio un nome diverso o tipi di argomenti). Per lavorare con numeri interi negativi, puoi ad esempio prendere un valore assoluto del dividendo e del divisore e moltiplicare il risultato per il suo segno.
Thash,

2

Un'altra alternativa è usare la funzione mod () (o '%'). Se è presente un resto diverso da zero, incrementare il risultato intero della divisione.


1

Faccio quanto segue, gestisce eventuali overflow:

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

E usa questa estensione per se ci sono 0 risultati:

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

Inoltre, per il numero di pagina corrente (non è stato chiesto ma potrebbe essere utile):

var currentPage = (int) Math.Ceiling(recordsperpage/(double) recordsperpage) + 1;

0

Alternativa per rimuovere la ramificazione nei test per zero:

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

Non sono sicuro che funzionerà in C #, dovrebbe farlo in C / C ++.


-1

Un metodo generico, di cui è possibile ripetere il risultato può essere interessante:

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

Guava ha un metodo simile ( Lists.partition(List, int)) e ironia della sorte il size()metodo del risultante List(a partire da r09) soffre del bug di overflow menzionato nella risposta di Brandon DuRette .
finnw,

-2

Avevo un bisogno simile in cui dovevo convertire i minuti in ore e minuti. Quello che ho usato è stato:

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

-2

Quanto segue dovrebbe arrotondare meglio delle soluzioni di cui sopra, ma a scapito delle prestazioni (a causa del calcolo in virgola mobile di 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;
}

-4

Ti consigliamo di eseguire la divisione in virgola mobile, quindi utilizzare la funzione soffitto, per arrotondare il valore al numero intero successivo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.