“mod”和“remainder”的区别是什么?

212

我的朋友说“mod”和“remainder”有区别。

如果是这样,在C和C++中有什么不同呢?在C中,'%'表示“mod”还是“rem”?


2
对于负操作数,它很可能是未定义的。 - Basile Starynkevitch
@BasileStarynkevitch: 你的意思是,当出现负数操作数时,差异取决于实现吗?我需要一个“是”或“否”,因为这个问题给我带来了麻烦。谢谢! - songhir
1
%代表取余数。详细答案请参见此处 -> http://blogs.msdn.com/b/ericlippert/archive/2011/12/05/what-s-the-difference-remainder-vs-modulus.aspx - wim
21
@David:这个问题涉及术语的含义。如果你说这个问题没有意义,尽管有几个人理解了提问者的意图,那么我认为你需要更具体地说明你所说的“意思”是什么;-) - Steve Jessop
3
这些术语存在竞争性的含义。如果问题能够明确指定要使用哪种竞争性含义,那么就可以说明它们之间的区别。 - David Heffernan
7个回答

198
欧几里得除法(模除)和C语言的余数运算符(%)有所区别。例如:
-21模4等于3,因为-21+4×6等于3。
但是,向0截断的-21除以4(即C语言的/运算符)得到-5,余数为-1(C的-21% 4)。
对于正值,欧几里得除法和向零截断的除法没有区别。

请参见https://en.wikipedia.org/wiki/Euclidean_division#Other_intervals_for_the_remainder - C语言选择将余数向0截断(自C99起必需),强制负商具有负余数范围。即使在C89中,当标准允许使用欧几里得除法进行/运算时。

如果商a/b可表示,则表达式(a/b)*b + a%b应等于a

(-21/4) * 4 + (-21%4) == -21; C99及更高版本要求(-5) * 4 + (-1),而不是欧几里得的-63


26
在C89标准中,符号“%”的含义由具体实现定义:它可以表示余数,也可以表示模数(即始终为正数),因为在C89中,整数除法可以向负无穷舍入而不是向0舍入。因此,在C89中,“-5 / 2”可能是“-2余-1”,也可能是“-3余1”,具体实现只需要记录一下就好了。但是,C99取消了这种灵活性,所以现在“-5 / 2”始终为“-2”。 - Steve Jessop
4
实际上,模数是什么并不清楚。根据上下文和语言的不同,似乎有许多不同的定义。请参阅关于模运算的维基百科文章。在某些情况下,它实际上与余数相同。 - Rudy Velthuis
19
可以有人解释一下第一次计算的步骤吗? 如何将 -21 mod 4 得出 3? 为什么计算式是 -21 + 4 x 6? 为了求得 -21 除以 4 的余数,我们可以将 -21 不断加上 4 直到结果大于等于 0,此时所加的总和就是余数。具体来说,我们可以进行以下操作:
  • -21 + 4 = -17
  • -17 + 4 = -13
  • -13 + 4 = -9
  • -9 + 4 = -5
  • -5 + 4 = -1
  • -1 + 4 = 3
因此,-21 mod 4 的结果为 3。对于计算式 -21 + 4 x 6,我们可以先计算 4 x 6 得到 24,再将其加上 -21 得到 -21 + 24 = 3,这与前面求得的 -21 mod 4 的结果相同。
- Oz Edri
18
要获得某个数对4取模的结果,你需要加上4的整数倍直到得到0到3之间的数。对于-21,这个整数是6,因为-21 + 4 x 6在0到3之间。 - David Schwartz
11
实际上这是错误的。根据定义(参见https://en.wikipedia.org/wiki/Euclidean_division ),余数应该是正数,因此-21÷4的结果为-6,余数为3。 - marcosh
显示剩余8条评论

70

在 C 语言中,% 表示余数1

.../ 运算符的结果是代数商,舍弃任何小数部分...(通常称为“向零取整”)C11dr §6.5.5 6

% 运算符的操作数必须具有整数类型。C11dr §6.5.5 2

/ 运算符的结果是第一个操作数除以第二个操作数的商;% 运算符的结果是余数。C11dr §6.5.5 5


什么是“mod”和“remainder”的区别?

C 语言没有定义“mod”或“modulo”运算符/函数,例如在欧几里得除法其他模运算中使用的整数模函数。

C 定义了余数

让我们将通过%运算符计算出的“余数”与欧几里得“模”进行比较。

a 为负数时,“欧几里得模”与 C 的 a % b运算结果不同。

 // a % b, the remainder after an integer division that truncates toward 0.
 7 %  3 -->  1  
 7 % -3 -->  1  
-7 %  3 --> -1  
-7 % -3 --> -1   

“Mod”或者叫做欧几里得除法中的模数。结果总是0或正数。

 7 modulo  3 -->  1
 7 modulo -3 -->  1
-7 modulo  3 -->  2
-7 modulo -3 -->  2

候选人取模代码:

int modulo_Euclidean(int a, int b) {
  int m = a % b;
  if (m < 0) {
    // m += (b < 0) ? -b : b; // avoid this form: it is UB when b == INT_MIN
    m = (b < 0) ? m - b : m + b;
  }
  return m;
}

关于浮点数的说明:即使被称为“fmod”,double fmod(double x, double y)与欧几里得除法的“mod”不同,但类似于C整数余数:

fmod函数计算x/y的浮点余数。 C11dr §7.12.10.1 2

fmod( 7,  3) -->  1.0
fmod( 7, -3) -->  1.0
fmod(-7,  3) --> -1.0
fmod(-7, -3) --> -1.0

消歧义:C语言中也有一个同名函数double modf(double value, double *iptr),该函数将参数value分解为整数和小数两部分,每个部分的类型和符号与参数相同。这与“mod”讨论无关,只是名称相似。


[编辑于2020年12月]

对于那些希望在所有情况下获得适当功能的人,可以使用改进版的modulo_Euclidean()函数,该函数检测到mod(x,0)并且在modulo_Euclidean2(INT_MIN, -1)中具有良好的结果而没有未定义行为。受4个具有完全定义行为的取模不同实现的启发。

int modulo_Euclidean2(int a, int b) {
  if (b == 0) TBD_Code(); // perhaps return -1 to indicate failure?
  if (b == -1) return 0; // This test needed to prevent UB of `INT_MIN % -1`.
  int m = a % b;
  if (m < 0) {
    // m += (b < 0) ? -b : b; // avoid this form: it is UB when b == INT_MIN
    m = (b < 0) ? m - b : m + b;
  }
  return m;
}

1 在 C99 之前,C 中对于 % 的定义仍然是除法的 余数,但是这时的 / 允许负商向下舍入而不是 "朝零方向截断"。 参见为什么在 C89 中进行整数除法会得到不同的值? 因此,在一些早期的 C 编译中,% 运算可以像欧几里得除法的 “mod” 一样运行。 上述的 modulo_Euclidean() 函数也适用于这种旧的余数计算方式。


2
要在C语言中实现欧几里得除法和模函数,请参阅《计算机科学家的除法和模运算》(Division and Modulus for Computer Scientists)。如果您只知道被除数可能为负数,但除数始终为正数,则可以更快地运行:https://godbolt.org/g/63UqJo。相关问题:[x86汇编问题,要求非负模数](http://stackoverflow.com/questions/40861023/how-do-i-get-a-positive-modulo-on-a-negative-dividend)。 - Peter Cordes

16

余数的符号与被除数相同,模数的符号与除数相同。

余数是两个整数相除后剩余的部分,而模数是当余数和除数异号时余数和除数之和,当余数和除数同号时则为两数相除后的剩余部分。

余数示例:

10 % 3 = 1 [这里被除数为正数,所以结果也将为正数]

-10 % 3 = -1 [这里被除数为负数,所以结果也将为负数]

10 % -3 = 1 [这里被除数为正数,所以结果也将为正数]

-10 % -3 = -1 [这里被除数为负数,所以结果也将为负数]

模数示例:

5 % 3 = 2 [这里被除数为正数,所以余数和除数都是正数。由于余数和除数都是同号的,所以结果将与余数相同]

-5 % 3 = 1 [这里被除数为负数,所以余数为负数,除数为正数。由于余数和除数异号,所以结果将为余数和除数的和 -2 + 3 = 1]

5 % -3 = -1 【被除数为正数5,因此余数也是正数,并且除数为负数。由于余数和除数异号,所以结果为余数和除数的和,即2 + (-3) = -1】

-5 % -3 = -2 【被除数为负数-5,因此余数也是负数,并且除数也是负数。由于余数和除数同号,所以结果就是余数本身】

我希望这样能够清楚地区分余数和模数。


"取模的符号与除数相同。" 和 "这里使用的“取模”是多种取模定义之一,肯定不是欧几里得除法。" - chux - Reinstate Monica
1
我怀疑你正在使用的模数定义是向下取整除法 - chux - Reinstate Monica

6
在C、C++和许多其他编程语言中,“%”表示余数,而不是模运算符。
例如,在操作“-21 / 4”中,整数部分为“-5”,小数部分为“-0.25”。余数等于小数部分乘以除数,所以我们的余数是“-1”。JavaScript使用余数运算符并证实了这一点。

console.log(-21 % 4 == -1);

模数运算就像你有一个“时钟”。想象一下一个圆圈,其上的值为0、1、2和3,在12点钟、3点钟、6点钟和9点钟位置。顺时针绕着时钟走quotient次,我们就得到了模数操作的结果,或者在我们的例子中,当quotient为负数时,反时针走,得到3。
注意:模数始终与除数的符号相同,余数与商的符号相同。当至少一个是负数时,将除数和余数相加得到模数。

2
你的意思是余数的符号总是与被除数相同吗?7除以-3的商为-2,但7 mod -3的余数为1。 - dx_over_dt
“余数与商具有相同的符号” --> 不,在C语言中不是这样的。7/-3 的商为 -2,而 7%-3 的余数为 1 - chux - Reinstate Monica

3
% is a remainder(leftover after dividend / divisor) NOT modulus. 

您可以使用余数(%)的关系编写自己的模函数。
  ((n%m)+m)%m

  where `n` is the given number and `m` is the modulus

以下是在n = (-7, 7)和m=3范围内余数和模数值之间的差异:

n       -7 -6 -5 -4 -3 -2 -1  0  1  2  3  4  5  6  7  
------------------------------------------------------------------------- 
%(-m)   -1  0 -2 -1  0 -2 -1  0  1  2  0  1  2  0  1  =>  remainder
% m     -1  0 -2 -1  0 -2 -1  0  1  2  0  1  2  0  1  =>  remainder
mod m    2  0  1  2  0  1  2  0  1  2  0  1  2  0  1  =>  ((n%m)+m)%m
mod(-m) -1  0 -2 -1  0 -2 -1  0 -2 -1  0 -2 -1  0 -2  =>  ((n%m)+m)%m

记住以下提示:

n%(-m)   = +(remainder)
(-n)%(m) = -(remainder)
sign of 'm' doesn't matter

n mod (-m) = -(result)
(-n) mod m = +(result)
sign of 'n' doesn't matter

For +ve 'n' and '%(-m)' or '%m' or 'mod m' gives the same remainder

3

在模运算中,模数指的是进行算术除法后剩余的值,通常称为余数。在C/C++语言中,%被正式定义为余数运算符。例如:

7 % 3 = 1  // dividend % divisor = remainder

需要讨论的是如何处理对此%操作的负输入。现代C和C++针对此操作产生有符号余数值,结果的符号始终与被除数的符号相同,不考虑除数的符号。


-3
在数学中,模运算的结果是欧几里得除法的余数。然而,其他约定也是可能的。计算机和计算器有各种存储和表示数字的方式;因此它们对模运算的定义取决于编程语言和/或底层硬件。
 7 modulo  3 -->  1  
 7 modulo -3 --> -2 
-7 modulo  3 -->  2  
-7 modulo -3 --> -1 

2
欧几里得除法断言0 ≤ r < |b|,这意味着余数也就是“模运算”始终至少为0。你使用的是哪个定义导致了-2和-1? - chux - Reinstate Monica
先生,我不知道,但我刚刚谷歌了7模-3 --> -2和-7模-3 --> -1,请解释一下为什么会这样。 - shub sharma
1
谷歌使用不同的模数定义(有符号模数?)而不是维基欧几里得除法(由Raymond T. Boute描述)。这里讨论了更多的差异。故事的寓意是:当a,b为正数时,a%ba模b具有相同的含义。C99精确地用负值定义了。C将其称为“余数”。在关于负数的世界中,“模数”有各种定义。C规范仅在正数的情况下使用“模数”。 - chux - Reinstate Monica

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