提问人:Bernard 提问时间:9/3/2016 最后编辑:Bernard 更新时间:5/21/2018 访问量:2174
除法为负红利,但四舍五入为负无穷大?
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 规范说,除法为负数的除法四舍五入为零。
有一个运算符或函数来做除法,四舍五入到负无穷大是非常有用的(例如,在迭代范围时与正被除数保持一致),那么标准库中是否有函数或运算符可以做我想要的?或者也许是编译器定义的函数/内部函数,在现代编译器中可以做到这一点?
我可以自己写,例如以下内容(仅适用于正除数):
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
n
d
就个人而言,我会选择自定义实现。但是,您也可以使用浮点版本,如果它更易于阅读,并且您已经验证了结果对于您调用的范围是正确的。
评论
我不知道它的任何内在因素。我只是回顾性地对标准除法进行更正。
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 之前,即没有四舍五入到零的标准化。
评论
b
C++ 11 有一个返回 和 结构 with 和 成员(所以余数和商原语)的 和 ,并且现代处理器有一条指令。C++ 11 执行截断除法。std::div(a, b)
a % b
a / b
rem
quot
要对余数和商进行地板除法,您可以这样写:
// 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个版本的优缺点。
这是另一种可能的变体,适用于正除数和任意被除数。
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 <= r < d
n = (-1 - q)d + (d - 1 - r)
0 <= d - 1 - r < d
d - 1 - r
-1 - q
请注意,这里的算术运算都是安全的,不会溢出,而不管有符号整数的内部表示形式如何(二的补码、一的补码、符号大小)。
假设有符号整数的 2 补码表示,一个好的编译器应该将两个运算优化为按位否定运算。在我的 x86-64 机器上,条件的第二个分支被编译为以下序列:-1-*
notl %edi
movl %edi, %eax
cltd
idivl %esi
notl %eax
评论
n >= 0 ? n / d : ~(~n / d)
当操作数均为正数时,运算符进行地板除法。/
当操作数均为负数时,运算符进行地板除法。/
当恰好其中一个操作数为负数时,运算符将进行天花板除法。/
对于最后一种情况,当正好有一个操作数为负且没有余数时,可以调整商(没有余数,地板除法和天花板除法的工作原理相同)。
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;
}
评论
-11 / 3 == -3
-11 % 3 == -2
-11 / 3 == -4
-11 % 3
1
)