为啥 operator% 被称为“模数”运算符而不是“余数”运算符?

Posted

技术标签:

【中文标题】为啥 operator% 被称为“模数”运算符而不是“余数”运算符?【英文标题】:Why is operator% referred to as the "modulus" operator instead of the "remainder" operator?为什么 operator% 被称为“模数”运算符而不是“余数”运算符? 【发布时间】:2012-02-14 21:45:55 【问题描述】:

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

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

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

令 q 为 a 和 n 的整数商。令 r 为余数。那么:

a = n * q + r

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

令 q 是 a 除以 n 的底。令 r 为余数。那么:

r = a - n * q

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

供参考:

Wikipedia on Modulus MSDN entry on the "modulus" operator (是的,我意识到它适用于 VS2003 ......我目前坚持使用它。悲伤) Modulus operator changes Don't assume positive remainder...

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

【问题讨论】:

您确实意识到这个问题和链接充满了避免使用负操作数取模的理由? 避免它不是问题的重点。 真的叫“模数”还是“模数”? “mod”有多少个音节? “余数”呢? @supercat “模数”和“余数”各有 3 个音节。 “mod”和“rem”各有 1 个音节。 @OllieFord:“REM”是 BASIC 中的关键字,可能也是一些 BASIC 派生语言,用于引入注释。此外,一些语言使用“%”来引入 cmets。因此,将“%”描述为“rem”运算符可能会引起一些混淆。 【参考方案1】:

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

【讨论】:

+1 我刚刚检查了 C++ 标准:二元 % 运算符从第一个表达式除以第二个表达式产生余数,因此 +1 表示运算符本身。现在另一个问题是 functional 中使用 operator% 的函子的名称,它被称为 modulus,所以至少有一部分问题是悬而未决的...... @DavidRodríguez-dribeas 不错的发现,我无法回答其背后的历史以及它为何如此命名。 我认为至少对我来说造成混淆的原因是,例如,如果您使用fmod,您将无法获得真正的模数,正如其名称所暗示的那样。似乎这两个词可以互换使用。但你是对的,真正的 C++ 标准确实说余数。我使用的原始来源是 MSDN,因为我专门寻找 VS2003。 msdn.microsoft.com/en-us/library/ty2ax9z9(v=vs.71).aspx。看起来它们是不正确的。 @ChadLaGuardia 也是一个没有完全指定返回值的错误命名函数的好例子。 C 和 C++ 已经通过添加更可靠的remainder 函数解决了这个问题,但我认为 VS2003 没有。【参考方案2】:

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

在 C++ 标准中,它被称为%operator,它产生除法的余数。在 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 基本原理说关于截断零整数除法:

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

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

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

【讨论】:

【参考方案3】:

我担心这个问题源于对数学的误解。同余模n是一个等价关系,所以它只定义了等价类。因此,正确地说“在数学中,答案确实应该是 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 的定义是,嗯,等价的......:)

【讨论】:

这些定义不相等。插入 -1 % 10 你就会明白为什么了。 为什么? “令 q 为 a 和 n 的整数商”与“令 q 为 a 除以 n 的底数”相同。定义的其余部分是完全相等的,假设你在等式的另一边翻转 n*q...;) 许多人会期望 % 操作会返回等价类的唯一代表。情况并非如此,因为它返回属于同一等价类(x 和 -x,x!=0)的整数对的不同代表。您关于 -1%10 可能是 -1 或 -11 的论点只有在始终使用与 [0, m-1] 的相同偏移量时才有意义(例如,如果 -1%10=-11,那么 9%10 也应该如此)是 -11 而不是 9)。 我并不是说在 C/C++ 中定义 % 的方式有什么问题。我只是说从 [0, m-1] 持续返回值的期望具有数学基础。重申一下:许多人会期望 % 操作将返回等价类的唯一代表。 (许多人会期望它的行为与它完全一样) @AmbrozBizjak:换句话说,从数学的角度来看,当且仅当 x 和 y 在同一个等价类 mod b 中时,声明 (x % b)==(y % b) 应该为真。一种语言可以例如定义 % 运算符以假定舍入除法(在这种情况下,结果的符号将指示应如何调整被除数以达到除数的精确倍数)并满足该期望,但在 C 中实现的运算符不.

以上是关于为啥 operator% 被称为“模数”运算符而不是“余数”运算符?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 C++ 使用模数时输出负数?

为啥允许使用泛型 lambda 而不允许使用带有模板化方法的嵌套结构?

如何使 std::vector 的 operator[] 编译在 DEBUG 中而不是在 RELEASE 中进行边界检查

为啥要使用三元运算符而不为“真”条件赋值 (x = x ?: 1)

为啥我们在赋值运算符重载中使用引用返回而不是在加减运算中?

为啥移动赋值运算符应该返回对 *this 的引用 [重复]