【问题标题】:Fast ceiling of an integer division in C / C++C / C ++中整数除法的快速上限
【发布时间】:2010-04-30 14:16:53
【问题描述】:

给定整数值 xy,C 和 C++ 都以商 q = x/y 的形式返回浮点等效值的下限。我对一种返回天花板的方法感兴趣。例如,ceil(10/5)=2ceil(11/5)=3

显而易见的方法包括:

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

这需要额外的比较和乘法;和我见过的其他方法(实际上使用)涉及转换为floatdouble。有没有更直接的方法可以避免额外的乘法(或二次除法)和分支,也可以避免转换为浮点数?

【问题讨论】:

  • 除法指令经常同时返回商和余数,所以不需要乘法,只需q = x/y + (x % y != 0);就足够了
  • @LưuVĩnhPhúc 说真的,您需要将其添加为答案。我只是在代码测试期间用它来回答我的问题。虽然我不确定答案的 mod 部分是如何工作的,但它就像一种魅力,但它确实起到了作用。
  • @AndreasGrapentin Miguel Figueiredo 下面的答案是在 Lưu Vĩnh Phúc 留下上述评论前将近一年提交的。虽然我了解 Miguel 的解决方案是多么吸引人和优雅,但我不倾向于在这么晚的时候更改已接受的答案。这两种方法仍然有效。如果您对此感觉足够强烈,我建议您通过在下面对 Miguel 的回答进行投票来表示您的支持。
  • 奇怪,我还没有看到对提议的解决方案进行任何理智的测量或分析。您谈论的是近乎骨干的速度,但没有讨论架构、管道、分支指令和时钟周期。

标签: c++ c algorithm math


【解决方案1】:

对于正数

unsigned int x, y, q;

四舍五入...

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

或(避免x+y溢出)

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

【讨论】:

  • @bitc:对于负数,我相信 C99 指定舍入到零,所以 x/y 是除法的上限。 C90 没有指定如何舍入,我认为当前的 C++ 标准也没有。
  • 注意:这可能会溢出。 q = ((long long)x + y - 1) / y 不会。不过我的代码比较慢,所以如果你知道你的数字不会溢出,你应该使用 Sparky 的版本。
  • 第二个有问题,x 为 0。ceil(0/y) = 0 但它返回 1。
  • @OmryYadan x == 0 ? 0 : 1 + ((x - 1) / y) 会安全有效地解决这个问题吗?
【解决方案2】:

对于正数:

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

【讨论】:

【解决方案3】:

Sparky 的回答是解决此问题的一种标准方法,但正如我在评论中所写的那样,您有溢出的风险。这可以通过使用更宽的类型来解决,但是如果你想划分long longs呢?

Nathan Ernst 的回答提供了一种解决方案,但它涉及函数调用、变量声明和条件,这使得它不比 OP 代码短,甚至可能更慢,因为它更难优化。

我的解决办法是这样的:

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

它会比 OPs 代码稍微快一些,因为模和除法是在处理器上使用相同的指令执行的,因为编译器可以看到它们是等价的。至少 gcc 4.4.1 在 x86 上使用 -O2 标志执行此优化。

理论上,编译器可能会在 Nathan Ernst 的代码中内联函数调用并发出相同的内容,但 gcc 在我测试时并没有这样做。这可能是因为它会将编译后的代码绑定到标准库的单个版本。

最后一点,在现代机器上这些都不重要,除非您处于一个非常紧凑的循环中并且您的所有数据都在寄存器或 L1 缓存中。否则,所有这些解决方案都将同样快,除了可能是 Nathan Ernst 的解决方案,如果必须从主内存中获取函数,它可能会慢得多。

【讨论】:

  • 有一种更简单的方法来修复溢出,只需减少 y/y:q = (x &gt; 0)? 1 + (x - 1)/y: (x / y);
  • -1:这是一种低效的方式,因为它用廉价的 * 换取了昂贵的 %;比 OP 方法更糟糕。
  • 不,它没有。正如我在答案中解释的那样,当您已经执行除法时,% 运算符是免费的。
  • 那么q = x / y + (x % y &gt; 0);? : 表达式容易吗?
  • 这取决于您所说的“更容易”。它可能会或可能不会更快,这取决于编译器如何翻译它。我的猜测会慢一些,但我必须测量它才能确定。
【解决方案4】:

您可以使用 cstdlib 中的 div 函数在一次调用中获取商和余数,然后单独处理上限,如下所示

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

【讨论】:

  • 作为一个有趣的双重爆炸案例,你也可以return res.quot + !!res.rem; :)
  • ldiv 不总是将参数提升为 long long 的吗?而且这不会花费任何成本,向上转换还是向下转换?
  • @einpoklum: std::divintlonglong longintmax_t 重载(后两者从 C++11 开始);它是否在内部推广将是一个实现细节(我看不出他们为什么不为每个独立实现它的充分理由)。 ldiv 提升,但 std::div 不需要。
【解决方案5】:

正面和负面x 都有解决方案,但仅适用于正面y,只有 1 个部门且没有分支:

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

注意,如果x为正数,则除法趋于零,如果提醒不为零,我们应该加1。

如果x 为负数,则除法为零,这就是我们需要的,我们不会添加任何内容,因为x % y 不是正数

【讨论】:

  • 很有趣,因为在常见的情况下 y 是常数
  • mod 需要除法,所以这里不只是 1 个除法,但也许编译器可以将两个相似的除法优化为一个。
  • This comment 暗示现代架构可以用一条指令划分和计算模块。当然,这仍然需要一个智能编译器。
【解决方案6】:

这个怎么样? (要求 y 非负数,因此在 y 是一个没有非负性保证的变量的极少数情况下不要使用它)

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

我将 y/y 减为 1,消除了 x + y - 1 一词,并消除了任何溢出的可能性。

x 是无符号类型且包含零时,我避免x - 1 回绕。

对于有符号的x,负数和零仍然合并为一个大小写。

对于现代通用 CPU 来说可能不是一个巨大的好处,但在嵌入式系统中这将比任何其他正确答案都快得多。

【讨论】:

  • 你的else总是返回0,不需要计算任何东西。
  • @Ruud:不正确。考虑 x=-45 和 y=4
【解决方案7】:

我宁愿发表评论,但我没有足够高的代表。

据我所知,对于正论点和除数是 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 的性能方面相得益彰。
  • 似乎q = x/y + (x % y == 0); 应该改为q = x/y + (x % y != 0);
【解决方案8】:

这适用于正数或负数:

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

如果有余数,检查xy 的符号是否相同,并相应地添加1

【讨论】:

  • 不适用于负 x 和正 y。
【解决方案9】:

简化的通用形式,

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++ functions for integer division with well defined rounding strategy

【讨论】:

    【解决方案10】:

    对于有符号或无符号整数。

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

    对于有符号除数和无符号除数。

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

    对于无符号除数和有符号除数。

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

    对于无符号整数。

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

    零除数失败(与本机操作一样)。不能导致溢出。

    此处提供相应的取模和取模constexpr 实现,以及用于选择必要重载的模板(作为全面优化并防止符号比较不匹配警告):

    https://github.com/libbitcoin/libbitcoin-system/wiki/Integer-Division-Unraveled

    【讨论】:

      【解决方案11】:

      用O3编译,编译器优化性能好。

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

      【讨论】:

        猜你喜欢
        • 2018-02-26
        • 1970-01-01
        • 2013-01-12
        • 2017-08-08
        • 1970-01-01
        • 2013-07-17
        • 2011-12-04
        • 2022-01-05
        • 2014-04-17
        相关资源
        最近更新 更多