除法有负的红利,但四舍五入到负无穷
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;
}
但是它不会像我的意图那样描述,并且可能不像标准库函数或编译器固有的(如果存在的话)那样优化。
我不知道它有什么内在的。我将简单地对标准除法进行回顾性修正。
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之前,即没有标准化的四舍五入除到零。
这是另一种可能的变体,适用于正除数和任意股息。
int div_floor(int n, int d) {
return n >= 0 ? n / d : -1 - (-1 - n) / d;
}
说明:在n
为负的情况下,对(-1 - n) / d
写q
,对满足0 <= r < d
的某个r
写-1 - n = qd + r
。重新排列得到n = (-1 - q)d + (d - 1 - r)
。很明显,0 <= d - 1 - r < d
,所以d - 1 - r
是除法运算的余数,-1 - q
是商。
请注意,这里的算术运算都是安全的,不会发生溢出,无论有符号整数的内部表示(2的补数,1的补数,符号大小)如何。
假设有符号整数的补码表示为2,一个好的编译器应该将两个-1-*
操作优化为按位求负操作。在我的x86-64机器上,该条件的第二个分支被编译成以下序列:
notl %edi
movl %edi, %eax
cltd
idivl %esi
notl %eax
标准库只有一个函数可以用来做你想做的事情:floor
。您需要的除法可以表示为floor((double) n / d)
。然而,这假设double
有足够的精度来准确地表示n
和d
。如果不是,那么这可能会引入舍入错误。
就我个人而言,我会使用自定义实现。但是你也可以使用浮点版本,如果它更容易阅读,并且你已经验证了你调用它的范围的结果是正确的。
c++ 11有一个std::div(a, b)
,它返回a % b
和a / b
的struct,其中包含rem
和quot
成员(即剩余和商原语),并且现代处理器只有一条指令。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};
}
有一篇微软的研究论文讨论了三个版本的优缺点。
当两个操作数都为正数时,/
运算符执行分层除法。
当两个操作数都为负时,/
运算符执行分层除法。
当其中一个操作数为负时,/
运算符执行上限除法。
对于最后一种情况,当恰好有一个操作数为负且没有余数时,商可以调整(没有余数时,下限除法和上限除法的工作相同)。
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;
}
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 在除法中不需要四舍五入
- 使用 BMI 计算器对C++中的数字进行四舍五入的问题
- 为什么将双精度转换为 int 似乎在第 16 位数字之后将其四舍五入?
- 在TFHE(完全快速同态加密)上执行除法
- 使用 int 表示浮点除法 C++
- 四舍五入C++
- 而循环:简单的除法程序输出零,不明白为什么
- 余数除法和不允许除以零 (c++) 时遇到问题
- 在数学上将浮点数四舍五入到 N 位小数
- 如何在C++中对OpenCV垫的小数进行四舍五入?
- 如何确定涉及 C++ 中除法的算术表达式的数据类型
- 如何使用除法和for_each?
- C++:奇怪的除法输出
- 分配给浮点数的积分文字除法 - 为什么结果是错误的?
- 除法函数返回错误的值
- 检查向量是否使用除法和阻抗算法进行排序
- 向下四舍五入到五个c++的最近倍数
- 除法有负的红利,但四舍五入到负无穷
- 有没有办法防止在 opencv 矩阵除法中四舍五入