Soffitto veloce di una divisione intera in C / C ++


262

Dati i valori interi xe y, C e C ++ restituiscono entrambi come quoziente q = x/yil piano dell'equivalente in virgola mobile. Sono invece interessato a un metodo per restituire il soffitto. Ad esempio, ceil(10/5)=2e ceil(11/5)=3.

L'approccio ovvio prevede qualcosa di simile:

q = x / y;
if (q * y < x) ++q;

Ciò richiede un ulteriore confronto e moltiplicazione; e altri metodi che ho visto (usato in effetti) prevedono il casting come a floato double. Esiste un metodo più diretto che evita la moltiplicazione aggiuntiva (o una seconda divisione) e il ramo e che evita anche il casting come numero in virgola mobile?


70
l'istruzione divario ritorna spesso sia quoziente e resto al tempo stesso quindi non c'è bisogno di moltiplicare, solo q = x/y + (x % y != 0);è sufficiente
phuclv

2
@ LưuVĩnhPhúc quel commento dovrebbe essere la risposta accettata, imo.
Andreas Grapentin,

1
@ LưuVĩnhPhúc Seriamente è necessario aggiungerlo come risposta. L'ho usato solo per la mia risposta durante un test di codilità. Ha funzionato come un fascino, anche se non sono sicuro di come funzioni la parte mod della risposta, ma ha fatto il lavoro.
Zachary Kraus,

2
@AndreasGrapentin la risposta in basso di Miguel Figueiredo è stata inviata quasi un anno prima che Lưu Vĩnh Phúc lasciasse il commento sopra. Mentre capisco quanto sia attraente ed elegante la soluzione di Miguel, non sono propenso a cambiare la risposta accettata in questa data avanzata. Entrambi gli approcci rimangono validi. Se ti senti abbastanza forte, ti suggerisco di mostrare il tuo sostegno votando di seguito la risposta di Miguel.
ee il

1
Strano, non ho visto alcuna misurazione sana o analisi delle soluzioni proposte. Parli di velocità quasi all'osso, ma non si discute di architetture, condutture, istruzioni di diramazione e cicli di clock.
Rado

Risposte:


394

Per numeri positivi

unsigned int x, y, q;

Per arrotondare ...

q = (x + y - 1) / y;

o (evitando l'overflow in x + y)

q = 1 + ((x - 1) / y); // if x != 0

6
@bitc: per i numeri negativi, credo che C99 specifichi il round-a-zero, così x/ycome il massimale della divisione. C90 non ha specificato come arrotondare, e non credo nemmeno lo standard C ++ corrente.
David Thornley,


3
Nota: questo potrebbe traboccare. q = ((long long) x + y - 1) / y no. Il mio codice è più lento, quindi, se sai che i tuoi numeri non trabocceranno, dovresti usare la versione di Sparky.
Jørgen Fogh,

1
@bitc: credo che il punto di David fosse che non avresti usato il calcolo sopra se il risultato fosse negativo - useresti soloq = x / y;
caf

12
Il secondo ha un problema in cui x è 0. ceil (0 / y) = 0 ma restituisce 1.
Omry Yadan,

78

Per numeri positivi:

    q = x/y + (x % y != 0);

5
l'istruzione di divisione dell'architettura più comune include anche il resto nel suo risultato, quindi questo ha davvero bisogno di una sola divisione e sarebbe molto veloce
phuclv,

58

La risposta di Sparky è un modo standard per risolvere questo problema, ma come ho scritto anche nel mio commento, corri il rischio di overflow. Questo può essere risolto utilizzando un tipo più ampio, ma cosa succede se si desidera dividere long longs?

La risposta di Nathan Ernst fornisce una soluzione, ma comporta una chiamata di funzione, una dichiarazione di variabile e un condizionale, che non lo rende più breve del codice OP e probabilmente anche più lento, perché è più difficile da ottimizzare.

La mia soluzione è questa:

q = (x % y) ? x / y + 1 : x / y;

Sarà leggermente più veloce del codice OP, perché il modulo e la divisione vengono eseguiti usando le stesse istruzioni sul processore, perché il compilatore può vedere che sono equivalenti. Almeno gcc 4.4.1 esegue questa ottimizzazione con -O2 flag su x86.

In teoria il compilatore potrebbe includere la funzione chiamata nel codice di Nathan Ernst ed emettere la stessa cosa, ma gcc non l'ha fatto quando l'ho provato. Ciò potrebbe essere dovuto al fatto che legherebbe il codice compilato a una singola versione della libreria standard.

Come nota finale, nulla di tutto ciò conta su una macchina moderna, tranne se si è in un ciclo estremamente stretto e tutti i dati sono nei registri o nella cache L1. Altrimenti tutte queste soluzioni saranno ugualmente veloci, ad eccezione di quelle di Nathan Ernst, che potrebbero essere significativamente più lente se la funzione debba essere recuperata dalla memoria principale.


3
C'era un modo più semplice per correggere l'overflow, semplicemente ridurre y / y:q = (x > 0)? 1 + (x - 1)/y: (x / y);
Ben Voigt

-1: questo è un modo inefficiente, poiché scambia un * economico con una% costosa; peggio dell'approccio OP.
Yves Daoust,

2
No non lo fa. Come ho spiegato nella risposta, l'operatore% è gratuito quando si esegue già la divisione.
Jørgen Fogh,

1
Quindi q = x / y + (x % y > 0);è più facile ? :dell'espressione?
Han,

Dipende da cosa intendi per "più facile". Potrebbe essere o non essere più veloce, a seconda di come il compilatore lo traduce. La mia ipotesi sarebbe più lenta ma dovrei misurarlo per essere sicuro.
Jørgen Fogh,

18

È possibile utilizzare la divfunzione in cstdlib per ottenere il quoziente e il resto in una singola chiamata e quindi gestire il soffitto separatamente, come nel seguito

#include <cstdlib>
#include <iostream>

int div_ceil(int numerator, int denominator)
{
        std::div_t res = std::div(numerator, denominator);
        return res.rem ? (res.quot + 1) : res.quot;
}

int main(int, const char**)
{
        std::cout << "10 / 5 = " << div_ceil(10, 5) << std::endl;
        std::cout << "11 / 5 = " << div_ceil(11, 5) << std::endl;

        return 0;
}

12
Come caso interessante del doppio botto, potresti anche return res.quot + !!res.rem;:)
Sam Harwell,

Leopoli non promuove sempre gli argomenti in lunghi? E questo non costa nulla, up-casting o down-casting?
einpoklum,

12

Cosa ne pensi di questo? (richiede y non negativo, quindi non usarlo nel raro caso in cui y sia una variabile senza garanzia di non negatività)

q = (x > 0)? 1 + (x - 1)/y: (x / y);

Ho ridotto y/ya uno, eliminando il termine x + y - 1e con esso ogni possibilità di overflow.

Evito di x - 1concludere quando xè un tipo senza segno e contiene zero.

Per firmato x, negativo e zero si combinano ancora in un singolo caso.

Probabilmente non è un enorme vantaggio su una moderna CPU per uso generico, ma questo sarebbe molto più veloce in un sistema incorporato rispetto a qualsiasi altra risposta corretta.


L'altro restituirà sempre 0, non è necessario calcolare nulla.
Ruud Althuizen,

@Ruud: non vero. Considera x = -45 e y = 4
Ben Voigt,

7

C'è una soluzione sia per positivo che per negativo, xma solo per positivo ycon solo 1 divisione e senza rami:

int ceil(int x, int y) {
    return x / y + (x % y > 0);
}

Nota, se xè positivo, la divisione è verso zero e dovremmo aggiungere 1 se il promemoria non è zero.

Se xè negativo, allora la divisione è verso zero, questo è ciò di cui abbiamo bisogno e non aggiungeremo nulla perché x % ynon è positivo


interessante, perché ci sono casi comuni in cui sei costante
Wolf,

1
mod richiede divisione quindi non è solo 1 divisione qui, ma forse il complier può ottimizzare due divisioni simili in una.
M.kazem Akhgary,

4

Funziona con numeri positivi o negativi:

q = x / y + ((x % y != 0) ? !((x > 0) ^ (y > 0)) : 0);

Se c'è un resto, controlla se xe yhanno lo stesso segno e aggiunge di 1conseguenza.


3

Avrei preferito commentare ma non ho un rappresentante abbastanza alto.

Per quanto ne so, per argomenti positivi e un divisore che è un potere di 2, questo è il modo più veloce (testato in CUDA):

//example y=8
q = (x >> 3) + !!(x & 7);

Solo per argomenti positivi generici, tendo a farlo in questo modo:

q = x/y + !!(x % y);

Sarebbe interessante vedere come si q = x/y + !!(x % y);scontrano q = x/y + (x % y == 0);e le q = (x + y - 1) / y;soluzioni dal punto di vista delle prestazioni nella CUDA contemporanea.
Greg Kramida,


-2

Compilare con O3, il compilatore esegue bene l'ottimizzazione.

q = x / y;
if (x % y)  ++q;
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.