Jak zaokrąglić wynik dzielenia liczb całkowitych?


335

Mam na myśli w szczególności sposób wyświetlania elementów sterujących paginacją podczas używania języka, takiego jak C # lub Java.

Jeśli mam x elementów, które chcę wyświetlić w kawałkach y na stronę, ile stron będzie potrzebnych?


1
Czy coś brakuje? y / x + 1 działa świetnie (pod warunkiem, że operator / zawsze zaokrągla w dół).
rikkit,

51
@rikkit - jeśli y i x są równe, y / x + 1 jest o jeden za wysokie.
Ian Nelson,

1
Dla każdego, kto właśnie to znajdzie, ta odpowiedź na podwójne pytanie pozwala uniknąć niepotrzebnej konwersji na podwójne i pozwala uniknąć obaw związanych z przepełnieniem, a także zapewnia jasne wyjaśnienie.
ZX9,

2
@IanNelson bardziej ogólnie, jeśli xmożna go podzielić y, y/x + 1byłby o jeden za wysoki.
Ohad Schneider

1
@ ZX9 Nie, nie pozwala uniknąć obaw związanych z przepełnieniem. To dokładnie to samo rozwiązanie, co Ian Nelson opublikował tutaj.
user247702,

Odpowiedzi:


478

Znaleziono eleganckie rozwiązanie:

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

Źródło: Konwersja liczb, Roland Backhouse, 2001


12
-1 z powodu błędu przepełnienia wskazanego przez Brandona DuRette
finnw

30
Pan Oczywisty mówi: Pamiętaj, aby upewnić się, że pPerPage nie ma wartości zero
Adam Gent

7
Dobra robota, nie mogę uwierzyć, że C # nie ma sufitu liczb całkowitych.
gosukiwi

2
Tak, tutaj w połowie 2017 roku natknąłem się na tę świetną odpowiedź po wypróbowaniu kilku znacznie bardziej złożonych podejść.
Mifo

1
W przypadku języków z odpowiednim operatorem podziału euklidesowego, takim jak Python, podejście byłoby jeszcze prostsze pageCount = -((-records) // recordsPerPage).
supercat

194

Konwersja na zmiennoprzecinkowe i wstecz wydaje się ogromną stratą czasu na poziomie procesora.

Rozwiązanie Iana Nelsona:

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

Można uprościć:

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

AFAICS, nie ma w nim błędu przepełnienia, na który zwrócił uwagę Brandon DuRette, a ponieważ używa go tylko raz, nie trzeba przechowywać recordPerPage specjalnie, jeśli pochodzi z drogiej funkcji pobierania wartości z pliku konfiguracyjnego lub coś.

Może to być nieefektywne, jeśli config.fetch_value użył wyszukiwania bazy danych lub czegoś takiego:

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

To tworzy zmienną, której tak naprawdę nie potrzebujesz, która prawdopodobnie ma (niewielkie) implikacje pamięci i po prostu zbyt dużo pisania:

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

To wszystko jest jedna linia i dane są pobierane tylko raz:

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

5
+1, problem zerowych rekordów wciąż zwracających 1 stronę Liczba jest faktycznie przydatna, ponieważ nadal chciałbym 1 stronę, pokazując symbol zastępczy / fałszywy wiersz „brak rekordów spełniających twoje kryteria”, pomaga uniknąć problemów z „liczbą stron 0” kontrola paginacji, której używasz.
Timothy Walters

27
Należy pamiętać, że dwa rozwiązania nie zwracają tego samego pageCount dla zerowych rekordów. Ta uproszczona wersja zwróci 1 pageCount dla zerowych rekordów, natomiast wersja Roland Backhouse zwraca 0 pageCount. Dobrze, jeśli tego chcesz, ale dwa równania nie są równoważne, gdy są wykonywane przez dzielenie całkowite w stylu C # / Java.
Ian Nelson

10
drobna edycja dla jasności dla osób skanujących ją i brakujących ciał przy przejściu na uproszczenie z rozwiązania Nelsona (tak jak zrobiłem za pierwszym razem!), uproszczenie za pomocą nawiasów to ... int pageCount = ((records - 1) / recordsPerPage) + 1;
dave heywood

1
Powinieneś dodać nawias do uproszczonej wersji, aby nie opierała się na określonej kolejności operacji. tj. ((rekordy - 1) / recordsPerPage) + 1.
Martin

1
@Ian, ta odpowiedź nie zawsze zwraca 1. Może zwraca 0 jeśli recordsPerPage jest „1” i istnieją 0 rekordy: -1 / 1 + 1 = 0. Chociaż nie jest to zbyt częste zdarzenie, należy pamiętać, że pozwalasz użytkownikom dostosowywać rozmiar strony. Więc albo nie zezwalaj użytkownikom na rozmiar strony 1, sprawdź rozmiar strony, albo jedno i drugie (prawdopodobnie lepiej, aby uniknąć nieoczekiwanego zachowania).
Michael

81

W przypadku C # rozwiązaniem jest rzutowanie wartości na wartość podwójną (ponieważ Math.Ceiling przyjmuje wartość podwójną):

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

W java powinieneś zrobić to samo z Math.ceil ().


4
dlaczego ta odpowiedź jest tak daleko w dół, gdy operacja wyraźnie prosi o C #!
felickz

2
Musisz także rzutować dane wyjściowe na, intponieważ Math.Ceilingzwraca a doublelub decimal, w zależności od typów danych wejściowych.
DanM7

15
ponieważ jest niezwykle nieefektywny
Zar Shardan

6
Może to być nieefektywne, ale bardzo łatwe do zrozumienia. Biorąc pod uwagę obliczanie liczby stron zwykle wykonuje się raz na żądanie, żadna utrata wydajności nie byłaby mierzalna.
Jared Kells,

1
Jest ledwo bardziej czytelny niż ten „(dywidenda + (dzielnik - 1)) / dzielnik;” także jest wolny i wymaga biblioteki matematycznej.
rzuca

68

To powinno dać ci to, czego chcesz. Na pewno chcesz x elementów podzielonych przez y elementów na stronę, problem pojawia się, gdy pojawiają się nierówne liczby, więc jeśli jest częściowa strona, chcemy również dodać jedną stronę.

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) unika gałęzi dla języków podobnych do C. Kursy są dobre, jednak kompilator i tak to robi.
Rhys Ulerich

2
+1 za to, że nie przepełnia się jak powyższe odpowiedzi ... chociaż konwersja ints do dublerów tylko dla Math.ceiling, a potem z powrotem jest złym pomysłem w kodzie wrażliwym na wydajność.
Koło zębate

3
@RhysUlerich, który nie działa w języku C # (nie można bezpośrednio przekonwertować int na bool). Myślę, że rozwiązanie rjmunro jest jedynym sposobem na uniknięcie rozgałęzień.
smead

18

Rozwiązanie matematyczne liczb całkowitych, które dostarczył Ian, jest dobre, ale ma błąd przepełnienia liczb całkowitych. Zakładając, że zmienne są wszystkie int, rozwiązanie można przepisać, aby użyć longmatematyki i uniknąć błędu:

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

Jeśli recordsjest a long, błąd pozostaje. Rozwiązanie modułu nie ma błędu.


4
Nie wydaje mi się, żebyś realistycznie napotkał ten błąd w przedstawionym scenariuszu. 2 ^ 31 rekordów to sporo do przejrzenia.
rjmunro


@finnw: AFAICS, na tej stronie nie ma rzeczywistego przykładu, tylko raport o tym, że ktoś znalazł błąd w scenariuszu teoretycznym.
rjmunro

5
Tak, pedantycznie wskazywałam na błąd. Wiele błędów może istnieć wiecznie, nie powodując żadnych problemów. Błąd o tej samej formie istniał w implementacji binarySearch przez JDK przez około dziewięć lat, zanim ktoś to zgłosił ( googleresearch.blogspot.com/2006/06/… ). Myślę, że pytanie brzmi: niezależnie od tego, jak mało prawdopodobne jest napotkanie tego błędu, dlaczego nie naprawić go z góry?
Brandon DuRette

4
Należy również zauważyć, że liczy się nie tylko liczba stron, które są stronicowane, ale także rozmiar strony. Tak więc, jeśli budujesz bibliotekę i ktoś zdecyduje się nie stronicować, przekazując 2 ^ 31-1 (Integer.MAX_VALUE) jako rozmiar strony, wówczas błąd jest wywoływany.
Brandon DuRette

7

Wariant odpowiedzi Nicka Berardi, który omija gałąź:

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

Uwaga: (-r >> (Integer.SIZE - 1))składa się z bitu znaku r, powtórzonego 32 razy (dzięki rozszerzeniu znaku >>operatora). Wartość ta rwynosi 0, jeśli jest zero lub ujemna, a -1, jeśli rjest dodatnia. Odjęcie go qpowoduje dodanie 1 if records % recordsPerPage > 0.


4

Dla rekordów == 0 rozwiązanie rjmunro daje 1. Prawidłowe rozwiązanie wynosi 0. To powiedziawszy, jeśli wiesz, że rekordy> 0 (i jestem pewien, że wszyscy założyliśmy recordPerPage> 0), wtedy rozwiązanie rjmunro daje prawidłowe wyniki i nie ma żadnych problemów z przepełnieniem.

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

Wszystkie rozwiązania matematyczne na liczbach całkowitych będą bardziej wydajne niż jakiekolwiek rozwiązania zmiennoprzecinkowe.


Ta metoda prawdopodobnie nie będzie wąskim gardłem w zakresie wydajności. A jeśli tak, należy również wziąć pod uwagę koszt oddziału.
finnw

4

Potrzebuje metody rozszerzenia:

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

Brak kontroli tutaj (przepełnienie, DivideByZero itp.), Możesz dodać, jeśli chcesz. Nawiasem mówiąc, dla tych, którzy martwią się narzutami wywołanymi metodami, kompilator może i tak wprowadzić takie proste funkcje, więc nie sądzę, że o to chodzi. Twoje zdrowie.

PS może ci się przydać świadomość tego (dostaje resztę):

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

1
To jest niepoprawne. Na przykład: DivideUp(4, -2)zwraca 0 (powinno być -2). Jest to poprawne tylko dla liczb całkowitych nieujemnych, które nie są jednoznaczne z odpowiedzi lub interfejsu funkcji.
Thash,

6
Pomyśl, dlaczego nie zrobisz czegoś pożytecznego, jak dodanie małego dodatkowego sprawdzania, a następnie, jeśli liczba jest ujemna, zamiast głosować moją odpowiedź w dół i niepoprawnie wypowiadając ogólne stwierdzenie: „To jest niepoprawne”, podczas gdy w rzeczywistości jest to tylko przewaga walizka. Wyjaśniłem już, że powinieneś najpierw wykonać inne kontrole: „Żadnych kontroli tutaj (przepełnienie, DivideByZero itp.), Możesz dodać, jeśli chcesz ”.
Nicholas Petersen

1
Pytanie brzmiało: „Myślę w szczególności o tym, jak wyświetlić elementy sterujące paginacją ”, więc liczby ujemne i tak byłyby poza zakresem. Ponownie, po prostu zrób coś użytecznego i zasugeruj dodatkowe sprawdzenie, jeśli chcesz, to człowiek wysiłku zespołowego.
Nicholas Petersen

Nie chciałem być niegrzeczny i przepraszam, jeśli tak to rozumiesz. Pytanie brzmiało: „Jak zaokrąglić wynik dzielenia liczb całkowitych”. Autor wspomniał o paginacji, ale inni ludzie mogą mieć inne potrzeby. Myślę, że byłoby lepiej, gdyby twoja funkcja w jakiś sposób odzwierciedlała, że ​​nie działa dla ujemnych liczb całkowitych, ponieważ nie jest to jasne z interfejsu (na przykład inna nazwa lub typy argumentów). Aby pracować z ujemnymi liczbami całkowitymi, możesz na przykład wziąć wartość bezwzględną dywidendy i dzielnika i pomnożyć wynik przez jego znak.
Thash,

2

Inną alternatywą jest użycie funkcji mod () (lub „%”). Jeżeli pozostała niezerowa wartość, zwiększ wynik dzielenia przez liczbę całkowitą.


1

Wykonuję następujące czynności, obsługuje wszelkie przepełnienia:

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

I użyj tego rozszerzenia, jeśli jest 0 wyników:

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

Ponadto dla bieżącego numeru strony (nie został zapytany, ale może być przydatny):

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

0

Alternatywą do usunięcia rozgałęzień w testach na zero:

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

Nie jestem pewien, czy to zadziała w C #, powinien zrobić w C / C ++.


-1

Interesująca może być metoda ogólna, której wynik można powtórzyć:

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

Guawa ma podobną metodę ( Lists.partition(List, int)) i jak na ironięsize() metoda wynikowa List(jak na razie r09) cierpi na błąd przepełnienia wymieniony w odpowiedzi Brandona DuRette .
finnw

-2

Miałem podobną potrzebę, gdy musiałem konwertować minuty na godziny i minuty. Użyłem:

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

Poniższe czynności powinny zaokrąglać lepiej niż powyższe rozwiązania, ale kosztem wydajności (ze względu na obliczenia zmiennoprzecinkowe 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

Będziesz chciał wykonać dzielenie zmiennoprzecinkowe, a następnie użyć funkcji pułapu, aby zaokrąglić wartość do następnej liczby całkowitej.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.