11

我对这些忽略模数运算的数学定义的语言(Java,C ...)很好奇。

在模块操作中返回负值有什么意义(根据定义,应该总是返回一个正数)?

4

6 回答 6

8

至少在 Java 中,它不是取模运算符——它是余数运算符

我相信以这种方式选择它的原因是为了使这种关系发挥作用(来自 JLS):

在二进制数值提升(第 5.6.2 节)之后,整数操作数的余数运算产生一个结果值,使得 (a/b)*b+(a%b) 等于 a。即使在被除数是其类型的最大可能量级的负整数并且除数是-1(余数是0)的特殊情况下,这个恒等式也成立。由这个规则得出,余数运算的结果只有当被除数为负时才能为负,只有当被除数为正时才能为正;此外,结果的大小总是小于除数的大小。

这种相等关系似乎可以用作定义的一部分。如果您将除法截断到零作为给定值,则会留下负余数。

于 2011-11-29T22:45:59.103 回答
6

来自维基百科(我的重点):

给定两个正数,a(被除数)和 n(除数),模 n(缩写为 mod n)可以被认为是 a 除以 n 的余数。例如,表达式“5 mod 4”的计算结果为 1,因为 5 除以 4 余数为 1,而“9 mod 3”的计算结果为 0,因为 9 除以 3 余数为 0;3 乘以 3 后,9 没有什么可减去的。(请注意,用计算器进行除法不会显示此操作所指的结果,商将表示为小数。)当 a 或n 是负数,这种幼稚的定义会失效,并且编程语言在定义这些值的方式上有所不同。尽管通常使用 a 和 n 都是整数来执行,但许多计算系统允许其他类型的数字操作数。n 的整数模数的范围是 0 到 n - 1。(n mod 1 始终为 0;n mod 0 未定义,可能导致计算机编程语言中的“除以零”错误)参见模运算数论中应用的较旧且相关的约定。

于 2011-11-29T22:46:31.037 回答
6

我怀疑余数运算符是故意设计成具有这些语义的,我同意这不是很有用。(你有没有写过一个日历程序来显示星期天、反星期六、反星期五、...、反星期一的日期?)

相反,负余数是定义整数除法的副作用。

A rem B := A - (A div B) * B

如果A div B定义为trunc(A/B),则得到 C 的%运算符。如果A div B定义为floor(A/B),则得到 Python 的%运算符。其他定义是可能的。

所以,真正的问题是:

为什么 C++、Java、C# 等使用截断整数除法?

因为这就是 C 的方式。

为什么C使用截断除法?

最初,C 没有指定/应该如何处理负数。它把它留给了硬件。

在实践中,每个重要的 C 实现都使用截断除法,因此在 1999 年,这些语义正式成为 C 标准的一部分。

为什么硬件使用截断除法?

因为在无符号除法方面实现起来更容易(=更便宜)。您只需计算abs(A) div abs(B)并翻转符号 if (A < 0) xor (B < 0)

如果余数不为零,则下限除法具有从商中减去 1 的附加步骤。

于 2011-11-30T00:46:27.583 回答
3

它们中的大多数未定义为返回模数。他们定义返回的是余数,正负值同样合理。

在 C 或 C++ 中,说它应该产生底层硬件产生的任何东西是很合理的。不过,这个借口/理由对 Java 来说几乎不起作用。

另请注意,在 C89/90 和 C++98/03 中,余数可以是正数或负数,只要余数和除法的结果一起工作 ( (a/b)*b+a%b == a)。在 C99 和 C++11 中,规则被收紧,因此除法必须向零截断,如果有余数,则必须为负数。

于 2011-11-29T22:47:38.410 回答
1

返回模数负值的一个实际原因是实现模数的硬件指令会这样做。

所以标准留下了不明确的定义,以便编译器可以做任何对他们来说更简单的事情。

于 2011-11-29T22:44:49.243 回答
1

在 C 或 Java 标准中,均未将其%称为模运算符 - 相反,它返回余数

它被定义为为负股息返回负数,以便关系(a/b)*b + a%b == a成立,只要a / b是可表示的。由于除法运算符被定义为向零截断,这限制了余数的符号。

于 2011-11-29T22:58:58.493 回答