it-swarm.com.ru

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

При заданных целочисленных значениях 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. Есть ли более прямой метод, который избегает дополнительного умножения (или второго деления) и ветвления, и который также избегает преобразования в число с плавающей запятой?

207
andand

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

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

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

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

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

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

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

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

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

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

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

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

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

56
Jørgen Fogh

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

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

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

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

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

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

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

12
Ben Voigt

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

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

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

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

5
RiaD

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

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

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

3
Mark Conway

упрощенная общая форма, 

int div_up(int n, int d) {
    return n / d + (((n < 0) ^ (d > 0)) && (n % d));
} //i.e. +1 iff (not exact int && positive result)

Для более общего ответа, C++ функции для целочисленного деления с четко определенной стратегией округления

2
Atif Hussain

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

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

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

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

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