Fast ceiling of an integer division in C / C++
给定整数值x和y,C和C ++都返回商q = x/y作为浮点等价的底限。 我对一种返回天花板的方法很感兴趣。 例如,ceil(10/5)=2和ceil(11/5)=3。
显而易见的方法包括:
1 2
| q = x / y;
if (q * y < x) ++q; |
这需要额外的比较和乘法; 和我见过的其他方法(事实上使用)涉及铸造float或double。 是否有更直接的方法可以避免额外的乘法(或第二个除法)和分支,并且还可以避免作为浮点数进行转换?
-
除法指令通常同时返回商和余数,因此不需要乘法,只需q = x/y + (x % y != 0);就足够了
-
@L?uV?nhPh&#250; c评论应该是接受的答案,imo。
-
@L?uV?nhPh&#250; c严重的是你需要添加它作为答案。我只是在训练时使用了它作为我的答案。虽然我不确定答案的mod部分是如何起作用的,但是它确实起了作用。
-
@AndreasGrapentin下面的答案由Miguel Figueiredo提交,差不多一年之后,L?u V?nh Ph&#250; c留下上述评论。虽然我理解Miguel的解决方案是多么吸引人和优雅,但我并不倾向于在这个较晚的日期改变已接受的答案。两种方法都是合理的。如果您对此感到非常满意,我建议您通过以下方式对Miguel的回答进行投票表达您的支持。
-
相关问题stackoverflow.com/q/3407012/61505
-
奇怪的是,我还没有看到任何理智的测量或分析建议的解决方案。你谈论速度近似,但没有讨论架构,流水线,分支指令和时钟周期。
-
@AndreasGrapentin虽然Luu Vinh Phuc的答案很优雅,但它有一个不等式比较,以及另一个模数,它可以有效地减慢它(编译器可以优化模数,但比较仍然存在)。接受的答案没有比较,只有一个分区,所以它似乎是一个更好的答案。如果没有看到编译器在每种情况下吐出的内容,很难
对于正数
围捕......
或(避免x + y溢出)
1
| q = 1 + ((x - 1) / y); // if x != 0 |
-
注意:这仅适用于正数。
-
为了以防万一,我会进行单元测试。
-
@bitc:对于负数,我认为C99指定为零到零,因此x/y是除法的上限。 C90没有指定如何舍入,我认为当前的C ++标准也没有。
-
请参阅Eric Lippert的帖子:stackoverflow.com/questions/921180/c-round-up/926806#926806
-
注意:这可能会溢出。 q =((long long)x + y - 1)/ y不会。我的代码虽然慢,所以如果你知道你的数字不会溢出,你应该使用Sparky的版本。
-
@David Thornley:iirc,仅适用于从浮点到int的转换。在上面的代码中,负数导致破坏:q =(11 +( - 5) - 1)/( - 5);
-
@bitc:我相信大卫的观点是,如果结果为负,你不会使用上面的计算 - 你只需要使用q = x / y;
-
第二个问题是x为0. ceil(0 / y)= 0但它返回1。
-
@OmryYadan会x == 0 ? 0 : 1 + ((x - 1) / y)安全有效地解决这个问题吗?
-
从评论来看,@ jamesmstone不,你的建议没有解决问题。正如第一条评论正确地注意到的那样,它仅适用于正数。尝试检查x = -y或x = -2*y等任何积极的y,你会看到同样的错误。我甚至没有开始讨论负面y的情况。建议在stackoverflow.com/questions/921180/c-round-up/926806#926806上查看Eric Lippert的回答以获取一些细节非常好。
对于正数:
1
| q = x/y + (x % y != 0); |
-
最常见的架构划分指令还包括其结果中的余数,因此这实际上只需要一个划分并且速度非常快
Sparky的答案是解决这个问题的一种标准方法,但正如我在评论中所写,你冒着溢出的风险。这可以通过使用更宽的类型来解决,但是如果要分割long long s怎么办?
Nathan Ernst的答案提供了一个解决方案,但它涉及函数调用,变量声明和条件,这使得它不比OP代码短,甚至可能更慢,因为它更难以优化。
我的解决方案是:
1
| 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 > 0)? 1 + (x - 1)/y: (x / y);
-
-1:这是一种效率低下的方式,因为它以便宜的价格换取成本高昂的百分比;比OP方法更糟糕。
-
不,不是的。正如我在答案中解释的那样,当你已经执行除法时,%运算符是免费的。
-
那么q = x / y + (x % y > 0);比? :表达更容易吗?
-
这取决于你的意思"更容易"。它可能会也可能不会更快,具体取决于编译器如何转换它。我的猜测会慢一点,但我必须要测量它才能
-
实际上,我没有看到添加分支指令应该如何制作这个faser。
您可以使用cstdlib中的div函数来获取商和&amp;剩下一个电话,然后单独处理天花板,如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #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总是将争论推进到长期的?这不是花费任何成本,向上铸造或向下铸造吗?
这个怎么样? (要求y为非负数,所以在极少数情况下不要使用它,其中y是一个没有非负性保证的变量)
1
| q = (x > 0)? 1 + (x - 1)/y: (x / y); |
我将y/y减少到1,消除了术语x + y - 1,并且随之出现溢出的可能性。
当x是无符号类型且包含零时,我避免x - 1环绕。
对于带符号x,负数和零仍然合并为一个案例。
对于现代通用CPU来说可能不是一个巨大的好处,但在嵌入式系统中,这比任何其他正确的答案要快得多。
-
你的其他人将永远返回0,无需计算任何东西。
-
@Ruud:不对。考虑x = -45和y = 4
有一个正面和负面x的解决方案,但仅适用于只有1个分区而没有分支的正y:
1 2 3
| int ceil(int x, int y) {
return x / y + (x % y > 0);
} |
注意,如果x为正,那么除法为零,如果提醒不为零,我们应该加1。
如果x为负,则除法为零,这就是我们需要的,我们不会添加任何东西,因为x % y不是正数
-
有趣的是,因为y是常数的常见情况
-
mod需要划分,因此它不仅仅是1个划分,但也许编译器可以将两个相似的划分优化为一个。
这适用于正数或负数:
1
| q = x / y + ((x % y != 0) ? !((x > 0) ^ (y > 0)) : 0); |
如果有余数,则检查x和y是否具有相同的符号并相应地添加1。
简化的通用形式,
1 2 3
| 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 ++用于整数除法,具有良好定义的舍入策略
我宁可评论,但我没有足够高的代表。
据我所知,+ ve&amp; pow of 2这是最快的方式(在CUDA中测试)
1 2
| //example y=8
q = x >> 3 + !!(x & 7); |
否则(也只是+ ve)我倾向于这样做:
使用O3编译,编译器可以很好地执行优化。
1 2
| q = x / y;
if (x % y) ++q; |