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

jjm*_*elo 43 ruby python integer-division perl6 raku

在我测试的语言中,- (x div y )不等于-x div y; 我已经//在Python中,/在Ruby中,div在Perl 6中进行了测试; C有类似的行为.

这种行为通常是根据规范,因为div通常被定义为舍入向下分工的结果,但它不从的观点算术点做出很大的意义,因为它使div行为以不同的方式取决于标志,它引起混乱,如这篇文章如何在Python中完成.

这个设计决策背后是否有一些特定的理由,或者只是div从头开始定义?显然Guido van Rossum在博客文章中使用了一致性论证,解释了它是如何在Python中完成的,但如果你选择整理,你也可以有一致性.

(受到PMurias在#perl6 IRC频道中的这个问题的启发)

chi*_*chi 44

理想情况下,我们希望为每个操作提供两个操作,div并且mod满意b>0:

  1. (a div b) * b + (a mod b) = a
  2. 0 <= (a mod b) < b
  3. (-a) div b = -(a div b)

然而,这是数学上的不可能性.如果以上都是真的,我们就会有

1 div 2 = 0
1 mod 2 = 1
Run Code Online (Sandbox Code Playgroud)

因为这是(1)和(2)的唯一整数解.因此,我们也可以通过(3),

0 = -0 = -(1 div 2) = (-1) div 2
Run Code Online (Sandbox Code Playgroud)

其中,(1),暗示

-1 = ((-1) div 2) * 2 + ((-1) mod 2) = 0 * 2 + ((-1) mod 2) = (-1) mod 2
Run Code Online (Sandbox Code Playgroud)

制造(-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也采用这种策略.

  • @EricLippert:出于好奇:如果是这样的话,为什么运算符的id字符串`op_Modulus`而不是`op_Remainder`? (10认同)
  • C#放弃了(2).C#中的`%`运算符不是"mod"运算符.它是"余数"运算符,余数可以是负数. (7认同)
  • @Heinzi:没有任何理由让我能够确定.这只是一个很长的小错误列表中的一个,这些错误并不重要. (3认同)

Chr*_*tix 38

浮点运算由IEEE754定义,并考虑了数字应用程序,默认情况下,以非常严格定义的方式舍入到最接近的可表示值.

计算机中的整数操作不是由一般国际标准定义的.语言(特别是C系列的语言)授予的操作倾向于遵循底层计算机提供的任何操作.有些语言比其他语言更有力地定义某些操作,但为了避免在他们那个时代的可用(和流行)计算机上实现过度困难或缓慢,将选择一个非常接近其行为的定义.

出于这个原因,整数运算倾向于在溢出(对于加法,乘法和向左移位)上回绕,并且在产生不精确的结果时(对于除法和向右移位)向负无穷大舍入. 这两个都是在二进制补码二进制算术中它们各自末端的简单截断 ; 处理角落箱的最简单方法.

其他答案讨论与语言可能提供的余数或模数运算符的关系.不幸的是他们倒退了. 余数取决于除法的定义,而不是相反的方式,而模数可以独立于除法来定义 - 如果两个参数恰好是正的并且除法向下舍入,它们的结果是相同的,所以人们很少注意到.

大多数现代语言提供余数运算符或模数运算符,很少提供.库函数可以为关心差异的人提供其他操作,即剩余部分保留被除数的符号,而模数保留除数的符号.

  • @iGian就是这样,我不知道一个随便 - 你就是那个提起它的人.除非另外明确指定,否则有几个静默地将整数操作数强制转换为浮点并执行浮点除法; 例如.BBC BASIC将`/`定义为FP div,将'DIV`定义为具有向下舍入的整数.6502甚至没有DIV指令,所以这是一个子程序. (3认同)
  • @iGian 好吧,事实是大多数语言 * 不* 对整数除法进行四舍五入,原因是我在答案中概述的。通常他们要么调用 CPU 的 DIV 指令并接受它的作用,要么调用一个实现欧几里德除法的子程序,通常做同样的事情。一个合理的问题可能是“为什么当大多数其他语言没有时,语言 X 会向上(或最接近,或向零)四舍五入?” - 但您必须指定 X,并且答案将特定于该语言。 (2认同)

Pau*_*mas 12

因为整数除法的含义是完整答案包括余数.

  • @ SeanFrancisN.Ballais显然只是一个惯例问题.[modiki操作上的维基百科条目](https://en.wikipedia.org/wiki/Modulo_operation):*当a或n为负数时,天真定义会中断,编程语言在定义这些值方面会有所不同.* (5认同)

aba*_*ert 12

维基百科有一篇很好的文章,包括历史和理论.


只要一种语言满足欧几里德划分属性(a/b) * b + (a%b) == a,地板划分和截断划分都是连贯的和算术上合理的.


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

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

许多语言都提供了这两种语言.通常 - 如在Ada,Modula-2,一些Lisps,Haskell和Julia中 - 它们使用与modPython样式运算符和remC++样式运算符相关的名称.但并非总是如此,Fortran语言,例如,要求同样的事情modulo,并mod(如上面提到的C99).


我们不知道为什么Python,Tcl,Perl和其他有影响力的脚本语言大多选择了地板.正如问题所述,Guido van Rossum的回答只能解释为什么他必须选择三个一致答案中的一个,而不是为什么他选择了他所做的那个.

但是,我怀疑C的影响是关键.大多数脚本语言(至少最初)是用C语言实现的,并从C语言中借用它们的运算符库存.C89的实现定义%明显被破坏,不适合像Tcl或Python这样的"友好"语言.C调用运算符"mod".所以他们选择模数,而不是余数.


尽管问题在于 - 并且许多人将其用作参数-C实际上并没有与Python和朋友类似的行为.C99需要截断分割,而不是地板.C89允许,也允许任一版本的mod,因此无法保证除法属性,也无法编写可执行有符号整数除法的可移植代码.那只是打破了.


iGi*_*ian 7

正如宝拉所说,这是因为其余部分.

该算法建立在欧几里德分部.

在Ruby中,您可以使用一致性来编写重建股息:

puts (10/3)*3 + 10%3
#=> 10
Run Code Online (Sandbox Code Playgroud)

它在现实生活中的作用相同.10个苹果和3个人.好的,你可以切三个苹果,但超出整数.

对于负数,保持一致性:

puts (-10/3)*3 + -10%3 #=> -10
puts (10/(-3))*(-3) + 10%(-3) #=> 10
puts (-10/(-3))*(-3) + -10%(-3) #=> -10
Run Code Online (Sandbox Code Playgroud)

商总是向下舍入(沿负轴向下),提醒如下:

puts (-10/3) #=> -4
puts -10%3 #=> 2

puts (10/(-3)) #=> -4
puts 10%(-3) # => -2

puts (-10/(-3)) #=> 3
puts -10%(-3) #=> -1 
Run Code Online (Sandbox Code Playgroud)