我对这些忽略模数运算的数学定义的语言(Java,C ...)很好奇。
在模块操作中返回负值有什么意义(根据定义,应该总是返回一个正数)?
至少在 Java 中,它不是取模运算符——它是余数运算符。
我相信以这种方式选择它的原因是为了使这种关系发挥作用(来自 JLS):
在二进制数值提升(第 5.6.2 节)之后,整数操作数的余数运算产生一个结果值,使得 (a/b)*b+(a%b) 等于 a。即使在被除数是其类型的最大可能量级的负整数并且除数是-1(余数是0)的特殊情况下,这个恒等式也成立。由这个规则得出,余数运算的结果只有当被除数为负时才能为负,只有当被除数为正时才能为正;此外,结果的大小总是小于除数的大小。
这种相等关系似乎可以用作定义的一部分。如果您将除法截断到零作为给定值,则会留下负余数。
来自维基百科(我的重点):
给定两个正数,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 未定义,可能导致计算机编程语言中的“除以零”错误)参见模运算数论中应用的较旧且相关的约定。
我怀疑余数运算符是故意设计成具有这些语义的,我同意这不是很有用。(你有没有写过一个日历程序来显示星期天、反星期六、反星期五、...、反星期一的日期?)
相反,负余数是定义整数除法的副作用。
A rem B := A - (A div B) * B
如果A div B
定义为trunc(A/B)
,则得到 C 的%
运算符。如果A div B
定义为floor(A/B)
,则得到 Python 的%
运算符。其他定义是可能的。
所以,真正的问题是:
因为这就是 C 的方式。
最初,C 没有指定/
应该如何处理负数。它把它留给了硬件。
在实践中,每个重要的 C 实现都使用截断除法,因此在 1999 年,这些语义正式成为 C 标准的一部分。
因为在无符号除法方面实现起来更容易(=更便宜)。您只需计算abs(A) div abs(B)
并翻转符号 if (A < 0) xor (B < 0)
。
如果余数不为零,则下限除法具有从商中减去 1 的附加步骤。
它们中的大多数未定义为返回模数。他们定义返回的是余数,正负值同样合理。
在 C 或 C++ 中,说它应该产生底层硬件产生的任何东西是很合理的。不过,这个借口/理由对 Java 来说几乎不起作用。
另请注意,在 C89/90 和 C++98/03 中,余数可以是正数或负数,只要余数和除法的结果一起工作 ( (a/b)*b+a%b == a
)。在 C99 和 C++11 中,规则被收紧,因此除法必须向零截断,如果有余数,则必须为负数。
返回模数负值的一个实际原因是实现模数的硬件指令会这样做。
所以标准留下了不明确的定义,以便编译器可以做任何对他们来说更简单的事情。
在 C 或 Java 标准中,均未将其%
称为模运算符 - 相反,它返回余数。
它被定义为为负股息返回负数,以便关系(a/b)*b + a%b == a
成立,只要a / b
是可表示的。由于除法运算符被定义为向零截断,这限制了余数的符号。