13

我需要将两个数字相除并四舍五入。有没有更好的方法来做到这一点?

int myValue = (int) ceil( (float)myIntNumber / myOtherInt );

我发现不得不投两个不同的时间有点过头了。(extern int cast 只是为了关闭警告)

注意我必须在内部强制转换才能浮动

int a = ceil(256/11); //> Should be 24, but it is 23
              ^example
4

8 回答 8

25

假设myIntNumbermyOtherInt都是积极的,你可以这样做:

int myValue = (myIntNumber + myOtherInt - 1) / myOtherInt;
于 2013-06-09T00:52:30.147 回答
10

在 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 返回商和余数的处理器上会更快(并且编译器足够聪明,可以使用它)。

于 2013-06-09T02:16:02.310 回答
3

也许这样做更容易:

int result = dividend / divisor;
if(dividend % divisor != 0)
    result++;
于 2017-08-03T11:53:57.613 回答
3

基准

由于答案中显示了许多不同的方法,并且没有一个答案实际上证明在性能方面有任何优势,我试图自己对它们进行基准测试。我的计划是写一个答案,其中包含一个简短的表格和一个明确的答案,哪种方法最快。

不幸的是,事情并没有那么简单。(从来没有。)似乎舍入公式的性能取决于使用的数据类型、编译器优化级别。

在一种情况下,从一种方法到另一种方法的速度提高了 7.5 倍。因此,对某些人来说,影响可能很大。

TL;博士

对于long整数,使用类型转换的朴素版本float实际上std::ceil是最快的。这对我个人来说很有趣,因为我打算将它与size_t通常定义unsigned long.

对于普通int的 s 这取决于您的优化级别。对于较低级别的@Jwodder 的解决方案表现最好。因为最高级别std::ceil是最佳级别。除了一个例外:对于铿锵/unsigned int组合@Jwodder's 仍然更好。

接受答案的解决方案从未真正超越其他两个。但是,您应该记住,@Jwodder 的解决方案不适用于底片。

结果在底部。

实现

在这里回顾一下我基准测试和比较的四种方法:

@Jwodder 的版本(Jwodder)

template<typename T>
inline T divCeilJwodder(const T& numerator, const T& denominator)
{
    return (numerator + denominator - 1) / denominator;
}

@Ben Voigt 的模数版本 (VoigtModulo)

template<typename T>
inline T divCeilVoigtModulo(const T& numerator, const T& denominator)
{
    return numerator / denominator + (((numerator < 0) ^ (denominator > 0))
        && (numerator%denominator));
}

@Ben Voigt不使用模数的版本 (VoigtNoModulo)

inline T divCeilVoigtNoModulo(const T& numerator, const T& denominator)
{
    T truncated = numerator / denominator;
    return truncated + (((numerator < 0) ^ (denominator > 0))
        && (numerator - truncated*denominator));
}

OP的实现(TypeCast)

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

于 2021-01-19T01:12:02.297 回答
1

整数除法与舍入。

每次调用只执行 1 次除法,没有%*或转换到/从浮点数,适用于正数和负数int。见注 (1)。

n (numerator) = OPs myIntNumber;  
d (denominator) = OPs myOtherInt;

下面的方法很简单。 int除法向 0 舍入。对于负商,这是向上舍入,因此不需要特别的东西。对于正商,添加d-1以实现向上舍入,然后执行无符号除法。

注意 (1) 通常的除法会在 2 的0MININT/-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;
  }
}

[编辑:删除测试代码,根据需要查看早期版本]

于 2013-06-09T04:06:33.093 回答
1

只需使用

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;
于 2016-03-16T17:32:10.090 回答
0

一个小技巧是:

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 !
于 2017-09-26T15:07:09.673 回答
-1

您可以添加一个非常接近(但不完全)等于 1 的常量,而不是在转换为 int 之前使用 ceil 函数 - 这样,几乎任何东西(除了完全或难以置信地接近实际整数的值)都会在被截断之前加一。

例子:

#define EPSILON (0.9999)

int myValue = (int)(((float)myIntNumber)/myOtherInt + EPSILON);

编辑:在看到您对另一篇帖子的回复后,我想澄清一下,这将四舍五入,而不是远离零 - 负数将变得不那么负数,正数将变得更加正数。

于 2013-06-09T00:56:29.417 回答