除法为负红利,但四舍五入为负无穷大?

Division with negative dividend, but rounded towards negative infinity?

提问人:Bernard 提问时间:9/3/2016 最后编辑:Bernard 更新时间:5/21/2018 访问量:2174

问:

请考虑以下代码(在 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;
}

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

C++ C++11

评论

0赞 BiagioF 9/3/2016
我无法证明这一点。法典
2赞 9/3/2016
这是问题中的一个简单的错误。, .该问题要求以一种使(并且将是-11 / 3 == -3-11 % 3 == -2-11 / 3 == -4-11 % 31)
0赞 Bernard 9/3/2016
是的,我一开始犯了一个错误。我只是改变了它。谢谢!
2赞 BiagioF 9/3/2016
“C++1 规范说,负被除数的除法四舍五入为零。正股息以不同的方式表现?可能我没有得到这个问题
4赞 9/3/2016
@BiagioFesta 从 C++11 开始,正值和负值的除法始终四舍五入为零。在 C++11 之前,除法允许四舍五入为负无穷大,这相当于正值四舍五入到零,但负值则不然。

答:

2赞 user743382 9/3/2016 #1

标准库只有一个函数可用于执行您想要的操作:。你所追求的除法可以表示为 。但是,这假设它具有足够的精度来准确表示两者。否则,这可能会引入舍入错误。floorfloor((double) n / d)doublend

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

评论

0赞 shirleyquirk 11/26/2022
这是不正确的。floor(1.9) == 1,不舍入。
9赞 Tomek Sowiński 9/3/2016 #2

我不知道它的任何内在因素。我只是回顾性地对标准除法进行更正。

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 之前,即没有四舍五入到零的标准化。

评论

0赞 9/3/2016
我被负值弄糊涂了,但看起来我的答案可能弄错了。b
0赞 Bernard 9/3/2016
std::d iv from 替换除法和模可能会提供更好的性能。但话又说回来,也许编译器应该足够聪明,可以注意到。<cstdlib>
0赞 Tomek Sowiński 9/3/2016
@Bernard 最有可能。其余部分是许多架构上分裂的副产品。不过,检查拆卸不会有什么坏处。
2赞 TemplateRex 9/3/2016 #3

C++ 11 有一个返回 和 结构 with 和 成员(所以余数和商原语)的 和 ,并且现代处理器有一条指令。C++ 11 执行截断除法。std::div(a, b)a % ba / bremquot

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

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

一篇Microsoft研究论文讨论了3个版本的优缺点。

4赞 Mark Dickinson 9/3/2016 #4

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

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

解释:在否定的情况下,写为,则为一些满意。重新排列得到 .很明显,地板除法操作的其余部分也是如此,并且是商。nq(-1 - n) / d-1 - n = qd + rr0 <= r < dn = (-1 - q)d + (d - 1 - r)0 <= d - 1 - r < dd - 1 - r-1 - q

请注意,这里的算术运算都是安全的,不会溢出,而不管有符号整数的内部表示形式如何(二的补码、一的补码、符号大小)。

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

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

评论

3赞 Bernard 9/4/2016
我特别喜欢这个解决方案。 是如此对称,以至于它很漂亮。n >= 0 ? n / d : ~(~n / d)
0赞 dannyadam 5/21/2018 #5

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

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

当恰好其中一个操作数为负数时,运算符将进行天花板除法。/

对于最后一种情况,当正好有一个操作数为负且没有余数时,可以调整商(没有余数,地板除法和天花板除法的工作原理相同)。

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