为什么操作符%被称为“模数”运算符而不是“余数”运算符?

23
今天在工作中,我和一位同事进行了一次有趣的讨论。他很惊讶地发现发生了以下情况:
assert(-1 % 10 == -1)  //Expecting 9

当他来问我这个问题时,我告诉他:“嗯,那很有道理。当你将-1除以10时,余数是-1,商是0。”然而,他的论点是模运算应该遵循“始终为正”的模型。我做了一些研究,发现他所指的模数如下所示: 引用: 设q是a和n的整数商,r是余数。则: a = n * q + r 然而,I使用的定义似乎是Knuth版本的模数,它是这样的: 引用: 设q是a除以n的底数。r是余数。则: r = a - n * q 因此,我的问题是为什么在FORTRAN标准(随后是C标准)中,模运算符会向0截断?对我来说,把它称为“模数”而不是“余数”似乎是一个错误的称呼(在数学上,答案应该是9)。这是否与硬件执行除法有关? 参考资料:

简而言之:硬件是使取模运算符向0截断的原因吗?


3
你应该意识到这个问题和链接中充满了避免使用负数取模的原因。 - H H
5
回避并不是问题的重点。 - Chad La Guardia
它真的被称为“模数”还是“模”?“模”有几个音节?“余数”呢? - supercat
1
@supercat,“modulus”和“remainder”每个单词都有3个音节。“mod”和“rem”每个单词只有1个音节。 - OJFord
1
@OllieFord:“REM”是BASIC中的一个关键字,可能在一些基于BASIC的语言中也是如此,它用于引入注释。此外,一些语言使用“%”来引入注释。因此,将“%”描述为“rem”运算符可能会导致一些混淆。 - supercat
3个回答

17
对我来说,称其为“模数”而不是“余数”似乎是一个错误的用词(在数学中,答案应该是9)。
C语言将其称为%运算符,并将其结果称为余数。C++从C语言中复制了这一点。两种语言都没有将其称为模运算符。这也解释了为什么余数是负数:因为/运算符向0截断,而(a / b) * b + (a % b)应该等于a。
编辑:David Rodríguez正确指出,C++确实定义了一个名为std::modulus的模板类,它调用operator%。在我看来,这个类的命名不太合适。稍微挖掘一下,它是从STL继承而来的,在那里它已经被命名为现在的样子。STL的下载说:“STL是在SGI MIPSproTM C++ 7.0、7.1、7.2和7.2.1上开发的。”,据我所知,没有实际拥有编译器和硬件,MIPSpro将除法传递给CPU,MIPS硬件向0截断,这意味着std::modulus一直被命名错误。

2
+1 我刚刚查了一下C++标准:二进制%运算符返回第一个表达式除以第二个表达式的余数,所以对于这个运算符本身给个赞。现在另一个问题是functional中使用operator%的函数对象的名称,它被称为modulus,所以至少问题的一部分还没有解决... - David Rodríguez - dribeas
@DavidRodríguez-dribeas 很好的发现,我无法回答它背后的历史以及为什么它被命名为这个名字。 - user743382
1
我认为困惑的根源,至少对我而言,是如果你使用 fmod,你将不会得到一个真正的模数,正如其名称所示。似乎这两个词可以互换使用。但您是正确的,真正的C++标准确实表示余数。我使用的原始来源是MSDN,因为我特别在寻找VS2003。http://msdn.microsoft.com/en-us/library/ty2ax9z9(v=vs.71).aspx。他们似乎是错误的。 - Chad La Guardia
1
@ChadLaGuardia 这也是一个函数命名不当且返回值未完全指定的好例子。C和C ++已经通过添加更可靠的remainder函数来解决了这个问题,但我认为VS2003没有它。 - user743382

5

%是C和C++中的余数运算符。

在C++标准中,它被称为%运算符,并且产生除法的余数。在C标准中,它被称为%运算符,自C99以来它实际上是一个余数运算符。模数和余数运算符在负值方面有所不同。

%运算符在C和C++中定义为a == (a / b * b) + a % b

C中向0截断整数除法是自C99以来进行的。在C89中,它是实现定义的(并且%可以是C89中的模数运算符)。对于整数除法,C++也执行向零截断。

当向0截断时,%是一个余数运算符,结果的符号是被除数的符号。当向负无穷大截断时,%是一个模数运算符,结果的符号是除数的符号。

关于C更改整数除法有关截断的实现定义行为的原因,来自C委员会的Doug Gwyn说:

C99强制实施了与Fortran兼容的要求,以试图吸引更多的Fortran程序员,并帮助将Fortran代码转换为C。

C99 Rationale在截断向零整数除法方面表示:

然而,在Fortran中,结果将始终截断至零,并且数字编程社区似乎可以接受此开销。因此,C99现在需要类似的行为,这应有助于从Fortran到C的移植。

gcc中,C89的实现行为一直是向零截断。

因此,%是C99、C++和Java中的余数运算符,但并不是所有编程语言中的余数运算符。在Ruby和Python中,%实际上是模数运算符(在这些语言中,整数除法是朝负无穷大进行的)。Haskhell和Scheme具有两个单独的运算符:Haskell的modrem,以及Scheme的moduloremainder


1

恐怕问题出在对数学的误解上。模n同余是一种等价关系,因此它只定义了等价类。因此,说“在数学中,答案确实应该是9”是不正确的,因为它也可以是19、29等等。当然,它也可以是-1或-11。在模10意义下,与n同余的数字有无限个。

http://en.wikipedia.org/wiki/Modular_arithmetic

http://en.wikipedia.org/wiki/Congruence_relation

所以,正确的问题应该是:在模10意义下与-1同余的数字类中的哪个元素将成为C++中-1%10的结果?答案是:-1除以10的余数。没有什么神秘的。

PS您对模和Knuth的定义是等价的... :)


这些定义并不相等。插入-1%10,你就会明白为什么了。 - Chad La Guardia
为什么?“设q是a除以n的整数商”,这与“设q是a除以n的底部”相同。鉴于您将n*q翻转到等式的另一侧,定义的其余部分完全相等...;) - ascanio
3
许多人会期望百分号(%)运算返回等价类的唯一代表。但实际上并非如此,因为对于同一等价类中属于不同整数对(x 和 -x,且 x!=0),它会返回不同的代表。你提出的 -1%10 可以是 -1 或 -11 的论点只有在总是使用相同的 [0, m-1] 偏移量时才有意义(例如,如果 -1%10=-11,则 9%10 也应该是 -11 而不是 9)。 - Ambroz Bizjak
我不明白你在说什么...我先是在谈论数学,然后是关于C++语言及其实际行为。在真实的C++世界中,我非常确定%运算符对于一个输入只产生一个结果(否则它将是不确定的..),但在数学中,它们原则上都是有效的。我相信这是一个典型的情况,在这种情况下,对于一个真实世界的编程语言的疑问来自于对数学概念的错误理解。在这种情况下,假设-1 % 10应该是9是完全错误的。 - ascanio
1
我并不是说 C/C++ 中 % 运算符的定义有什么问题。我只是想说,期望它始终返回 [0, m-1] 范围内的值是有数学基础的。再次强调:许多人会期望 % 运算符返回等价类的唯一代表(而许多人也会期望它的行为与现在完全相同)。 - Ambroz Bizjak
2
换句话说,从数学角度来看,当且仅当x和y在模b下处于相同的等价类时,语句(x%b)==(y%b)应该为真。例如,一种语言可以定义运算符以假定四舍五入除法(在这种情况下,结果的符号将指示如何调整被除数以达到除数的精确倍数)并满足该期望,但是C中实现的运算符不是这样的。 - supercat

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