在我测试过的语言中,- (x div y )不等于-x div y; 我已经在Python中测试了//,在Ruby中测试了/,在Perl 6中测试了div; C具有类似的行为。
该行为通常是按照规范进行的,因为div通常被定义为除法结果的四舍五入,但是从算术的角度来看,这没有多大意义,因为它使div在 取决于符号的不同方式,这会引起混乱,例如有关如何在Python中完成操作的帖子。
该设计决策背后是否有一些特定的理论依据,还是只是从头开始定义div? 显然,Guido van Rossum在博客文章中使用了一个一致性参数,该参数解释了Python的完成方式,但是如果选择四舍五入,您也可以具有一致性。
(受PMurias在#perl6 IRC频道中提出的问题的启发)
在Python中,FWIW 被称为地板分割。 试试这个:.7 .1。 请注意,它不会计算为int。
FWIW,在Python中,您可以使用双重否定来获得上限划分:例如-(-23 10)
理想情况下,我们希望每个b>0都有两个操作div和mod满足:
(a div b) * b + (a mod b) = a
0 <= (a mod b) < b
(-a) div b = -(a div b)
但是,这在数学上是不可能的。如果以上所有条件都成立,我们将有
1
21 div 2 = 0
1 mod 2 = 1
因为这是(1)和(2)的唯一整数解。因此,到(3),
10 = -0 = -(1 div 2) = (-1) div 2
由(1)表示
1-1 = ((-1) div 2) * 2 + ((-1) mod 2) = 0 * 2 + ((-1) mod 2) = (-1) mod 2
制作与(2)相矛盾的(-1) mod 2 < 0。
因此,我们需要放弃(1),(2)和(3)中的某些属性。
一些编程语言放弃(3),而将div舍入(Python,Ruby)。
在某些(很少)情况下,该语言提供了多个除法运算符。例如,在Haskell中,与Python类似,div,mod仅满足(1)和(2),并且quot,rem仅满足(1)和(3)。后一对运算符将除法舍入为零,以返回负余数为代价,例如,我们有(-1) `quot` 2 = 0和(-1) `rem` 2 = (-1)。
C#也放弃(2),并允许%返回负的余数。连贯地,整数除法向零舍入。从C99开始的Java,Scala,Pascal和C也采用了这种策略。
C#放弃了(2)。 C#中的%运算符不是" mod"运算符。它是"余数"运算符,余数可以为负。
@EricLippert:出于好奇:如果是这种情况,为什么操作员ID字符串为op_Modulus而不是op_Remainder?
@Heinzi:我没有确定的充分理由。这只是很长的小错误列表中的一个,这些小错误并不重要。
只需将方程式的两边都除以零!
浮点运算是由IEEE754定义的,并考虑了数字应用程序,默认情况下,以非常严格定义的方式舍入到最接近的可表示值。
通用国际标准未定义计算机中的整数运算。语言(尤其是C家族的语言)授予的操作倾向于遵循基础计算机提供的任何内容。某些语言比某些语言更健壮地定义某些操作,但是为了避免在当时可用(和流行)的计算机上实现过于困难或缓慢的实现,它们会选择一种非常接近其行为的定义。
因此,整数运算倾向于在溢出时回绕(用于加法,乘法和左移),并且在产生不精确的结果时(用于除法和右移)会向负无穷大舍入。在二进制补码二进制算术中,这两个都是在整数各自的结尾处的简单截断;处理极端情况的最简单方法。
其他答案则讨论了语言与除法运算可能提供的余数或模运算符之间的关系。不幸的是,他们倒退了。余数取决于除法的定义,而不是相反的定义,而模数可以独立于除法定义-如果两个参数碰巧都是正数且除法向下取整,则它们的结果相同,因此人们很少注意到。
大多数现代语言都提供余数运算符或模数运算符,很少会同时提供。库函数可以为关心差异的人们提供其他操作,即余数保留除数的符号,而模数保留除数的符号。
这是一个有趣的观点,也因为您是指模块化算术。如果问题是:"为什么整数除法会在许多脚本语言中取整?",您是否认为相同的答案可能适用?
@iGian好吧,事实是,由于我在答案中概述的原因,大多数语言都无法进行整数除法。通常,他们要么调用CPU DIV指令并只接受它的功能,要么调用实现欧几里得除法的子例程,并且通常执行相同的操作。一个合理的问题可能是"为什么在大多数其他语言都没有的情况下,语言X会四舍五入(或趋近于零,或趋近于零)?" -但您必须指定X,答案将特定于该语言。
那么,许多脚本语言会舍入整数除法,因为CPU DIV基本上是欧几里得除法的实现? ----我很好奇为什么需要四舍五入。您能推荐一种语言X吗?
@iGian就是这样,我不知道一个副手-您是提出它的人。除非另有明确说明,否则有几种方法会默默地将整数操作数强制转换为浮点并执行浮点除法。例如。 BBC BASIC将定义为FP div,将DIV定义为带舍入的整数。 6502甚至没有DIV指令,因此带有子程序。
在C和C ++和Java中,除法向零截断,向右移向负无穷大。
如何处理负无穷大是处理极端情况的最简单方法?为什么不趋向于零或正无穷大或下一个整数?
@ GOTO0我的答案就在那:二进制截断补码算术。具有除法指令的现代CPU通常会这样做,许多软件子例程也会这样做。但是,在子例程中实现它的一种方法是对操作数的正形式执行无符号除法,然后再固定符号。将趋近于零。
事实并非如此:整数除法根本不涉及截断(在所有中间步骤中仅计算整数值)。同样,在x86和类似体系结构上,整数除法指令始终四舍五入为零。
@ GOTO0:"向零"舍入模式的名称是"截断"。例如浮点trunc()函数:en.cppreference.com/w/c/numeric/math/trunc。如果将整数除法视为产生实数的数学除法,然后将其四舍五入为整数,则舍入舍入模式将表示该行为。当然,那不是其在内部实际实施的方式。 BTW,对于算术右移,向-Inf显然是2s补码中最简单的:您只需移入符号位的副本即可得到。
但是这个答案有一个错误。在大多数语言/ ISA中,似乎声称整数除法通常会向-Inf取整。那对于所有具有除法指令的现代CPU体系结构都是不正确的,对于ISO C99和ISO C ++ 11也是不正确的,它们都精确地指定了有符号整数除法舍入语义(以匹配所有现代CPU的功能)。在C ++中使用负数进行整数除法舍入。先前的修订版将其留给实现来选择一种行为(以提高使用不同语义的硬件的效率)。
@PeterCordes:实际上,"截断"仅描述符号幅度表示(例如IEEE-754浮点)中的舍入为零。在二进制补码表示法中,截断实际上是朝着负无穷大舍入,这就是从最有意义的一端转移符号位副本的简单权宜之计。特别是,如果0xFFFF为-1,则0xFFFE为-2。
@Chromatix:啊,我明白了这个术语的意义。但是我的意思是数学截断(en.wikipedia.org/wiki/Truncation),即截断是指将小数点(或二进制)点后的某些位置截断,或者将小数部分去除/归零。截断是数学中的一个术语,不仅仅是计算机科学/工程学,其含义是向零舍入。我认为,不管实现如何,都将舍入为零是最有意义的。 (即使这意味着您不能简单地截断2s补码表示的位。)
Wikipedia在这方面有很棒的文章,包括历史和理论。
只要语言满足(a/b) * b + (a%b) == a的欧几里得除法属性,则地板除法和截断除法都是连贯且在算术上有意义的。
当然,人们喜欢争辩说,一个显然是正确的,而另一个显然是错误的,但它比起明智的讨论更具有圣战的性质,并且通常更多地与选择他们的早期首选语言有关。其他。他们通常也倾向于主要为他们选择的%争论,尽管先选择/然后选择匹配的%可能更有意义。
地板(如Python):
就像唐纳德·克努斯(Donald Knuth)所建议的那样。
%跟随除数的符号显然是大约70%的学生猜测
通常将运算符读取为mod或modulo而不是remainder。
" C做到了" —甚至都不是事实。1
截断(如C ++):
使整数除法与IEEE浮点除法更加一致(在默认的四舍五入模式下)。
更多的CPU实现了它。 (在历史的不同时期可能并非如此。)
运算符被读取为modulo而不是remainder(即使这实际上与他们的观点背道而驰)。
从概念上讲,除法属性更多地是关于余数而不是模量。
运算符被读取为mod而不是modulo,因此应遵循Fortran的区分。 (这听起来很愚蠢,但可能是C99的关键所在。请参阅此线程。)
"欧几里得"(如Pascal- /底数或根据符号截断,因此%永远不会为负):
尼克劳斯·沃思(Niklaus Wirth)认为,没有人对肯定的mod感到惊讶。
雷蒙德·布特(Raymond T. Boute)后来提出,您不能凭借其他任何一条规则天真地实现欧几里得除法。
多种语言都提供。通常,与Ada,Modula-2,一些Lisps,Haskell和Julia一样,它们使用与mod有关的名称(对于Python风格的运算符)和与rem有关的名称对于C ++风格的运算符。但并非总是如此-例如,Fortran调用相同的内容modulo和mod(如上面C99所述)。
我们不知道为什么Python,Tcl,Perl和其他有影响力的脚本语言大多选择地板。正如问题中提到的,Guido van Rossum的答案仅说明了为什么他必须选择三个一致的答案之一,而不是为什么他选择了自己选择的答案。
但是,我怀疑C的影响是关键。大多数脚本语言(至少在最初是用C语言实现的),并从C语言借用其操作员清单。C89的实现定义的%显然已损坏,不适合Tcl或Python之类的"友好"语言。 C将运算符称为" mod"。因此它们采用模数,而不是余数。
1。尽管问题说了什么(很多人都使用它作为参数),但C的行为实际上与Python和其朋友并不相似。 C99需要截断分割,而不是分割。 C89允许使用任一版本,也允许使用任一版本的mod,因此无法保证除法属性,也无法编写具有符号整数除法的可移植代码。真是坏了。 sub>
一些Lisps:Common Lisp定义了mod和rem,请参见lispworks.com/documentation/HyperSpec/Body/f_mod_r.htm和lispworks.com/documentation/HyperSpec/Body/f_floorc.htm
@coredump和其他一些Lisps使用其他名称。 MacLisp只提供了一个,而Racket今天也做同样的事情。 Scheme提供了三个(modulo,remainder,和Wirth样式的mod)。我认为答案不需要与世界上每种语言的方言相关联;说"某些Lips"使用mod / rem样式命名似乎足够了。
因为整数除法的含义是完整答案包括余数。
...而根据定义,其余部分必须始终为正?显然,并非每种语言都适用:在C99中,其余符号与红利符号相同
是的,这是正确的,评论的下限使我发疯!
@jjmerelo在Python中,其余部分(使用%或divmod())始终与除数具有相同的符号。例如123 % -10-> -7
@ SeanFrancisN.Ballais显然只是个惯例问题。维基百科关于模运算的条目说:当a或n为负数时,天真定义会失效,并且编程语言会在定义这些值的方式上有所不同。
正如宝拉(Paula)所说,这是由于剩余的原因。
该算法基于欧几里得除法。
在Ruby中,您可以编写以下代码来重建一致性:
1
2puts (10/3)*3 + 10%3
#=> 10
它在现实生活中的作用相同。 10个苹果和3个人。好的,您可以将苹果切成三等分,但要超出设定的整数。
使用负数时,也保持一致性:
1
2
3puts (-10/3)*3 + -10%3 #=> -10
puts (10/(-3))*(-3) + 10%(-3) #=> 10
puts (-10/(-3))*(-3) + -10%(-3) #=> -10
商总是向下取整(沿负轴向下),并且提示如下:
1
2
3
4
5
6
7
8puts (-10/3) #=> -4
puts -10%3 #=> 2
puts (10/(-3)) #=> -4
puts 10%(-3) # => -2
puts (-10/(-3)) #=> 3
puts -10%(-3) #=> -1
因此,除法和模运算必须是连贯的,这很清楚。但是,您可以选择向上或向下取整,只要这些等式成立,就可以保持连贯性。
@jjmerelo,我同意你的看法,在数学上,您可以选择其他规则,其中# 10 3 = 4 # 10 % 3 = -2 # 3 * 4 - 2 = 10。但这不适用于-2不存在的自然数集。
@iGian您缺少的答案是,有多种方法可以保持负数的一致性而不更改正数的结果。