英文原文链接:http://python-history.blogspot.com/2009/03/problem-with-integer-division.html
原文作者:Guido van Rossum
Python对整数除法的处理是一个早期犯错导致后患无穷的典型范例。前面已经提到创立Python时我抛弃了ABC中采用的数值处理方法。例如,在ABC中当你进行两个整数相除,结果是一个准确的有理数。而在Python中,整数除法的结果被截断为一个整数。
依据我的经验,ABC中的精确有理数并没有达到设计者的期望。写一个商业应用软件小软件就可以发觉(例如,计算一个人缴税多少),比通常想象的慢太多。调试程序可发现原因在于程序内部在运算时用数千位精度的有理数来表示数值,这些值在最后输出时精度只要求到小数点后两三位就足够了。这个问题可以利用计算起始时附加一个不完全精确的0来解决,但是这个思路对初学者来说并不直观,且难以调试。
所以在设计Python时,我参考了自己已经熟悉的C语言的数值模型。C包括各种长度的整数和浮点数。我就选了C的long类型为Python的整数(integer)(可以保证至少有32位精度),选择C的double表示Python的浮点数(floating point number)。然后我又增加了一个任意精度的整数类型,称为“long。”
我从C语言还借鉴了一条规则:标准的算术运算,包括除法,返回值总是和操作数同一类型,这条在C中实用的规则在Python这种十分高级的语言(very-high-level language)中则铸成大错。更糟的是,我最初还设定了一个禁止混合类型运算的错误规则,当时这么做的目的是为了让各个类型实现之间互不关联。所以,一开始不能计算int和floating相加,即使int和long也不行。Python公开发布之后,Time Peters很快就说服我禁止混合类型运算是个坏点子,于是我引入常用的强制转换规则(coercion rules)来支持混合类型运算。例如int和long的运算会先把int类型的参数转换为long类型,然后返回long类型的运算结果,任何一个参数为float时就把另外一个参数转换为float类型并返回float类型的运算结果。
至此大错已成--整数相除返回整数类型结果。你可能会奇怪“不会是杞人忧天吧?”或者庸人自扰吧?随后为了改变这一设定多次遭受Python社区用户坚韧的反对,他们坚持能明白“整除”的含义对是所有程序员必经的“成人礼”。那么,请让我来解释一下我认为这是一条设计缺陷的原因。
当你写一个实现数值运算的函数(假设,计算月相)你通常希望参数是浮点数类型。然而,由于Python没有类型声明,就无法阻止函数调用者传递整数参数。对于C这类静态类型语言,编译器可以强制转换参数为浮点数,然而这在Python中不存在-算法中各步骤将会一直用整形计算直到遇到和浮点数的混合运行后才会恢复中间计算结果为浮点数类型。
除了除法之外的其它运算,整数和浮点数的行为是一致的。例如1+1等于2,恰如1.0+1.0是2.0,其它运算也是。这就很容易造成一种假象:数值算法的参数是整数还是浮点数无关紧要。然而,当除法参与进来,两个运算数都是整数时,运算结果直接截断,这造成运算中一个潜在质的误差。虽然可以通过在函数入口强制转换所有参数为浮点数来进行保护,这样很不方便,也无助与代码的可读性和可维护性。另外,这样也限制了算法同时支持复数类型参数(当然这种情况很罕见)。
回过来还是要说,问题原因全在于Python不能自动转换参数到预先声明的类型。传递一个无效参数,例如字符串,通常很容易发现,因为很少有运算支持字符串/数值混合类型的运算(除了乘法)。然而传递整数参数时,可能会得到和正确答案相近却可能有相当大误差的结果-很难调试甚至不容易发现问题。(最近我又遇到这样的情况,是在画一个模拟时钟时候--表针的位置由于整除截断而不准确,但是错误却不容易看到,只有每天某些特定时刻才比较明显。)
修正整除错误不是一件简单的事情,因为程序已经依赖整除截断行为了。语言增加了整除运算符(//)来提供原有截断语义。另外通过(“from __future__ import division”)机制可使整数除法使用不再截断的新语义。最后,还可以通过添加命令行参数(-Qxxx)使用新语义或者辅助代码转换到新语义。幸运的是,Python3000中除法的缺省行为是正确的。
没有评论:
发表评论