带有负数的模运算符

198

为什么会出现以下这种操作:

std::cout << (-7 % 3) << std::endl;
std::cout << (7 % -3) << std::endl;

会给出不同的结果吗?

-1
1
3个回答

229

来自ISO14882:2011(e) 5.6-4:

二元运算符/生成商,二元运算符%生成第一个表达式除以第二个表达式的余数。如果/或%的第二个操作数为零,则行为未定义。 对于整型操作数,/运算符产生代数商,丢弃任何小数部分; 如果商a/b表示为结果类型,则(a/b)* b + a%b等于a。

其余是基本数学:

(-7 / 3) => -2
-2 * 3   => -6
so a % b => -1

(7 / -3) => -2
-2 * -3  => 6
so a % b => 1

请注意:

如果两个操作数都是非负数,则余数为非负数;如果不是,则余数的符号是实现定义的。

ISO14882:2003(e)中的内容在ISO14882:2011(e)中已经不存在了。


1
“代数商”这个表达在 ISO 14882:2003 中并不存在;那里只有“商”这个表达(而实现定义是 -7/3 的结果是 -2 还是 -3)。 - James Kanze
4
无论如何、不论你用什么方法和次数,对于有符号运算对象的除法和取模操作都是实现定义的。总有一种或另一种方式可供选择。该引语最重要的是保证了结果的确定性。(尽管我认为C99可能会解决这个问题。) - Kerrek SB
9
@JamesKanze:C++03仍然包含实现定义性,而C++11则删除了它。(并要求除法遵循Fortran的规则) - PlasmaHH
1
非常好的答案,我没有意识到在负参数的情况下使用“%”会出现这个问题。 - Chris A.
6
现在在C++11中已经对其进行了定义。 - Buge
显示剩余3条评论

45
a % b

C++中的默认情况:

(-7 / 3) => -2
-2 * 3   => -6
so a % b => -1

(7 / -3) => -2
-2 * -3  => 6
so a % b => 1

在Python中:

-7 % 3 => 2
7 % -3 => -2

在C++中转换为Python:

(b + (a % b)) % b

7
对于负数的符号结果,不存在“C++默认”。 - Sebastian Mach

24
在这种情况下(即当一个或两个操作数为负数时),符号是实现定义的。规范在§5.6/4(C++03)中表示:

二进制运算符/产生商,二进制运算符%产生第一个表达式除以第二个表达式的余数。如果/或%的第二个操作数为零,则行为未定义;否则(a/b)*b+a%b等于a。如果两个操作数都为非负数,则余数为非负数; 如果不是,则余数的符号是实现定义的

就C ++ 03而言,这就是所有语言所要说的了。

23
这就是语言所能表达的全部。C++11(像C99一样)采用Fortran的四舍五入规则(即截去小数部分)。实际上,所有硬件早在很久以前就已经采用了Fortran的规则,因此所有的实现都已经按照这种方式进行了。 (“实现定义”的目的是允许C/C++做任何硬件所做的事情。我认为一些非常古老的硬件确实总是向下取整,这样-7/3将得到-3,但那已经是遥远的过去了。) - James Kanze
2
@JamesKanze:如果b是2的幂,以前可以通过n % b计算为n & (b-1)。新标准要求将其计算为n < 0 ? n | -b : n & (b-1)。同样地,即使在支持算术移位的硬件上,n / b也不能简单地写成一个移位操作;在这种系统上,像n/16(如果nint32)这样的表达式必须写成n < 0 ? (n+15) >> 4 : n >> 4。我个人认为这个标准很糟糕。请注意,由于非2的幂除法本身就很慢,强制使用欧几里得行为并不会使其变慢太多。 - supercat
4
@JamesKanze: 在优化二的次幂的除法操作(即移位)时,现有硬件的行为是不同的。根据旧规则,foo /= 16可以写成 asr [dword foo],4。但根据新规则,最优表示需要更多指令[可能是 mov eax,[foo] / mov ebx,eax, asr eax,31 / lsr eax,28 / add eax,ebx / asr eax,4 / mov [foo],eax]。虽然不像除法指令那么慢,但比简单的移位要更加复杂繁琐。 - supercat
2
@supercat 抱怨一个算术函数在人类可读语言中的工作方式,因为为了保持一致性而必须执行多少机器代码指令,而不是使用一个只简化了所有可能值的微小子集操作的 hacky 快捷方式?哇哦。真是厉害。慢慢拍手 - CptRobby
3
你似乎忘记了 C 语言的整个设计哲学是围绕着使用巧妙的捷径来生成更快的汇编代码。 - Martin
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接