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

При заданных целых значениях x и y, C и С++ возвращают как фактор q = x/y пол эквивалента с плавающей точкой. Меня интересует метод возврата потолка. Например, ceil(10/5)=2 и ceil(11/5)=3.

Очевидный подход включает в себя что-то вроде:

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

Для этого требуется дополнительное сравнение и умножение; и другие методы, которые я видел (на самом деле), включают кастинг как float или double. Есть ли более прямой метод, который позволяет избежать дополнительного умножения (или второго деления) и ветки, а также избегает кастинга как числа с плавающей запятой?

Ответ 1

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

unsigned int x, y, q;

Чтобы округлить...

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

или (избегая переполнения в x + y)

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

Ответ 2

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

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

Ответ 3

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

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

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

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

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

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

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

Ответ 4

Вы можете использовать функцию 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;
}

Ответ 5

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

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

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

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

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

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

Ответ 6

Там есть решение как для положительного, так и для отрицательного x, но только для положительных y с одним делением и без ветвей:

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

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

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

Ответ 7

Это работает для положительных или отрицательных чисел:

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

Если есть остаток, проверяет, имеют ли x и y одинаковый знак, и добавляет 1 соответственно.

Ответ 9

Я бы скорее прокомментировал, но у меня недостаточно высокая репутация.

Насколько я знаю, для +ve & Pow из 2 это самый быстрый способ (проверено в CUDA)

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

в противном случае (также только +ve) я бы сделал это следующим образом:

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

Ответ 10

Компилировать с O3, Компилятор хорошо выполняет оптимизацию.

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