Швидка стеля цілого поділу в C / C ++


262

За даними цілих значень xі y, C і C ++ обидва повертають як коефіцієнт q = x/yпідлогу еквівалента плаваючої точки. Мене цікавить метод повернення стелі натомість. Наприклад, ceil(10/5)=2і ceil(11/5)=3.

Очевидний підхід передбачає щось на кшталт:

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

Це вимагає додаткового порівняння та множення; та інші методи, які я бачив (фактично використовуються), включають кастинг як floatабо double. Чи існує більш прямий метод, який дозволяє уникнути додаткового множення (або другого поділу) та гілки, а також уникає відтворення як числа з плаваючою точкою?


70
інструкція про ділення часто одночасно повертає як коефіцієнт, так і залишок, тому не потрібно q = x/y + (x % y != 0);
множуватись,

2
@ LưuVĩnhPhúc, що коментар повинен бути прийнятою відповіддю, imo.
Андреас Грапентін

1
@ LưuVĩnhPhúc Серйозно вам потрібно додати це як відповідь. Я просто використав це для своєї відповіді під час тесту на придатність. Це спрацювало як шарм, хоча я не впевнений, як працює модна частина відповіді, але це зробило роботу.
Захарій Краус

2
@AndreasGrapentin відповідь нижче Мігеля Фігейредо була подана майже за рік до того, як L beforeu Vưnh Phúc залишив коментар вище. Хоча я розумію, наскільки привабливим та елегантним є рішення Мігеля, я не схильний змінювати прийняту відповідь у цю пізню дату. Обидва підходи залишаються здоровими. Якщо ви відчуваєте це досить сильно, пропоную вам показати свою підтримку, голосуючи відповідь Мігеля нижче.
andand

1
Як не дивно, я не бачив жодного розумного вимірювання чи аналізу запропонованих рішень. Ви говорите про швидкість майже на кістках, але не йдеться про архітектури, трубопроводи, інструкції з розгалуження та цикли годин.
Радо

Відповіді:


394

Для додатних чисел

unsigned int x, y, q;

Щоб округлити ...

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

або (уникаючи переповнення в x + y)

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

6
@bitc: Я вважаю, що для від'ємних чисел C99 визначає округлення до нуля, x/yяк і стелю поділу. C90 не вказав, як округлити, і я не думаю, що поточний стандарт C ++ також не робить.
Девід Торнлі

6
Дивіться пост Ерік Ліпперта: stackoverflow.com/questions/921180/c-round-up/926806#926806
Mashmagar

3
Примітка. Це може переповнитися. q = ((довгий довгий) x + y - 1) / y не буде. Мій код повільніше, тому, якщо ви знаєте, що ваші номери не переповняться, вам слід скористатись версією Sparky.
Jørgen Fogh

1
@bitc: Я вважаю, що справа Давіда полягала в тому, що ви не використовуєте вищезазначений розрахунок, якщо результат буде негативним - ви просто використаєтеq = x / y;
caf

12
У другій є проблема, де x дорівнює 0. ceil (0 / y) = 0, але він повертається 1.
Omry Yadan

78

Для позитивних цифр:

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

5
Найпоширеніша інструкція щодо поділу архітектури також включає в себе результат, який залишився, тому для цього дійсно потрібен лише один поділ, і це було б дуже швидко
phuclv

58

Відповідь Спаркі - це один стандартний спосіб вирішити цю проблему, але, як я також писав у своєму коментарі, ви ризикуєте переповнювати. Це можна вирішити за допомогою ширшого типу, але що робити, якщо ви хочете розділити long longs?

Відповідь Натана Ернста пропонує одне рішення, але воно включає виклик функції, оголошення змінної та умовне, що робить її не коротшою за код ОП та, ймовірно, навіть повільніше, оскільки її важче оптимізувати.

Моє рішення таке:

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

Це буде трохи швидше, ніж код OPs, тому що модуль і поділ виконуються за допомогою тієї ж інструкції на процесорі, тому що компілятор може бачити, що вони еквівалентні. Принаймні gcc 4.4.1 виконує цю оптимізацію за допомогою прапора -O2 на x86.

Теоретично компілятор може вбудовувати виклик функції в код Натана Ернста і випромінювати те саме, але gcc цього не робив, коли я тестував його. Це може бути тому, що він прив'язує складений код до однієї версії стандартної бібліотеки.

На завершення, це не має значення для сучасної машини, за винятком випадків, коли ви знаходитесь в надзвичайно тісному циклі і всі ваші дані знаходяться в регістрах або L1-кеші. Інакше всі ці рішення будуть однаково швидкими, за винятком, можливо, Натана Ернста, який може бути значно повільнішим, якщо функція повинна бути отримана з основної пам'яті.


3
Був простіший спосіб виправити переповнення, просто зменшіть г / р:q = (x > 0)? 1 + (x - 1)/y: (x / y);
Бен Войгт

-1: це неефективний спосіб, оскільки він торгує дешевим * за дорогий%; гірше, ніж підхід до ОП.
Ів Дауст

2
Ні, це не є. Як я пояснив у відповіді, оператор% вільний, коли ви вже виконуєте поділ.
Jørgen Fogh

1
Тоді q = x / y + (x % y > 0);простіше, ніж ? :вираження?
Хань

Це залежить від того, що ви маєте на увазі під "легше". Це може бути, а може і не бути швидшим, залежно від того, як компілятор перекладає його. Моя здогадка була б повільнішою, але я повинен був би виміряти це, щоб бути впевненим.
Jørgen Fogh

18

Ви можете використовувати divфункцію в cstdlib, щоб отримати коефіцієнт і залишок за один виклик, а потім обробляти стелю окремо, як у наведеному нижче

#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
Як цікавий випадок подвійного удару ви також можете return res.quot + !!res.rem;:)
Сем Харвелл

Чи не завжди льдів просуває аргументи у довгі довгі? І це нічого не коштує, аніжних чи литих?
einpoklum

12

Як щодо цього? .

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

Я зводився y/yдо одного, виключаючи термін x + y - 1і з цим будь-який шанс переповнення.

Я уникаю x - 1обгортання, коли xце непідписаний тип і містить нуль.

Для підписаних x, негативних та нульових все ще поєднуються в один випадок.

Можливо, це не величезна користь для сучасного процесора загального призначення, але це буде набагато швидше у вбудованій системі, ніж будь-який з інших правильних відповідей.


Ваш інший завжди буде повертати 0, не потрібно нічого обчислювати.
Рууд Альтюйзен

@Ruud: неправда. Поміркуйте x = -45 і y = 4
Бен Войгт

7

Існує рішення як для позитивного, так і для негативного, xале тільки для позитивного yлише з 1 поділом і без гілок:

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

Зауважте, якщо xпозитивне, то ділення до нуля, і нам слід додати 1, якщо нагадування не дорівнює нулю.

Якщо xнегативний, то ділення на нуль, це те, що нам потрібно, і ми нічого не додамо, оскільки x % yне є позитивним


Цікаво, адже є звичайні випадки, коли y є постійним
Вовк

1
мод вимагає поділу, тому його не просто 1 поділ тут, але, можливо, компроміс може оптимізувати два подібних підрозділи в один.
M.kazem Akhgary

4

Це працює для позитивних чи негативних чисел:

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

Якщо є залишок, перевірте, чи є xі yмають однаковий знак, і додайте 1відповідно.


3

Я б скоріше прокоментував, але у мене немає достатньо високого представника.

Наскільки мені відомо, для позитивних аргументів та дільника, який має потужність 2, це найшвидший спосіб (випробуваний у CUDA):

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

Лише для загальних позитивних аргументів я прагну так робити:

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

Цікаво було б побачити, як q = x/y + !!(x % y);протиставляє q = x/y + (x % y == 0);себе q = (x + y - 1) / y;рішення та ефективність рішень у сучасному CUDA.
Грег Краміда


-2

Компілюйте з O3, компілятор добре виконує оптимізацію.

q = x / y;
if (x % y)  ++q;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.