在我测试的语言中,- (x div y )
不等于-x div y
;我在Python中测试了//
,在Ruby中测试了/
,在Perl 6中测试了div
; C has a similar behavior
这种行为通常是根据规范,因为div
通常被定义为the rounding down of the result of the division,但从算术的角度来看,它没有多大意义,因为它使{{1}根据符号以不同的方式表现,并导致混淆,例如this post on how it is done in Python。
这个设计决定背后是否有一些特定的理由,或者只是从头开始div
定义了这个?显然Guido van Rossum uses a coherency argument在博客文章中解释了如何在Python中完成,但如果你选择整理,你也可以有一致性。
答案 0 :(得分:44)
理想情况下,对于每个div
,我们希望有两个操作mod
和b>0
,令人满意:
(a div b) * b + (a mod b) = a
0 <= (a mod b) < b
(-a) div b = -(a div b)
1 div 2 = 0
1 mod 2 = 1
因为这是(1)和(2)的唯一整数解。因此,我们也可以通过(3)
0 = -0 = -(1 div 2) = (-1) div 2
,由(1)暗示
-1 = ((-1) div 2) * 2 + ((-1) mod 2) = 0 * 2 + ((-1) mod 2) = (-1) mod 2
使(-1) mod 2 < 0
与(2)相矛盾。
因此,我们需要在(1),(2)和(3)之间放弃一些属性。
一些编程语言放弃了(3),并使div
向下舍入(Python,Ruby)。
在某些(罕见)情况下,该语言提供多个除法运算符。例如,在Haskell中,我们div,mod
只满足(1)和(2),类似于Python,我们也quot,rem
仅满足(1)和(3)。后一对运算符以>为零,以返回负余数为代价,例如,我们有(-1) `quot` 2 = 0
和(-1) `rem` 2 = (-1)
。
C#也放弃了(2),并允许%
返回负余数。相干地,整数除法向零舍入。从C99开始,Java,Scala,Pascal和C也采用这种策略。
答案 1 :(得分:37)
浮点运算由IEEE754定义,并考虑了数字应用程序,默认情况下,以非常严格定义的方式舍入到最接近的可表示值。
计算机中的整数操作不由一般国际标准定义。语言(特别是C系列的语言)授予的操作倾向于遵循底层计算机提供的任何操作。有些语言比其他语言更有力地定义某些操作,但为了避免在他们那个时代的可用(和流行)计算机上实现过度困难或缓慢,将选择一个非常接近其行为的定义。
出于这个原因,整数运算在溢出时(对于加法,乘法和向左移位)环绕,并且当产生不精确时,向负无穷大舍入结果(分裂,右移)。 在两个补码二进制算术中,这两个都是整数各自末端的简单截断;处理角落案件的最简单方法。
其他答案讨论与语言可能提供的余数或模数运算符的关系。不幸的是他们倒退了。 剩余取决于除法的定义,而不是相反的方式,而模数可以独立于除法定义 - 如果两个参数恰好是正的并且除法向下舍入,它们的结果是相同的,所以人们很少注意到。
大多数现代语言都提供余数运算符或模数运算符,很少提供。库函数可以为关心差异的人提供其他操作,即剩余部分保留被除数的符号,而模数保留除数的符号。
答案 2 :(得分:12)
因为整数除法的含义是完整答案包含余数。
答案 3 :(得分:12)
Wikipedia has a great article on this,包括历史和理论。
只要一种语言满足(a/b) * b + (a%b) == a
的欧几里德分割属性,地板分割和截断分割都是连贯的,算术上合理的。
当然,人们喜欢争辩说一个人显然是正确的,另一个人显然是错的,但它更具有圣战的特征,而不是一个明智的讨论,而且通常更多地与他们早期偏好的选择有关。语言比什么都重要。他们也经常倾向于主张他们选择的%
,尽管首先选择/
然后选择匹配的%
可能更有意义。
%
遵循除数的标志显然是70%的学生猜测mod
或modulo
而不是remainder
。modulo
而非remainder
(即使这实际上反对反对他们的观点)。mod
而不是modulo
,因此它应该遵循Fortran的区别。 (这可能听起来很愚蠢,但可能是C99的关键。见this thread。)/
楼层或截断取决于符号,因此%
永远不会消极):
mod
感到惊讶。许多语言都提供。通常 - 如在Ada,Modula-2,一些Lisps,Haskell和Julia中 - 他们使用与{-1}}相关的名称作为Python风格的运算符,使用mod
作为C ++风格的运算符。但并非总是如此 - 例如,Fortran会调用相同的内容rem
和modulo
(如上面针对C99所述)。
我们不知道为什么Python,Tcl,Perl和其他有影响力的脚本语言大多选择地板。正如问题所述,Guido van Rossum的回答只能解释为什么他必须选择三个一致答案中的一个,而不是为什么他选择了他所做的那个。
然而,我怀疑C的影响是关键。大多数脚本语言(至少最初)是用C实现的,并从C语言中借用它们的运算符库存.C89的实现定义的mod
显然被破坏了,不适合像Tcl或Python这样的“友好”语言。 C调用运算符“mod”。所以他们选择模数,而不是余数。
<子> 1。尽管问题在于 - 并且很多人使用它作为参数 - 但实际上 与Python和朋友没有相似的行为。 C99需要截断分割,而不是地板。 C89允许,也允许任一版本的mod,因此无法保证除法属性,也无法编写可执行有符号整数除法的可移植代码。那只是破了。
答案 4 :(得分:7)
正如Paula所说,这是因为其余部分。
该算法建立在Euclidean division。
之上在Ruby中,你可以用一致性写这个重建红利:
puts (10/3)*3 + 10%3
#=> 10
它在现实生活中的作用相同。 10个苹果和3个人。好的,你可以在三个中切一个苹果,但超出整数组。
对于负数,保持一致性:
puts (-10/3)*3 + -10%3 #=> -10
puts (10/(-3))*(-3) + 10%(-3) #=> 10
puts (-10/(-3))*(-3) + -10%(-3) #=> -10
商总是向下舍入(沿负轴向下),提示符如下:
puts (-10/3) #=> -4
puts -10%3 #=> 2
puts (10/(-3)) #=> -4
puts 10%(-3) # => -2
puts (-10/(-3)) #=> 3
puts -10%(-3) #=> -1