23

今天在工作中,我和我的一位同事进行了一次有趣的讨论。当他遇到以下事情时,他感到很惊讶:

assert(-1 % 10 == -1)  //Expecting 9

所以当他来问我这个问题时,我告诉他“嗯,这是有道理的。当你将 -1 除以 10 时,你得到 0,剩下 -1。然而,他的论点是模运算符应该适用于“总是积极的”模型。我做了一些研究,发现他所指的模数看起来像这样:

令 q 为 a 和 n 的整数商。令 r 为余数。然后:

a = n * q + r

然而,我使用的定义似乎是模数的 Knuth 版本,即:

设 q 是 a 除以 n 的底。令 r 为余数。然后:

r = a - n * q

所以,我的问题是为什么它最终在 FORTRAN 标准(以及随后的 C 标准)中使模运算符截断为 0?将其称为“模数”而不是“余数”对我来说似乎用词不当(在数学中,答案确实应该是 9)。这与硬件如何进行划分有关吗?

以供参考:

TLDR;硬件是模数运算符向 0 截断的原因吗?

4

3 回答 3

16

将其称为“模数”而不是“余数”对我来说似乎用词不当(在数学中,答案确实应该是 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一直被错误命名。

于 2012-02-14T21:50:42.600 回答
5

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

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

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

从 C99 开始,C 中的整数除法向 0 截断。在 C89 中,它是实现定义的(并且%可以是 C89 中的模运算符)。C++ 还为整数除法执行向零截断。

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

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

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

C99 Rationale 说关于截断零整数除法:

然而,在 Fortran 中,结果总是会向零截断,而且开销似乎对数值编程社区来说是可以接受的。因此,C99 现在需要类似的行为,这应该有助于将代码从 Fortran 移植到 C。

gccC89 中的实现行为一直是向零截断。

C99、C++ 和 Java 中的余数运算符也是如此%,但并非所有编程语言中的余数运算符。在 Ruby 和 Python 中,%实际上是模运算符(在这些语言中,整数除法是朝着负无穷大方向进行的)。Haskell 和 Scheme 有两个独立的运算符:modand remfor Haskell,moduloand remainderfor Scheme。

于 2012-02-14T21:54:34.960 回答
1

恐怕问题出在对数学的误解。Congruence modulon 是一个等价关系,所以它只定义了等价类。因此,说“在数学中,答案真的应该是 9 ”是正确的,因为它也可能是 19、29 等等。当然它可以是-1或-11。数 n 的类中有无限个元素,它们是 n ≡ -1 mod(10)。

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

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

因此,正确的问题可能是:≡ -1 mod(10) 的数字类中的哪个元素将是 C++ 中 -1 % 10 的结果?答案是:-1 除以 10的余数。这并不神秘。

PS您对模数和Knuth的定义是,嗯,等效... :)

于 2012-02-14T22:28:09.490 回答