C / C ++中整数除法的快速上限


262

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

显而易见的方法包括:

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

这需要额外的比较和乘法。我见过的(实际上使用过的)其他方法都涉及将as float或as强制转换double。有没有更直接的方法来避免额外的乘法(或第二除法)和分支,并且还避免将其转换为浮点数?


70
除法指令通常同时返回商和余数,因此不需要乘法,就q = x/y + (x % y != 0);足够了
phuclv 2014年

2
@LưuVĩnhPhúc该评论应该被接受,imo。
Andreas Grapentin

1
@LưuVĩnhPhúc认真的说,您需要添加它作为答案。我只是在进行一致性测试时将其用作答案。尽管我不确定答案的mod部分是如何工作的,但是它确实起到了吸引人的作用,但是它确实起到了作用。
Zachary Kraus 2014年

2
@AndreasGrapentin在Miguel Figueiredo的回答下面将在LưuVĩnhPhúc发表以上评论之前将近一年提交。虽然我了解Miguel的解决方案多么吸引人和优雅,但我不愿意在这个较晚的日期之前更改接受的答案。两种方法都保持良好状态。如果您对此有足够的信心,建议您通过在下面对Miguel的答案进行投票来表示支持。
and14

1
奇怪的是,我还没有对所提出的解决方案进行任何合理的测量或分析。您谈论的是近乎骨头的速度,但是没有讨论体系结构,管线,分支指令和时钟周期。
拉多

Answers:


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 ++标准也可以。
David Thornley,2010年

6
参见埃里克·利珀特(Eric Lippert)的文章:stackoverflow.com/questions/921180/c-round-up/926806#926806
Mashmagar

3
注意:这可能会溢出。q =((长长)x + y-1)/ y不会。不过,我的代码比较慢,因此,如果您知道数字不会溢出,则应使用Sparky版本。
约尔根·福格

1
@bitc:我相信David的观点是,如果结果为负,则您将不会使用上述计算-您只会使用q = x / y;
caf 2010年

12
第二个有一个问题,其中x是0小区(0 / Y)= 0,但它返回1
Omry雅丹


58

Sparky的答案是解决此问题的一种标准方法,但是正如我在评论中所写的那样,您还存在溢出的风险。这可以通过使用更广泛的类型来解决,但是如果要除以long longs怎么办?

内森·恩斯特(Nathan Ernst)的答案提供了一种解决方案,但是它涉及到函数调用,变量声明和条件操作,这使其不比OPs代码短,甚至可能更慢,因为它难以优化。

我的解决方案是这样的:

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

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

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

最后要注意的是,在现代计算机上,这一切都不重要,除非您处于非常紧密的循环中,并且所有数据都位于寄存器或L1缓存中。否则,除了Nathan Ernst的解决方案之外,所有这些解决方案都将同样快,如果必须从主内存中提取函数,Nathan Ernst的解决方案可能会明显变慢。


3
有一种更简单的方法来解决溢出问题,只需减少y / y:q = (x > 0)? 1 + (x - 1)/y: (x / y);
Ben Voigt 2010年

-1:这是一种低效的方式,因为它以便宜的*换来了昂贵的%;比OP方法更糟糕。
Yves Daoust 2014年

2
不,不是的。正如我在答案中所解释的,当您已经执行除法时,%运算符是自由的。
约尔根·福格·

1
q = x / y + (x % y > 0);? :表达容易吗?
汉族

这取决于您所说的“更轻松”。它可能更快也可能不会更快,这取决于编译器如何翻译它。我的猜测会比较慢,但是我必须对其进行测量以确保。
约根·福

18

您可以使用divcstdlib中的函数在一次调用中获得商和余数,然后分别处理上限,如下所示

#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;:)
Sam Harwell 2010年

ldiv不会总是将参数提成long long吗?而且这不花任何费用,向上转换还是向下转换?
einpoklum

12

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

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

我简化y/y为一个,消除了该术语,x + y - 1并避免了溢出的可能性。

我避免x - 1在when x是无符号类型且包含零的情况下回绕。

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

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


您的else将始终返回0,无需进行任何计算。
Ruud Althuizen 2015年

@Ruud:不正确。考虑x = -45和y = 4
Ben Voigt

7

有一个解决方案,既可以解决正面问题,也可以解决负面问题,x但是仅针对y只有1个分区且没有分支的正面问题:

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

请注意,如果x为正,则除法接近零;如果提醒不为零,则应加1。

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


有趣,因为在某些情况下y是常数
Wolf

1
mod需要除法,因此它不仅是1除法,而且也许编译器可以将两个相似的除法优化为一个。
M.kazem Akhgary

4

这适用于正数或负数:

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

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


3

我本来想发表评论,但我的代表不够高。

据我所知,对于正参数和除数为2的幂,这是最快的方法(在CUDA中测试):

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

仅对于一般肯定论点,我倾向于这样做:

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

看看当代CUDA 如何与之q = x/y + !!(x % y);抗衡q = x/y + (x % y == 0);以及 q = (x + y - 1) / y;解决方案在性能方面会很有趣。
Greg Kramida


-2

使用O3进行编译,编译器可以很好地执行优化。

q = x / y;
if (x % y)  ++q;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.