Быстрый потолок целочисленного деления в C / C ++


262

При заданных целочисленных значениях xи yC, и 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);достаточно
phuclv

2
@ LưuVĩnhPhúc, что комментарий должен быть принятым ответом, IMO.
Андреас Грапентин

1
@ LưuVĩnhPhúc Серьезно, вы должны добавить это в качестве ответа. Я только использовал это для своего ответа во время теста на codility. Это сработало как шарм, хотя я не уверен, как мод часть ответа работает, но он сделал свою работу.
Захари Краус

2
@AndreasGrapentin ответ Мигеля Фигейредо, представленный ниже, был представлен почти за год до того, как Лю Вон Фук оставил комментарий выше. Хотя я понимаю, насколько привлекательным и элегантным является решение Мигеля, я не склонен менять принятый ответ в такой поздний срок. Оба подхода остаются здравыми. Если вы чувствуете в этом достаточно сильные чувства, я предлагаю вам показать свою поддержку, проголосовав Мигелем ниже.
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
Машмагар

3
Примечание. Это может привести к переполнению. q = ((long long) x + y - 1) / y не будет. Мой код медленнее, поэтому, если вы знаете, что ваши цифры не переполнятся, вы должны использовать версию Sparky.
Йорген Фог

1
@bitc: Я полагаю, что точка зрения Дэвида состояла в том, что вы не использовали бы приведенный выше расчет, если бы результат был отрицательным - вы просто использовали быq = x / y;
кафе

12
У второй проблемы, где x равен 0. ceil (0 / y) = 0, но возвращается 1.
Omry Yadan

78

Для положительных чисел:

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

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

58

Ответ Спарки - это один из стандартных способов решения этой проблемы, но, как я уже писал в своем комментарии, вы рискуете переполниться. Это можно решить, используя более широкий тип, но что, если вы хотите разделить long longs?

Ответ Натана Эрнста дает одно решение, но оно включает вызов функции, объявление переменной и условие, что делает его не короче кода OP и, возможно, даже медленнее, поскольку его сложнее оптимизировать.

Мое решение таково:

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

Это будет немного быстрее, чем код OP, потому что по модулю и делению выполняется одна и та же инструкция на процессоре, потому что компилятор может видеть, что они эквивалентны. По крайней мере, gcc 4.4.1 выполняет эту оптимизацию с флагом -O2 на x86.

Теоретически, компилятор может встроить вызов функции в код Натана Эрнста и выдать то же самое, но gcc не сделал этого, когда я тестировал его. Это может быть связано с тем, что скомпилированный код будет привязан к одной версии стандартной библиотеки.

В заключение отметим, что на современном компьютере все это не имеет значения, за исключением случаев, когда вы находитесь в чрезвычайно узком цикле и все ваши данные находятся в регистрах или L1-кэше. В противном случае все эти решения будут одинаково быстрыми, за исключением, возможно, решения Натана Эрнста, которое может быть значительно медленнее, если функция должна быть извлечена из основной памяти.


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

-1: это неэффективный способ, так как он торгует дешевым * за дорогой%; хуже, чем ОП подход.
Ив Дауст

2
Нет. Как я объяснил в ответе, оператор% свободен, когда вы уже выполняете деление.
Йорген Фог

1
Тогда q = x / y + (x % y > 0);это легче, чем ? :выражение?
Хан

Это зависит от того, что вы подразумеваете под «проще». Это может быть или не быть быстрее, в зависимости от того, как компилятор переводит это. Мое предположение было бы медленнее, но я должен был бы измерить это, чтобы быть уверенным.
Йорген Фог

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;:)
Сэм Харуэлл,

Разве ldiv не всегда продвигает аргументы в long long? И это ничего не стоит, повышая или понижая?
einpoklum

12

Как насчет этого? (требует, чтобы y было неотрицательным, поэтому не используйте его в редком случае, когда y - переменная без гарантии неотрицательности)

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

Я сократил y/yдо одного, исключив термин x + y - 1и , тем самым, шанс переполнения.

Я избегаю x - 1обтекания, когда xтип без знака и содержит ноль.

Для знаковых xотрицательный и ноль все еще объединяются в один случай.

Вероятно, это не большое преимущество для современного процессора общего назначения, но это будет намного быстрее во встроенной системе, чем любой другой правильный ответ.


Ваш else всегда будет возвращать 0, не нужно ничего вычислять.
Рууд Альтуизен

@ Рууд: не правда. Рассмотрим х = -45 и у = 4
Бен Фойгт

7

Существует решение как для положительного, так и для отрицательного, xно только для положительного yтолько с 1 делением и без ветвей

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

Обратите внимание: если xположительное значение, то деление стремится к нулю, и мы должны добавить 1, если напоминание не равно нулю.

Если xотрицательно, то деление к нулю, это то, что нам нужно, и мы не будем ничего добавлять, потому что x % yэто не положительно


интересно, потому что часто встречаются случаи, когда у постоянна
Вольф

1
Мод требует деления, поэтому здесь не только одно деление, но, возможно, Complier может оптимизировать два одинаковых деления в одно.
М.Казем Ахгари

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.