【问题标题】:Dividing two integers and rounding up the result, without using floating point将两个整数相除并将结果四舍五入,不使用浮点数
【发布时间】:2013-06-05 00:22:44
【问题描述】:

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

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

我觉得必须施放两个不同的时间有点过头了。 (extern int cast 只是为了关闭警告)

注意,否则我必须在内部强制转换为浮动

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

【问题讨论】:

  • 我怀疑有人会提出一个不那么难看的解决方案。你所拥有的是最清晰的方式来做到这一点。等等,你实际上并没有在你的真实代码中使用文字,是吗?如果是,那么只需将它们设为浮点文字即可。否则,铸造是要走的路。
  • 你应该使用double而不是float,除非你确定你永远不会有大于16777216的数字。
  • @yes123 不想打扰你,但优雅是什么意思,你的很容易阅读,但我发布的可能更数学,可能更快
  • @Raymond Chen 这个问题和你提到的那个问题,OP 都检查了一个答案,而不是仅适用于 unsigned 或使用浮点数。您的参考也仅适用于unsigned。由于存在适用于所有int 的非浮点解决方案,因此需要进一步的问答。
  • @Damon 我昨天也被抓住了。示例:对于 n = -7,d = 5,我们希望 -7/5 = -1.2 向上舍入到 -1。但是 (-7+5-1)/5 = -3/5 结果为 0。至于“为什么”。对于正数,添加d-1 会补偿向下舍入到 0 的正常除法。但是当 n 为负数(d 正数)时,正常除法会向上舍入(朝向 0)而不进行补偿,并给出带补偿的错误答案。

标签: c++


【解决方案1】:

假设myIntNumbermyOtherInt 都是肯定的,你可以这样做:

int myValue = (myIntNumber + myOtherInt - 1) / myOtherInt;

【讨论】:

  • 如果您怀疑其他人难以阅读,请附上写得很好的评论。
  • 其他开发人员如果发现它令人困惑,应该学会阅读它 - 这不是一个不常见的习语。
  • @yes123 让它成为一个不言自明的函数。 int myValue = divide_and_round_up(myIntNumber, myOtherInt);(或div_ceil
  • 假设两个输入都是正数,它确实有效。
  • int myValue = (myIntNumber - 1) / myOtherInt + 1; 在 myOtherInt 很大的情况下避免溢出。
【解决方案2】:

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

【讨论】:

  • 您可以省略 8 个括号,但您会收到警告。
  • @aaronman 它丢弃整数除法的小数部分,但这是等价的。
  • @aaronman 接受的答案是不正确。阅读假设。
  • @aaronman:因为这个网站会惩罚那些不“接受”答案的人。所以他们最终选择了最不坏的那个。
  • 你确定它是无分支的吗?我希望 &amp;&amp; 运算符引入一个分支,因为它需要短路评估。
【解决方案3】:

也许这样做更容易:

int result = dividend / divisor;
if(dividend % divisor != 0)
    result++;

【讨论】:

  • 让编译器优化这一切。这只是最好的答案。
【解决方案4】:

基准测试

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

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

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

TL;DR

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

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

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

结果在底部。

实现

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

@Jwodder's version (Jwodder)

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

@Ben Voigt's version 使用模数 (VoigtModulo)

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

@Ben Voigt's version 不使用模数 (VoigtNoModulo)

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

OP's implementation(类型转换)

template<typename T>
inline T divCeilTypeCast(const T& numerator, const T& denominator)
{
    return (int)std::ceil((double)numerator / denominator);
}

方法

在单个批次中,除法执行 1 亿次。针对编译器/优化级别、使用的数据类型和使用的实现的每种组合计算十个批次。下面显示的值是所有 10 个批次的平均值,以毫秒为单位。给出的错误是standard deviations

使用的整个源代码可以在here 找到。此外,您可能会发现 this 脚本很有用,它使用不同的编译器标志编译和执行源代码。

整个基准测试是在 i7-7700K 上执行的。使用的编译器版本是 GCC 10.2.0 和 clang 11.0.1。

结果

现在不用多说,结果如下:

DataType
Algorithm
GCC
-O0
-O1 -O2 -O3 -Os -Ofast -Og clang
-O0
-O1 -O2 -O3 -Ofast -Os -Oz
unsigned
Jwodder 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 ?
VoigtModulo 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
TypeCast 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
Jwodder 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
VoigtModulo 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
TypeCast 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
Jwodder 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 ?
VoigtModulo 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
TypeCast 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
Jwodder 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
VoigtModulo 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
TypeCast 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

【讨论】:

  • 当然还有另一个复杂的情况——当输入数据是 64 位整数(longunsigned long)时,“TypeCast”不会产生正确的结果,因为转换为 @987654348 @ 失去精度。使用 float 的 OP 版本的 TypeCast 也会对 32 位整数产生不正确的结果(此处测试的所有情况)。
【解决方案5】:

四舍五入的整数除法。

每次调用只执行 1 个除法,没有 %* 或与浮点的转换,适用于正负 int。见注(1)。

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

以下方法很简单。 int 除法向 0 舍入。对于负商,这是一个向上舍入,因此不需要什么特别的。对于正商,添加d-1 进行向上取整,然后执行无符号除法。

注意 (1) 通常除以 0 会搞砸事情,MININT/-1 在 2 的补码机器上按预期失败。

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

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

【讨论】:

    【解决方案6】:

    随便用

    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;
    

    【讨论】:

    • 由于下溢而失败。
    • 请举例?
    • dividendstd::limits&lt;int&gt;::min()(假设dividendint),你会得到[未定义的行为](如果在计算表达式期间,结果不是数学定义的或不在其类型的可表示值范围内,行为未定义。[...])。即使行为是环绕的,你也会得到错误的信号。
    【解决方案7】:

    要做一个小技巧:

    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 !
    

    【讨论】:

      【解决方案8】:

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

      例子:

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

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

      【讨论】:

        猜你喜欢
        • 2020-10-01
        • 1970-01-01
        • 2012-10-19
        • 1970-01-01
        • 2013-06-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多