【问题标题】:Is there a reason some languages allow a negative modulus?某些语言允许负模数是否有原因?
【发布时间】:2012-01-09 06:24:02
【问题描述】:

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

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

【问题讨论】:

  • 没有定义说任何事情都应该是“总是积极的”。模算术是关于等价类的,任何代表都和其他代表一样好。
  • @OliCharlesworth:C99 是否对模运算指定了任何约束?
  • 如果有一个模运算产生规范代表,那将是很好
  • @Matt:是的,隐含的。它要求(a/b)*b + a%b == a,并且该除法截断为零。
  • @KerrekSB 这里的问题是负数与正数属于不同的等价类,这与数学模数不同。

标签: java c math modulus


【解决方案1】:

至少在 Java 中,它不是取模运算符 - 它是 remainder operator

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

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

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

【讨论】:

  • @JerryCoffin:是的,但是结合除法截断为 0,你最终会得到它。我将在我的答案中添加有关除法的内容。
  • C99 中也大体如此:操作符除了% 之外没有其他名字,但它的结果是“余数”ab 之间的相同欧几里得关系是正确的(除了 C 没有 INT_MIN % -1 的特殊情况 - 这是未定义的行为)。
  • @caf:你在哪里找到任何说INT_MIN % -1给UB的东西?我在 C 或 C++ 标准中的任何地方都看不到这一点。
  • @JerryCoffin:实际上我相信你是对的 - INT_MIN / -1 可能由于整数溢出而未定义,但似乎余数仍应为零。但是我可以通过该操作将gcc 转换为SIGFPE...
  • @JerryCoffin:似乎R. and Jens have been here before us - 当前的标准草案现在明确将其称为未定义行为。
【解决方案2】:

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

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

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) 并在(A < 0) xor (B < 0) 时翻转符号。

如果余数不为零,则浮点除法有一个额外的步骤,即从商中减去 1。

【讨论】:

  • 我想知道C99版本的签名除法实际上对哪些应用有用?根据我的经验,在需要精确性的情况下,我几乎总是必须专门处理负数,因此让涉及负数的除法返回未指定的值不会比让它们按照标准要求的那样有用。我可以看到一个关于不指定行为的论点(这可以在缺少签名除法指令的硬件上加快速度),但对所写的标准没有太大优势。
【解决方案3】:

来自Wikipedia(我的重点):

给定两个正数,a(被除数)和 n(除数),a modulo n(缩写为 mod n)可以认为是余数, 在 a 除以 n 时。例如,表达式“5 mod 4”将 计算为 1,因为 5 除以 4 余数为 1,而“9 mod 3" 将评估为 0,因为 9 除以 3 会留下 a 余数为 0;相乘后没有什么可以从 9 中减去 3 乘以 3。(请注意,用计算器进行除法不会 显示此操作在此处引用的结果,商 将表示为小数。) 当 a 或 n 为负时, 这个幼稚的定义被打破了,编程语言在 这些值是如何定义的。 虽然通常使用 n 都是整数,许多计算系统允许其他类型的 数字操作数。 n 的整数模数的范围是 0 到 n - 1。(n mod 1 始终为 0;n mod 0 未定义,可能 导致计算机编程中的“除以零”错误 语言)请参阅模块化算术以了解较旧的相关约定 应用于数论。

【讨论】:

  • 在数学上,整数余数总是在 0 到比除数小 1 的范围内,假设为正除数。这与编程语言(和 cpus)倾向于使用的丑陋定义相冲突。前一个定义非常有用,例如用于日期计算。后者是无用的,除非使用整数作为非整数值的近似值(例如固定点)。
  • @R..:我不会说“数学上”(除非你有一些真正的理论来支持这一点),而是“传统上”。 (对于正股息,这当然只与负除数有关。)
  • 除法的代数定义n/d我一直都知道是n=qd+r,其中0≤r<d。 :-)
  • @R..:实际上,这是一个很好的观点——它仍然只是惯例,但这是陈述除法定理的流行方式。 (但请注意,该定理对于任何其他固定选择陪集同样有效。)也就是说,这是一个很好的论据,支持将余数固定为正数。
【解决方案4】:

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

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

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

【讨论】:

    【解决方案5】:

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

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

    【讨论】:

    • 在某些标准中定义明确;例如,Java 和 C99。
    • 你确定 C99 定义了负数吗?
    • 是的。它是根据 / 运算符定义的,现在它的行为对于负值已经很好地定义了。
    【解决方案6】:

    在 C 或 Java 标准中,% 都没有被称为模运算符 - 相反,它返回 余数

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-16
      • 1970-01-01
      • 2020-07-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多