有负股息的分部,但向负无穷大四舍五入?

时间:2016-09-03 08:03:58

标签: c++ c++11 division

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

但它不是我的意图的描述,也可能不是标准库函数或编译器内在的优化(如果存在)。

5 个答案:

答案 0 :(得分:3)

我不知道任何内在因素。我只是回顾性地对标准部门进行修正。

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

答案 1 :(得分:2)

标准库只有一个可用于执行所需操作的功能:floor。您所关注的部门可以表示为floor((double) n / d)。但是,这假设double具有足够的精确度来准确表示nd。如果没有,那么这可能会引入舍入误差。

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

答案 2 :(得分:1)

C ++ 11有一个std::div(a, b),它在结构中返回a % ba / b remquot成员(所以余数和商数都是原语并且现代处理器只有一条指令。 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个版本的优缺点。

答案 3 :(得分:1)

这是另一种可能的变体,对正除数和任意红利有效。

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的某些r0 <= r < d。重新排列给出n = (-1 - q)d + (d - 1 - r)。显然0 <= d - 1 - r < dd - 1 - r是分区操作的剩余部分,而-1 - q是商。

请注意,无论有符号整数的内部表示(二进制补码,一些补码,符号幅度),此处的算术运算都可以避免溢出。

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

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

答案 4 :(得分:0)

当操作数都是正数时,/运算符会进行分割。

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

当其中一个操作数为负数时,/运算符执行上限除法。

对于最后一种情况,只要一个操作数为负且没有余数(没有余数,平面除法和上限除法工作相同),就可以调整商。

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