【问题标题】:Division with negative dividend, but rounded towards negative infinity?具有负股息的除法,但四舍五入到负无穷大?
【发布时间】:2017-01-11 07:04:41
【问题描述】:

考虑以下代码(在 C++11 中):

int a = -11, b = 3;
int c = a / b;
// now c == -3

C++11 规范规定,除以负除数时,会向零舍入。

如果有一个运算符或函数来进行除法并舍入到负无穷大(例如,为了在迭代范围时与正除数保持一致),那么在标准库中是否有一个函数或运算符来做什么?我想要?或者也许是在现代编译器中执行它的编译器定义的函数/内在函数?

我可以自己写,例如以下(仅适用于正除数):

int div_neg(int dividend, int divisor){
    if(dividend >= 0) return dividend / divisor;
    else return (dividend - divisor + 1) / divisor;
}

但它不会像我的意图那样描述,并且可能不像标准库函数或编译器内在函数(如果存在)那样优化。

【问题讨论】:

  • 我无法证明这一点。 Code
  • 这是问题中的一个简单错误。 -11 / 3 == -3-11 % 3 == -2。问题要求以使-11 / 3 == -4 (和-11 % 3 将是1)的方式进行划分
  • 是的,一开始我犯了一个错误。我刚改了。谢谢!
  • “C++11 规范说,除以负除法四舍五入为零。” 正除法以不同的方式表现?可能我没有得到问题
  • @BiagioFesta 从 C++11 开始,对于正值和负值,除法总是向零舍入。在 C++11 之前,除法允许舍入到负无穷大,这相当于将正值舍入为零,但对于负值则不然。

标签: c++ c++11 division


【解决方案1】:

当操作数都为正数时,/ 运算符会进行地板除法。

当操作数都为负数时,/ 运算符会进行地板除法。

当恰好有一个操作数为负时,/ 运算符会进行上限除法。

对于最后一种情况,当恰好一个操作数为负并且没有余数时,可以调整商(没有余数,下限除法和上限除法的工作方式相同)。

int floored_div(int numer, int denom) {
  int div = numer / denom;
  int n_negatives = (numer < 0) + (denom < 0);
  div -= (n_negatives == 1) && (numer % denom != 0);
  return div;
}

【讨论】:

    【解决方案2】:

    这是另一种可能的变体,适用于正除数和任意除数。

    int div_floor(int n, int d) {
        return n >= 0 ? n / d : -1 - (-1 - n) / d;
    }
    

    解释:在否定n的情况下,写q代表(-1 - n) / d,然后-1 - n = qd + r代表一些r满足0 &lt;= r &lt; d。重新排列给出n = (-1 - q)d + (d - 1 - r)。很明显0 &lt;= d - 1 - r &lt; d,所以d - 1 - r是地板除法的余数,-1 - q是商。

    请注意,这里的算术运算都是安全的,不考虑有符号整数的内部表示(二进制补码、二进制补码、符号大小)。

    假设有符号整数的二进制补码表示,一个好的编译器应该将两个-1-* 操作优化为按位求反操作。在我的 x86-64 机器上,条件的第二个分支被编译为以下序列:

    notl    %edi
    movl    %edi, %eax
    cltd
    idivl   %esi
    notl    %eax
    

    【讨论】:

    • 我特别喜欢这个解决方案。 n &gt;= 0 ? n / d : ~(~n / d) 对称得漂亮。
    【解决方案3】:

    C++11 有一个std::div(a, b),它在带有remquot 成员的结构中返回a % ba / b(所以余数和商基元),现代处理器有一条指令. C++11 进行截断除法。

    要同时对余数和商进行地板除法,您可以这样写:

    // http://stackoverflow.com/a/4609795/819272
    auto signum(int n) noexcept
    {
            return static_cast<int>(0 < n) - static_cast<int>(n < 0);
    }
    
    auto floored_div(int D, int d) // Throws: Nothing.
    {
            assert(d != 0);
    
            auto const divT = std::div(D, d);
            auto const I = signum(divT.rem) == -signum(d) ? 1 : 0;
            auto const qF = divT.quot - I;
            auto const rF = divT.rem + I * d;
    
            assert(D == d * qF + rF);
            assert(abs(rF) < abs(d));
            assert(signum(rF) == signum(d));
    
            return std::div_t{qF, rF};
    }
    

    最后,在您自己的库中还可以使用欧几里得除法(余数始终为非负数)也很方便:

    auto euclidean_div(int D, int d) // Throws: Nothing.
    {
            assert(d != 0);
    
            auto const divT = std::div(D, d);
            auto const I = divT.rem >= 0 ? 0 : (d > 0 ? 1 : -1);
            auto const qE = divT.quot - I;
            auto const rE = divT.rem + I * d;
    
            assert(D == d * qE + rE);
            assert(abs(rE) < abs(d));
            assert(signum(rE) != -1);
    
            return std::div_t{qE, rE};
    }
    

    a Microsoft research paper讨论3个版本的优缺点。

    【讨论】:

      【解决方案4】:

      标准库只有一个函数可以用来做你想做的事:floor。您要的除法可以表示为floor((double) n / d)。但是,这假设 double 具有足够的精度来准确地表示 nd。如果不是,那么这可能会引入舍入错误。

      就个人而言,我会使用自定义实现。但是您也可以使用浮点版本,前提是它更易于阅读并且您已验证结果对于您调用它的范围是正确的。

      【讨论】:

        【解决方案5】:

        我不知道它有任何内在函数。我会简单地回顾性地对标准划分进行更正。

        int div_floor(int a, int b)
        {
            int res = a / b;
            int rem = a % b;
            // Correct division result downwards if up-rounding happened,
            // (for non-zero remainder of sign different than the divisor).
            int corr = (rem != 0 && ((rem < 0) != (b < 0)));
            return res - corr;
        }
        

        请注意,它也适用于 C99 之前和 C++11 之前,即没有将舍入除法标准化为零。

        【讨论】:

        • 我对@9​​87654323@ 的负值感到困惑,但看起来我的回答可能有误。
        • std::div&lt;cstdlib&gt; 替换除法和模数可能会提供更好的性能。但话又说回来,也许编译器应该足够聪明才能注意到。
        • @Bernard 很有可能。其余部分是许多架构上除法的副产品。不过,检查拆卸不会有什么坏处。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-01
        • 2013-11-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多