《Python 3程序开发指南(第2版•修订版)》——2.3 浮点类型-阿里云开发者社区

开发者社区> 开发与运维> 正文
登录阅读全文

《Python 3程序开发指南(第2版•修订版)》——2.3 浮点类型

简介:

本节书摘来自异步社区《Python 3程序开发指南(第2版•修订版)》一书中的第2章,第2.3节,作者[英]Mark Summerfield,王弘博,孙传庆 译,更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.3 浮点类型

Python提供了3种浮点值:内置的float与complex类型,以及来自标准库的decimal.Decimal类型,这3种数据类型都是固定的。float类型存放双精度的浮点数,具体取值范围则依赖于构建Python的C(或C#或Java)编译器,由于精度受限,对其进行相等性比较并不可靠。float类型的数值要使用小数点或使用指数表示,比如,0.0、4.、5.7、-2.5、-2e9、8.9e-4等。

计算机使用基数2表示浮点数——这意味着,有些十进制数可以准确表示(比如0.5),有些只能大约表示(比如0.1与0.2)。进一步地,在表示上使用了固定数量的比特位,因此,可以存放的数值大小是有限的。下面给出了一个在IDLE中输入的salutary实例:

>>> 0.0, 5.4, -2.5, 8.9e-4
(0.0, 5.4000000000000004, -2.5, 0.00088999999999999995)

表示上的不尽准确不是Python特有的问题——所有程序设计语言在表示浮点数时都存在这个问题。

Python 3.1可以产生看上去更为合理的输出:

>>>0.0, 5.4, -2.5, 8, 9e-4
(0.0, 5.4, -2.5 ,0.00089)

当Python 3.1输出一个浮点数时,在大多数情况下将使用David Gay的算法。该输出具有尽可能少的位数,而且没有丢失精确性。尽管该输出相当不错,但它没有改变这样的事实,即计算机(无论使用哪种计算机语言)有效地将浮点数以近似值存储。

如果我们确实需要高精度,那么可以使用来自decimal模块的decimal.Decimal数。这种类型在计算时,可以达到我们指定的精度(默认情况下,到小数点后28位),并且可以准确地表示循环小数,比如0.1,但在处理速度上比通常的floatS要慢很多。由于准确性的优势,decimal.Decimal数适合于财政计算。

Python支持混合模式的算术运算,比如使用int与float运算,生成float数;使用float与complex运算,生成complex结果。由于decimal.Decimal是混合精度的,只能用于其他decimal.Decimal数或intS,在与intS混合运算时,会生成decimal.Decimal结果。如果使用不兼容的数据类型进行运算,会产生TypeError异常。

2.3.1 浮点数

表2-2中的所有数值型操作与函数都可以与floatS一起使用,包括增强版的赋值运算符。float数据类型可以作为函数调用——不指定参数时返回0.0,指定的是float型参数时返回该参数的一个拷贝,指定其他任意类型的参数时都尝试将其转换为float。用于转换时,可以给定一个字符串参数,或者使用简单的十进制表示,或者使用指数表示。在进行涉及floatS的计算时,可能会生成NaN(“不是一个数字”)或“无穷”——遗憾的是,这种行为并不是在所有实现中都一致,可能会由于系统底层数学库的不同而不同。

下面给出一个简单函数,该函数用于比较floatS是否相等(按机器所能提供的最大精度):

def equal_float(a, b):
    return abs(a - b) <= sys.float_info.epsilon

为了实现上述函数,需要导入sys模块。sys.float_info对象有很多属性,sys.float_ info.epsilon是机器可以区分出的两个浮点数的最小区别。在作者的某台32位系统的机器上,区别仅仅是0.000 000 000 000 000 2(Epsilon是这一数值的传统名称)。Python的floatS通常会提供至多17个数字的精度。

如果在IDLE中输入sys.float_info,就可以显示其所有属性,包括机器可以表示的浮点数的最小值与最大值,输入help(sys.float_info)会显示关于sys.float_info对象的某些信息。

使用int()函数,可以将浮点数转换为整数——返回其整数部分,舍弃其小数部分;或者使用round()函数,该函数将对小数部分四舍五入;或者使用math.floor()函数,或math.ceil()函数——该函数可以将浮点数转换为最近邻的整数(或者向上转换,或者向下转换)。如果浮点数的小数部分为0,float.is_integer()方法将返回True,浮点数的小数表示可以使用float.as_integer_ratio()方法获取,比如,给定浮点数x = 2.75,则调用x.as_integer_ratio()将返回(11, 4)。使用float()函数,可以将整数转换为浮点数。

使用float.hex()方法,可以将浮点数以十六进制形式表示为字符串,相反的转换可以使用float.fromhex()实现1。比如:

s = 14.25.hex()          # str s == '0x1.c800000000000p+3'
f = float.fromhex(s)     # float f == 14.25
t = f.hex()              # str t == '0x1.c800000000000p+3'

指数使用p(“幂”)进行表示,而不是使用e,因为e是一个有效的十六进制数字。

除内置的浮点功能之外,math模块提供了更多的一些可用于floatS的函数,如表2-5与表2-6中所示,下面给出的一些代码段,展示了如何使用模块中的函数:

>>> import math
>>> math.pi * (5 ** 2) # Python 3.1 outputs: 78.53981633974483
78.539816339744831
>>> math.hypot(5, 12)
13.0
>>> math.modf(13.732) # Python 3.1 outputs: (0.7319999999999993, 13.0)
(0.73199999999999932, 13.0)


screenshot


screenshot


screenshot


screenshot

函数math.hypot()用于计算从原点到点point (x, y)的距离,与函数math.sqrt((x 2) + (y 2))产生的结果相同。

math模块非常依赖于编译Python时使用的底层数学模块,这意味着,一些错误条件与边界情况在不同的平台上会有不同的表现。

2.3.2 复数

复数这种数据类型是固定的,其中存放的是一对浮点数,一个表示实数部分,另一个表示虚数部分。Literal复数在书写上使用+或-符号将实数部分与虚数部分(其后跟随一个字母j2)连接在一起,共同构成复数。比如下面这些实例:3.5+2j、0.5j、4+0j、-1-3.7j等。注意,如果实数部分为0,就可以忽略。

复数的两个部分都以属性名的形式存在,分别为real与imag,例如:

>>> z = -89.5+2.125j
>>> z.real, z.imag
(-89.5, 2.125)

除//、%、divmod()以及三个参数的pow()之外,表2-2中所有数值型操作符与函数都可用于对复数进行操作,赋值操作符的增强版也可以。此外,复数类型有一个方法conjugate(),该方法用于改变虚数部分的符号,例如:

>>> z.conjugate()
(-89.5-2.125j)
>>> 3-4j.conjugate()
(3+4j)

注意,这里我们在一个字面意义的复数上调用了方法。通常,Python允许在任何字面意义值上调用方法或存取属性,只要该字面意义的数据类型提供了该方法或属性——然而,这一规律不适合于特殊的方法,因为总有相应的操作符应该使用(比如+),比如,4j.real为0.0,4j.imag为4.0,4j + 3+2j为3+6j。

复数数据类型可以作为函数进行调用——不给定参数进行调用将返回0j,指定一个复数参数时,会返回该参数的拷贝,对其他任意参数,则尝试将其转换为一个复数。用于转换时,complex()接受的或者是一个字符串参数,或者1或2个浮点数,如果只给定一个浮点数,那么虚数部分被认为是0。

math模块中的函数不能处理复数,这是一个慎重的设计决策,可以确保在有些情况下,math模块的用户得到的是异常,而不是得到一个复数。

要使用复数,可以先导入cmath模块,该模块提供了math模块中大多数三角函数与对数函数的复数版,也包括一些复数特定的函数,比如cmath.phase()、cmath.polar()与 cmath.rect(),还包括cmath.pi、cmath.e 等常量,这些常量与math模块中对应对象包含同样的浮点值。

2.3.3 十进制数字

很多应用程序中,使用浮点数时导致的数值不精确性并没有很大的影响,在很多时候都被浮点计算的速度优势所掩盖,但有些情况下,我们更需要的是完全的准确性,即便要付出速度降低的代价。decimal模块可以提供固定的十进制数,其精度可以由我们自己指定。涉及Decimals的计算要比浮点数的计算慢,但这是否需要关注要依赖于具体的应用程序。

要创建Decimal,必须先导入decimal模块,例如:

>>> import decimal
>>> a = decimal.Decimal(9876)
>>> b = decimal.Decimal("54321.012345678987654321")
>>> a + b
Decimal('64197.012345678987654321')

十进制数是使用decimal.Decimal()函数创建的,该函数可以接受一个整数或字符串作为参数——但不能以浮点数作为参数,因为浮点数不够精确,decimals则很精确。如果使用字符串作为参数,就可以使用简单的十进制数表示或指数表示。除提供了准确性外,decimal.Decimals的精确表述方式意味着可以可靠地进行相等性比较。

从Python 3.1开始,使用decimal.Decimals from-float()函数将floats转换为十进制数成为可能,该函数以一个float型数作为参数,并返回与该float数值最为接近的decimal.Decimal。

表2-2中列出的所有数值型操作符与函数,包括增强版的赋值操作符,都可以用于decimal.Decimals,但有两个约束;如果操作符**左边的操作数为decimal.Decimal,那么其右边的操作数必须为整数;同样地,如果pow()函数的第一个参数为decimal. Decimal,那么其第二个以及可选的第三个参数必须为整数。

math模块与cmath模块不适于处理decimal.Decimals,但是math模块提供的一些函数可以作为decimal.Decimal的方法使用,比如,要计算ex(x是一个浮点数),可以使用math.exp(x),但如果x是一个decimal.Decimal,就需要使用x.exp()。从要素3的讨论可以看出,x.exp()在语法效果上与decimal.Decimal.exp(x)类似。

decimal.Decimal数据类型还提供了ln()方法,用于计算自然对数(以e为基数),就像带一个参数的math.log()、log10()与sqrt(),以及很多特定于decimal.Decimal数据类型的方法。

数据类型为decimal.Decimal的数在某个上下文的作用范围内进行操作,这里,上下文是指影响decimal.Decimal行为的一组设置。上下文会指定需要使用的精度(默认情况下是28位)、截取技术以及其他一些详细信息。

有些情况下,浮点数与decimal.Decimals在精度上的差别会变得很明显:

>>> 23 / 1.05
21.904761904761905
>>> print(23 / 1.05)
21.9047619048
>>> print(decimal.Decimal(23) / decimal.Decimal("1.05"))
21.90476190476190476190476190
>>> decimal.Decimal(23) / decimal.Decimal("1.05")
Decimal('21.90476190476190476190476190')

尽管使用decimal.Decimal的除法操作要比使用浮点数的精确得多,这一场景(32位机器上)下,区别只在fifteenth decimal place才表现出来。在很多情况下,这种差别并不是很大的问题——比如,本书中,所有需要浮点数的实例都使用floatS。

另一个需要注意的是上面实例中最后两行,这里第一次展示了打印对象时的一些幕后格式化操作。在使用decimal.Decimal(23) / decimal.Decimal("1.05")的结果作为参数调用print()函数时,打印的是bare number——其输出为字符串形式。如果只是简单地输入该表达式,就会得到一个decimal.Decimal输出——此时的输出是表象形式。所有Python对象都有两种输出形式。字符串形式在设计目标上是为了更易于阅读,表象形式在设计目标上则是生成备用的输出信息,这种输出信息作为Python解释器的输入时(如果可能)会重新产生所代表的对象。下一节讨论字符串时,我们会再次讨论这一主题,在第6章中讨论为自定义数据类型提供字符串与表象形式时会再一次讨论。

库索引中的decimal模块文档提供了所有相关的详细资料,这些内容或者不够清晰,或者超出了本书的范围,此外,该文档还提供了更多的实例以及一个FAQ 列表。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享: