Як закріпити результат цілочислового поділу?


335

Я особливо думаю про те, як відобразити елементи керування сторінками при використанні такої мови, як C # або Java.

Якщо у мене є x елементів, які я хочу відображати у відрізках y на сторінці, скільки сторінок знадобиться?


1
Я щось пропускаю? y / x + 1 працює чудово (за умови, що ви знаєте, що / оператор завжди збивається).
rikkit

51
@rikkit - якщо y і x рівні, y / x + 1 - один занадто високий.
Ян Нельсон

1
Для тих, хто тільки зараз виявляє це, ця відповідь на придумливе запитання дозволяє уникнути непотрібного перетворення в подвійне та уникає проблем із переповненням на додаток до надання чіткого пояснення.
ZX9

2
@IanNelson загалом, якщо xділиться на y, y/x + 1було б занадто високим.
Охад Шнайдер

1
@ ZX9 Ні, це не уникає проблем із переповненням. Це саме те саме рішення, що і тут розмістив Ян Нельсон.
user247702

Відповіді:


478

Знайшли елегантне рішення:

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

Джерело: Перетворення чисел, Roland Backhouse, 2001


12
-1 через помилку переповнення, яку вказав Брендон Дюретт
finnw

30
Г - н Очевидні каже: Чи не забудьте переконатися , що recordsPerPage не дорівнює нулю
Адам Gent

7
Хороша робота, я не можу повірити, що у C # немає цілої стелі.
gosukiwi

2
Так, ось я в середині 2017 року натрапляю на цю чудову відповідь, спробувавши кілька набагато складніших підходів.
Міфо

1
Для мов з належним оператором поділу Евклідової, таким як Python, був би ще простіший підхід pageCount = -((-records) // recordsPerPage).
supercat

194

Перетворення на плаваючу крапку і назад здається величезною тратою часу на рівні процесора.

Рішення Яна Нельсона:

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

Можна спростити:

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

AFAICS, у цьому відсутності помилки переповнення, на яку вказував Брендон Дюретт, і тому що він використовує його лише один раз, вам не потрібно зберігати recordsPerPage спеціально, якщо він походить від дорогої функції для отримання значення з конфігураційного файлу або щось.

Тобто це може бути неефективним, якщо config.fetch_value використовував пошук бази даних чи щось таке:

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

Це створює змінну, яка вам насправді не потрібна, яка, ймовірно, має (незначні) наслідки для пам’яті і просто занадто багато вводить:

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

Це все в одному рядку і отримує дані лише один раз:

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

5
+1, випуск нульових записів все ще повертає 1 сторінкуCount насправді зручно, оскільки я все одно хотів би, щоб 1 сторінка, що відображає заповнювач / підроблений рядок "жоден запис не відповідає вашим критеріям", допомагає уникнути будь-яких проблем "0 кількість сторінок" у будь-якому випадку контроль сторінки, який ви використовуєте.
Тімоті Уолтерс

27
Майте на увазі, що два рішення не повертають однакову сторінкуCount для нульових записів. Ця спрощена версія поверне 1 сторінкуCount за нульові записи, тоді як версія Roland Backhouse повертає 0 pageCount. Добре, якщо ви цього хочете, але два рівняння не є еквівалентними, коли їх виконує ціле ділення C # / Java stylee.
Ян Нельсон

10
крихітна редакція для ясності для людей, які сканують її та відсутні бодми при переході на спрощення з рішення Нельсона (як я це робив у перший раз!), спрощення за допомогою дужок - це ... int pageCount = ((записи - 1) / recordsPerPage) + 1;
Дейв Хейвуд

1
Ви повинні додати дужки в спрощену версію, щоб вона не покладалася на певний порядок операцій. тобто ((записи - 1) / recordsPerPage) + 1.
Мартін

1
@Ian, ця відповідь не завжди повертає 1. Вона може повертати 0 , якщо ваш recordsPerPage є «1» , і є 0 записи: -1 / 1 + 1 = 0. Хоча це не надто поширене явище, важливо пам’ятати, якщо ви дозволяєте користувачам коригувати розмір сторінки. Тому або не дозволяйте користувачам мати розмір сторінки 1, зробіть перевірку на розмір сторінки або обидва (можливо, бажано, щоб уникнути несподіваної поведінки).
Майкл

81

Для C # рішенням є приведення значень у подвійне (оскільки Math.Ceiling займає подвійне):

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

У Java вам слід зробити те ж саме з Math.ceil ().


4
чому ця відповідь настільки вниз, коли ОП явно просить C #!
felickz

2
Вам також потрібно подати вихід, intтому що Math.Ceilingповертає a doubleабо decimal, залежно від типів введення.
DanM7

15
адже це надзвичайно неефективно
Зар Шардан

6
Це може бути неефективним, але зрозуміти це надзвичайно просто. З огляду на обчислення кількості сторінок, як правило, проводиться один раз на запит, будь-яка втрата продуктивності не піддається вимірюванню.
Джаред Келлс

1
Це ледве читабельніше, ніж це "(дивіденд + (дільник - 1)) / дільник;" також його повільний і вимагає математичної бібліотеки.
булочки

68

Це має дати вам те, що ви хочете. Ви обов'язково захочете x елементів, поділених на y елементів на сторінку, проблема полягає в тому, коли з’являються нерівномірні числа, тому якщо є часткова сторінка, ми також хочемо додати одну сторінку.

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) уникає гілки для С-подібних мов. Коефіцієнти хороші, однак ваш компілятор все одно це робить.
Rhys Ulerich

2
+1 за те, що не переповнюється, як відповіді вище ... хоча конвертування вставних значень у подвійні лише для Math.ceiling, а потім знову - це погана ідея у коді, що залежить від продуктивності.
Cogwheel

3
@RhysUlerich, який не працює в c # (не може безпосередньо перетворити int у bool). Рішення rjmunro - це єдиний спосіб уникнути розгалуження, я думаю.
мазати

18

Ціле математичне рішення, яке надав Ян, є приємним, але страждає від цілої помилки переповнення. Якщо припустимо, що всі змінні - intце рішення, можна переписати на використання longматематики та уникнути помилок:

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

Якщо recordsє long, помилка залишається. Рішення модуля не має помилок.


4
Я не думаю, що ви реально збираєтеся вдарити цю помилку у поданому сценарії. 2 ^ 31 записів досить багато, що доведеться переглядати сторінки.
rjmunro


@finnw: AFAICS, на цій сторінці немає прикладу реального світу, а лише повідомлення про те, що хтось інший знайшов помилку в теоретичному сценарії.
rjmunro

5
Так, я виявляв педантичність, вказуючи на помилку. Багато помилок можуть існувати вічно, не завдаючи жодних проблем. Помилка такої ж форми існувала при впровадженні JDK binarySearch протягом дев'яти років, перш ніж хтось повідомив про це ( googleresearch.blogspot.com/2006/06/… ). Я думаю, питання полягає в тому, незалежно від того, наскільки навряд чи ви зіткнетеся з цією помилкою, чому б не виправити її спереду?
Брендон Дюрет

4
Крім того, слід зазначити, що важлива не лише кількість елементів, які піддаються підключення до сторінки, це також розмір сторінки. Отже, якщо ви створюєте бібліотеку, і хтось вирішив не розміщувати сторінку, передавши 2 ^ 31-1 (Integer.MAX_VALUE) як розмір сторінки, тоді помилка спрацьовує.
Брендон Дюретт

7

Варіант відповіді Ніка Берарді, який уникає гілки:

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

Примітка: (-r >> (Integer.SIZE - 1))складається з бітового знаку r, повтореного 32 рази (завдяки розширенню знака >>оператора.) Це оцінюється до 0, якщо rдорівнює нулю або негативу, -1 якщо rпозитивно. Таким чином, віднімання його з qрезультату додає 1, якщо records % recordsPerPage > 0.


4

Для записів == 0, рішення rjmunro дає 1. Правильне рішення - 0. Це означає, що якщо ви знаєте, що записи> 0 (і я впевнений, що всі ми вважали, що записиPerPage> 0), то рішення rjmunro дає правильні результати і не має жодної із проблем із переповненням.

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

Всі рішення з цілої математики будуть більш ефективними, ніж будь-яке рішення з плаваючою комою.


Цей метод навряд чи буде вузьким місцем продуктивності. І якщо це так, ви також повинні врахувати вартість філії.
finnw

4

Необхідний метод розширення:

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

Тут немає жодних чеків (переповнення DivideByZeroтощо), сміливо додайте, якщо хочете. До речі, для тих, хто переживає виклик методу накладних витрат, такі прості функції, як ця, можуть бути накреслені компілятором у будь-якому випадку, тому я не думаю, що саме тут слід турбуватися. Ура.

PS Вам може бути корисно також знати про це (він отримує решту):

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

1
Це неправильно. Наприклад: DivideUp(4, -2)повертає 0 (має бути -2). Це правильно лише для негативних цілих чисел, що не зрозуміло з відповіді чи з інтерфейсу функції.
Таш

6
Тхаш, чому б ти не зробив щось корисне, як-от додати невеликий додатковий чек, то якщо число негативне, замість того, щоб голосувати мою відповідь і неправильно скласти ковдру: "Це неправильно", адже насправді це просто край справа. Я вже дав зрозуміти, що вам слід спочатку зробити інші перевірки: "Ніяких чеків тут (переповнення, DivideByZero тощо), сміливо додайте, якщо вам це подобається ".
Микола Петерсен

1
Згадане питання "Я, зокрема, думаю про те, як відобразити елементи керування сторінками ", тому негативні цифри в будь-якому разі були б поза межами. Знову ж таки, просто зробіть щось корисне і запропонуйте додатковий чек, якщо ви хочете, це команда зусиль.
Микола Петерсен

Я не мав на увазі бути грубим, і мені шкода, якщо ви так сприймете. Питання було "Як округлити результат цілочислового поділу". Автор згадав про пагинацію, але інші люди можуть мати різні потреби. Я думаю, що було б краще, якби ваша функція якось відображала, що вона не працює для негативних цілих чисел, оскільки це не зрозуміло з інтерфейсу (наприклад, імені диференціала або типи аргументів). Для роботи з від’ємними цілими числами можна, наприклад, взяти абсолютне значення дивіденда і дільника і помножити результат на його знак.
Таш

2

Інша альтернатива - використання функції mod () (або '%'). Якщо є ненульовий залишок, то збільшуємо цілий результат ділення.


1

Я виконую наступне, обробляє будь-які переливи:

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

І використовуйте це розширення, якщо є 0 результатів:

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

Також для поточного номера сторінки (не запитували, але може бути корисним):

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

0

Альтернатива видалити розгалуження при тестуванні на нуль:

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

Не впевнений, чи це буде працювати в C #, слід робити в C / C ++.


-1

Загальний метод, результат якого ви можете повторити, може зацікавити:

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

Гуава має аналогічний метод ( Lists.partition(List, int)) і, за іронією долі, size()метод отриманого List(станом на r09) страждає від помилки переповнення, згаданої у відповіді Брендона Дюрета .
finnw

-2

У мене була подібна потреба, коли мені потрібно було перетворити Хвилини на години та хвилини. Що я використовував:

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

Наступні повинні робити округлення краще, ніж наведені вище рішення, але за рахунок продуктивності (за рахунок обчислення з плаваючою комою 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

Ви хочете зробити поділ з плаваючою комою, а потім скористатися функцією стелі, щоб округлити значення до наступного цілого числа.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.