除法为负股息,但四舍五入为负无穷大?

Division with negative dividend, but rounded towards negative infinity?

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

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

C++11 规范规定负被除数的除法会向零舍入。

有一个运算符或函数来进行除法并向负无穷大舍入是非常有用的(例如,为了在迭代范围时与正股息保持一致),标准库中是否有一个函数或运算符是我想要的吗?或者可能是编译器定义的 function/intrinsic 在现代编译器中执行此操作?

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

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

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

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

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

我不知道它有任何内在函数。我会简单地追溯对标准除法进行更正。

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 之前的版本,即没有将舍入除法标准化为零。

C++11 在具有 remquot 成员的结构中有 std::div(a, b) returns a % ba / b (所以余数和商原语)并且现代处理器只有一条指令。 C++11 做截断除法。

要对余数和商都做底除法,你可以这样写:

// 
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个版本的优缺点。

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

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

解释:在负数n的情况下,(-1 - n) / dq,然后-1 - n = qd + r一些r满足0 <= r < d .重新排列得到 n = (-1 - q)d + (d - 1 - r)。很明显0 <= d - 1 - r < d,所以d - 1 - r是底除运算的余数,-1 - q是商

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

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

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

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

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

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

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

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