我需要将两个数字相除并四舍五入。有没有更好的方法来做到这一点?
int myValue = (int) ceil( (float)myIntNumber / myOtherInt );
我发现不得不投两个不同的时间有点过头了。(extern int cast 只是为了关闭警告)
注意我必须在内部强制转换才能浮动
int a = ceil(256/11); //> Should be 24, but it is 23
^example
我需要将两个数字相除并四舍五入。有没有更好的方法来做到这一点?
int myValue = (int) ceil( (float)myIntNumber / myOtherInt );
我发现不得不投两个不同的时间有点过头了。(extern int cast 只是为了关闭警告)
注意我必须在内部强制转换才能浮动
int a = ceil(256/11); //> Should be 24, but it is 23
^example
假设myIntNumber
和myOtherInt
都是积极的,你可以这样做:
int myValue = (myIntNumber + myOtherInt - 1) / myOtherInt;
在 DyP 的帮助下,提出了以下无分支公式:
int idiv_ceil ( int numerator, int denominator )
{
return numerator / denominator
+ (((numerator < 0) ^ (denominator > 0)) && (numerator%denominator));
}
它避免了浮点转换并通过了一套基本的单元测试,如下所示:
这是另一个避免模运算符的版本。
int idiv_ceil ( int numerator, int denominator )
{
int truncated = numerator / denominator;
return truncated + (((numerator < 0) ^ (denominator > 0)) &&
(numerator - truncated*denominator));
}
第一个在 IDIV 返回商和余数的处理器上会更快(并且编译器足够聪明,可以使用它)。
也许这样做更容易:
int result = dividend / divisor;
if(dividend % divisor != 0)
result++;
由于答案中显示了许多不同的方法,并且没有一个答案实际上证明在性能方面有任何优势,我试图自己对它们进行基准测试。我的计划是写一个答案,其中包含一个简短的表格和一个明确的答案,哪种方法最快。
不幸的是,事情并没有那么简单。(从来没有。)似乎舍入公式的性能取决于使用的数据类型、编译器和优化级别。
在一种情况下,从一种方法到另一种方法的速度提高了 7.5 倍。因此,对某些人来说,影响可能很大。
对于long
整数,使用类型转换的朴素版本float
实际上std::ceil
是最快的。这对我个人来说很有趣,因为我打算将它与size_t
通常定义为unsigned long
.
对于普通int
的 s 这取决于您的优化级别。对于较低级别的@Jwodder 的解决方案表现最好。因为最高级别std::ceil
是最佳级别。除了一个例外:对于铿锵/unsigned int
组合@Jwodder's 仍然更好。
接受答案的解决方案从未真正超越其他两个。但是,您应该记住,@Jwodder 的解决方案不适用于底片。
结果在底部。
在这里回顾一下我基准测试和比较的四种方法:
template<typename T>
inline T divCeilJwodder(const T& numerator, const T& denominator)
{
return (numerator + denominator - 1) / denominator;
}
template<typename T>
inline T divCeilVoigtModulo(const T& numerator, const T& denominator)
{
return numerator / denominator + (((numerator < 0) ^ (denominator > 0))
&& (numerator%denominator));
}
inline T divCeilVoigtNoModulo(const T& numerator, const T& denominator)
{
T truncated = numerator / denominator;
return truncated + (((numerator < 0) ^ (denominator > 0))
&& (numerator - truncated*denominator));
}
template<typename T>
inline T divCeilTypeCast(const T& numerator, const T& denominator)
{
return (int)std::ceil((double)numerator / denominator);
}
在一个批次中进行1亿次分割。针对编译器/优化级别、使用的数据类型和使用的实现的每种组合计算十个批次。下面显示的值是所有 10 个批次的平均值,以毫秒为单位。给出的误差是标准偏差。
可以在此处找到所使用的全部源代码。此外,您可能会发现此脚本很有用,它使用不同的编译器标志编译和执行源代码。
整个基准测试是在 i7-7700K 上执行的。使用的编译器版本是 GCC 10.2.0 和 clang 11.0.1。
现在不用多说,结果如下:
DataType 算法 |
海合会 -O0 |
-O1 | -O2 | -O3 | -Os | -Ofast | -Og | 铿锵 -O0 |
-O1 | -O2 | -O3 | -Ofast | -Os | -盎司 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
unsigned |
||||||||||||||
乔德 | 264.1 ± 0.9 | 175.2±0.9 | 153.5±0.7 | 175.2±0.5 | 153.3±0.5 | 153.4±0.8 | 175.5±0.6 | 329.4±1.3 | 220.0±1.3 | 146.2±0.6 | 146.2±0.6 | 146.0±0.5 | 153.2±0.3 | 153.5±0.6 |
Voigt 模数 | 528.5±2.5 | 306.5±1.0 | 175.8±0.7 | 175.2±0.5 | 175.6±0.7 | 175.4±0.6 | 352.0±1.0 | 588.9±6.4 | 408.7±1.5 | 164.8±1.0 | 164.0±0.4 | 164.1±0.4 | 175.2±0.5 | 175.8±0.9 |
VoigtNoModulo | 375.3±1.5 | 175.7±1.3 | 192.5±1.4 | 197.6±1.9 | 200.6±7.2 | 176.1±1.5 | 197.9±0.5 | 541.0±1.8 | 263.1±0.9 | 186.4±0.6 | 186.4±1.2 | 187.2±1.1 | 197.2±0.8 | 197.1±0.7 |
类型转换 | 348.5±2.7 | 231.9 ± 3.9 | 234.4±1.3 | 226.6±1.0 | 137.5±0.8 | 138.7±1.7 | 243.8±1.4 | 591.2±2.4 | 591.3±2.6 | 155.8±1.9 | 155.9±1.6 | 155.9±2.4 | 214.6±1.9 | 213.6±1.1 |
unsigned long |
||||||||||||||
乔德 | 658.6±2.5 | 546.3±0.9 | 549.3±1.8 | 549.1±2.8 | 540.6±3.4 | 548.8±1.3 | 486.1 ± 1.1 | 638.1±1.8 | 613.3±2.1 | 190.0±0.8 | 182.7±0.5 | 182.4±0.5 | 496.2±1.3 | 554.1±1.0 |
Voigt 模数 | 1,169.0 ± 2.9 | 1,015.9 ± 4.4 | 550.8±2.0 | 504.0±1.4 | 550.3±1.2 | 550.5±1.3 | 1,020.1 ± 2.9 | 1,259.0 ± 9.0 | 1,136.5 ± 4.2 | 187.0±3.4 | 199.7±6.1 | 197.6±1.0 | 549.4±1.7 | 506.8±4.4 |
VoigtNoModulo | 768.1 ± 1.7 | 559.1±1.8 | 534.4±1.6 | 533.7±1.5 | 559.5±1.7 | 534.3±1.5 | 571.5±1.3 | 879.5±10.8 | 617.8±2.1 | 223.4±1.3 | 231.3±1.3 | 231.4 ± 1.1 | 594.6±1.9 | 572.2±0.8 |
类型转换 | 353.3±2.5 | 267.5±1.7 | 248.0±1.6 | 243.8 ± 1.2 | 154.2±0.8 | 154.1±1.0 | 263.8±1.8 | 365.5±1.6 | 316.9±1.8 | 189.7±2.1 | 156.3±1.8 | 157.0±2.2 | 155.1±0.9 | 176.5±1.2 |
int |
||||||||||||||
乔德 | 307.9 ± 1.3 | 175.4±0.9 | 175.3±0.5 | 175.4±0.6 | 175.2±0.5 | 175.1±0.6 | 175.1±0.5 | 307.4 ± 1.2 | 219.6±0.6 | 146.0±0.3 | 153.5±0.5 | 153.6±0.8 | 175.4±0.7 | 175.2±0.5 |
Voigt 模数 | 528.5±1.9 | 351.9 ± 4.6 | 175.3±0.6 | 175.2±0.4 | 197.1±0.6 | 175.2±0.8 | 373.5±1.1 | 598.7±5.1 | 460.6±1.3 | 175.4±0.4 | 164.3±0.9 | 164.0±0.4 | 176.3±1.6 | 460.0±0.8 |
VoigtNoModulo | 398.0±2.5 | 241.0±0.7 | 199.4±5.1 | 219.2±1.0 | 175.9±1.2 | 197.7±1.2 | 242.9 ± 3.0 | 543.5±2.3 | 350.6±1.0 | 186.6±1.2 | 185.7±0.3 | 186.3±1.1 | 197.1±0.6 | 373.3±1.6 |
类型转换 | 338.8 ± 4.9 | 228.1±0.9 | 230.3±0.8 | 229.5±9.4 | 153.8±0.4 | 138.3±1.0 | 241.1±1.1 | 590.0±2.1 | 589.9±0.8 | 155.2±2.4 | 149.4±1.6 | 148.4±1.2 | 214.6±2.2 | 211.7±1.6 |
long |
||||||||||||||
乔德 | 758.1±1.8 | 600.6±0.9 | 601.5±2.2 | 601.5±2.8 | 581.2±1.9 | 600.6±1.8 | 586.3±3.6 | 745.9 ± 3.6 | 685.8±2.2 | 183.1±1.0 | 182.5±0.5 | 182.6±0.6 | 553.2±1.5 | 488.0±0.8 |
Voigt 模数 | 1,360.8 ± 6.1 | 1,202.0 ± 2.1 | 600.0±2.4 | 600.0±3.0 | 607.0 ± 6.8 | 599.0±1.6 | 1,187.2 ± 2.6 | 1,439.6 ± 6.7 | 1,346.5 ± 2.9 | 197.9±0.7 | 208.2±0.6 | 208.0±0.4 | 548.9±1.4 | 1,326.4 ± 3.0 |
VoigtNoModulo | 844.5 ± 6.9 | 647.3±1.3 | 628.9±1.8 | 627.9±1.6 | 629.1±2.4 | 629.6±4.4 | 668.2±2.7 | 1,019.5 ± 3.2 | 715.1±8.2 | 224.3±4.8 | 219.0±1.0 | 219.0±0.6 | 561.7±2.5 | 769.4 ± 9.3 |
类型转换 | 366.1±0.8 | 246.2±1.1 | 245.3±1.8 | 244.6±1.1 | 154.6±1.6 | 154.3±0.5 | 257.4±1.5 | 591.8±4.1 | 590.4±1.3 | 154.5±1.3 | 135.4±8.3 | 132.9±0.7 | 132.8±1.2 | 177.4±2.3 |
现在我终于可以继续我的生活了:P
整数除法与舍入。
每次调用只执行 1 次除法,没有%
或*
或转换到/从浮点数,适用于正数和负数int
。见注 (1)。
n (numerator) = OPs myIntNumber;
d (denominator) = OPs myOtherInt;
下面的方法很简单。 int
除法向 0 舍入。对于负商,这是向上舍入,因此不需要特别的东西。对于正商,添加d-1
以实现向上舍入,然后执行无符号除法。
注意 (1) 通常的除法会在 2 的0
补MININT/-1
码机器上按预期失败。
int IntDivRoundUp(int n, int d) {
// If n and d are the same sign ...
if ((n < 0) == (d < 0)) {
// If n (and d) are negative ...
if (n < 0) {
n = -n;
d = -d;
}
// Unsigned division rounds down. Adding d-1 to n effects a round up.
return (((unsigned) n) + ((unsigned) d) - 1)/((unsigned) d);
}
else {
return n/d;
}
}
[编辑:删除测试代码,根据需要查看早期版本]
只需使用
int ceil_of_division = ((dividend-1)/divisor)+1;
例如:
for (int i=0;i<20;i++)
std::cout << i << "/8 = " << ((i-1)/8)+1 << std::endl;
一个小技巧是:
int divideUp(int a, int b) {
result = (a-1)/b + 1;
}
// Proof:
a = b*N + k (always)
if k == 0, then
(a-1) == b*N - 1
(a-1)/b == N - 1
(a-1)/b + 1 == N ---> Good !
if k > 0, then
(a-1) == b*N + l
(a-1)/b == N
(a-1)/b + 1 == N+1 ---> Good !
您可以添加一个非常接近(但不完全)等于 1 的常量,而不是在转换为 int 之前使用 ceil 函数 - 这样,几乎任何东西(除了完全或难以置信地接近实际整数的值)都会在被截断之前加一。
例子:
#define EPSILON (0.9999)
int myValue = (int)(((float)myIntNumber)/myOtherInt + EPSILON);
编辑:在看到您对另一篇帖子的回复后,我想澄清一下,这将四舍五入,而不是远离零 - 负数将变得不那么负数,正数将变得更加正数。