为什么整数除法在许多脚本语言中向下舍入?

时间:2018-05-20 09:37:15

标签: python ruby perl6 integer-division raku

在我测试的语言中,- (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中完成,但如果你选择整理,你也可以有一致性。

(灵感来自this question by PMurias in the #perl6 IRC channel

5 个答案:

答案 0 :(得分:44)

理想情况下,对于每个div,我们希望有两个操作modb>0,令人满意:

  1. (a div b) * b + (a mod b) = a
  2. 0 <= (a mod b) < b
  3. (-a) div b = -(a div b)
  4. 然而,这是一种数学不可能性。如果以上都是真的,我们就会

    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的欧几里德分割属性,地板分割和截断分割都是连贯的,算术上合理的。

当然,人们喜欢争辩说一个人显然是正确的,另一个人显然是错的,但它更具有圣战的特征,而不是一个明智的讨论,而且通常更多地与他们早期偏好的选择有关。语言比什么都重要。他们也经常倾向于主张他们选择的%,尽管首先选择/然后选择匹配的%可能更有意义。

  • 地板(如Python):
    • 不亚于唐纳德·克努特所暗示的权威。
    • %遵循除数的标志显然是70%的学生猜测
    • 操作员通常被视为modmodulo而不是remainder
    • “C做到了” - 这甚至都不是真的。 1
  • 截断(如C ++):
    • 使整数除法与IEEE浮点除法更加一致(在默认舍入模式下)。
    • 更多CPU实现它。 (在历史的不同时期可能不是真的。)
    • 操作员阅读modulo而非remainder(即使这实际上反对反对他们的观点)。
    • 除法属性在概念上更多地是关于余数而不是模数。
    • 运算符是mod而不是modulo,因此它应该遵循Fortran的区别。 (这可能听起来很愚蠢,但可能是C99的关键。见this thread。)
  • “Euclidean”(与Pascal一样 - /楼层或截断取决于符号,因此%永远不会消极):
    • Niklaus Wirth认为没有人会对积极的mod感到惊讶。
    • Raymond T. Boute后来认为,你不能用其他任何规则天真地实施欧几里德分裂。

许多语言都提供。通常 - 如在Ada,Modula-2,一些Lisps,Haskell和Julia中 - 他们使用与{-1}}相关的名称作为Python风格的运算符,使用mod作为C ++风格的运算符。但并非总是如此 - 例如,Fortran会调用相同的内容remmodulo(如上面针对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