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.