当编程语言设计人员决定取模运算结果采用什么符号时,使用什么原理?


9

通过走出去模运算(同时探索之间的区别我走进林荫道remmod我碰上了):

在数学中,模运算的结果是欧几里得除法的余数。但是,其他约定也是可能的。计算机和计算器具有多种存储和表示数字的方式;因此,它们对模运算的定义取决于编程语言和/或底层硬件。

问题:

  • 通过欧几里得除法运算,我发现此运算的余数始终为正(或0)。底层计算机硬件的哪些限制迫使编程语言设计师与数学有所不同?
  • 每种编程语言都有其预定义或未定义的规则,根据这些规则,取模运算的结果将得到其符号。制定这些规则时采用什么理由?并且如果底层硬件是问题所在,那么规则是否不应该根据编程语言而独立于编程语言而改变?

1
在我的代码中,我几乎总是需要模而不是余数。不知道为什么余数如此受欢迎。
CodesInChaos

8
相关有什么区别?Remainder vs Modulus-Eric Lippert的博客(由一位C#设计师设计,但我相信他是在做出这一决定后加入团队的)
CodesInChaos 2014年

1
如果您继续阅读Wikipedia文章(引用部分之外的内容),则可以很好地解释引用的内容。您对此解释感到困惑吗?
罗伯特·哈维

1
一个相关的问题是这些操作中的哪一个直接映射到CPU指令。在c中定义了实现,它符合直接在尽可能多的平台上映射到硬件的c哲学。因此,它没有指定CPU之间可能有所不同的内容。
CodesInChaos

5
@BleedingFingers编程经常使用接近于零的整数除法,例如(-3)/2 == -1。此定义可能有用。当您希望%与该划分保持一致时,x == (x/y)*y + x % y您最终会%在C#中使用的定义。
CodesInChaos 2014年

Answers:


7

所有现代计算机的硬件都足够强大,可以实现任何符号的mod操作,而不会影响性能(或影响很小)。这不是原因。

大多数计算机语言的普遍期望是(a div b)* b +(a mod b)= a。换句话说,将div和mod一起考虑可以将数字分为几个部分,这些部分可以可靠地再次放在一起。此要求在C ++标准中是明确的。该概念与多维数组的索引紧密相关。我经常使用它。

由此可见,如果b为正(通常是正数),div和mod将保留a的符号。

某些语言提供了与mod相关的'rem()'函数,并具有其他一些数学上的依据。我从来不需要使用它。参见例如Gnu C中的frem()。[编辑]


我认为这rem(a,b)是更likley是mod(a,b),如果是正面还是mod(a,b) + b如果它不是。
user40989 2014年

3
(a div b) * b + (a mod b) = a-这个非常 实际上,与Wikipedia描述的如何将其扩展到欧几里得除法中的负数相反(特别是“余数是四个永远不会为负数的数字中的唯一一个。”),这使我感到困惑,因为我总是被教导我余数可以为负数。在该级别的每个数学课上。
Izkata 2014年

@ user40989:我说我从没用过。查看编辑!
david.pfx 2014年

4

对于编程,通常需要X == (X/n)*n + X%n; 因此,模的定义方式取决于整数除法的定义方式。

考虑到这一点,您实际上是在问“ 编程语言设计人员决定整数除法的工作原理是什么?

实际上,大约有7个选择:

  • 四舍五入为负无穷大
  • 四舍五入为正无穷大
  • 四舍五入
  • “四舍五入到最接近”的几种版本(对0.5进行四舍五入的方式有所不同)

现在考虑-( (-X) / n) == X/n。我希望这是真的,因为其他任何东西似乎都不一致(对于浮点数是真的)和不合逻辑(可能是错误的原因,也可能是错过了优化)。这使得整数除法(朝任一无穷大舍入)的前两个选择不合需要。

所有“四舍五入”的选择对于编程来说都是一件痛苦的事情,特别是当您执行位图之类的操作时(例如offset = index / 8; bitNumber = index%8;)。

这使“可能最理智的”选择接近四舍五入,这意味着模数返回的值与分子的符号相同(或为零)。

注意:您还将注意到大多数CPU(我知道的所有CPU)都以相同的“四舍五入”的方式进行整数除法。这可能是出于相同的原因。


但是,截断式划分也有其自身的不一致之处:打破(a+b*c)/b == a % ba >> n == a / 2 ** n,因此下限式划分具有理智的行为。
dan04 2014年

您的第一个示例没有道理。您的第二个例子对程序员来说是一团糟:对于正a和正n而言,它是一致的;对于负a和正n而言,这取决于定义右移的方式(算术或逻辑),而对于负n则是破损的(例如1 >> -2 == a / 2 ** (-2))。
布伦丹2014年

第一个例子是一个错字:我的意思是说(a + b * c) % b == a % b,即,%运算符的除数是除数周期的,这通常很重要。例如,使用下限除法,可以day_count % 7为您提供星期几,但是采用截断除法,则可以中断到纪元之前的日期。
dan04 2014年

0

首先,我将重复一次,取模b应该等于a-b *(a div b),如果语言不提供模b,那么您将陷入一团糟。表达式a-b *(a div b)实际上是有多少种实现计算模b。

有一些可能的理由。首先是要获得最大速度,因此将div b定义为所使用的处理器将提供的一切。如果您的处理器具有“ div”指令,则div b就是该div指令所做的事情(只要它不是完全疯狂的东西)。

第二个是您想要一些特定的数学行为。首先让我们假设b>0。将div b的结果四舍五入为零是很合理的。因此,4 div 5 = 0,9 div 5 = 1,-4 div 5 = -0 = 0,-9 div 5 = -1。这将为您提供(-a)div b =-(a div b)和(-a)模b =-(a模b)。

这是合理的,但并不完美。例如(a + b)div b =(a div b)+ 1不成立,例如a = -1。在固定的b> 0的情况下,通常存在(b)个可能的值,使得div b给出相同的结果,除了从-b + 1到b-1的2b-1个值a,其中div b等于0这也意味着,如果a为负数,则模b将为负数。我们希望模b始终为0到b-1范围内的数字。

另一方面,请求a的连续值时,取模b的值应从0到b-1,然后再从0开始,这也是很合理的。并要求(a + b)div b应该是(a div b)+1。要实现这一点,您希望将div b的结果四舍五入为-infinity,因此-1 div b = -1。再次,有缺点。(-a)div b =-(a div b)不成立。重复除以2或除以b> 1的任何数字最终不会得到0的结果。

由于存在冲突,因此语言必须决定哪种优势对他们来说更重要,并据此做出决定。

对于负数b,大多数人无法完全理解div b和模b应该是什么,因此一种简单的方法是定义div b =(-a)div(-b)和a模b =(-a)如果b <0,则为(-b)模,或者对正b使用代码的自然结果。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.