Bootstrap

【Python学习手册(第四版)】学习笔记05.1-Python对象类型-数字类型中的小数数字、分数类型详解

个人总结难免疏漏,请多包涵。更多内容请查看原文。本文以及学习笔记系列仅用于个人学习、研究交流。


目录

小数数字

设置全局精度

小数上下文管理器

分数类型

基本知识

数值精度

转换和混合类型

Python的核心数字类型:整数、浮点数和复数。对于绝大多数程序员来说,需要进行的绝大多数数字处理都满足了。然而,Python还自带了一些更少见的数字类型

小数数字

基本知识

小数对象是一种核心数据类型,比其他数据类型复杂一些,小数是通过一个导入的模块调用函数后创建的,而不是通过运行常量表达式创建的。从功能上来说,小数对象就像浮点数,只不过它们有固定的位数和小数点,因此小数是有固定的精度的浮点值。

例如,使用了小数对象,我们能够使用一个只保留两位小数位精度的浮点数。此外,我们能够定义如何省略和截断额外的小数数字。尽管它对平常的浮点数类型来说带来了微小的性能损失,小数类型对表现固定精度的特性(例如,钱的总和)以及对实现更好的数字精度是一个理想的工具。

你可能已经知道,也可能还不知道:浮点数学缺乏精确性,因为用来存储数值的空间有限。例如,下面的计算应该得到零,但是结果却没有。结果接近零,但是却没有足够的位数去实现这样的精度

>>> 0.1+0.1+0.1-0.3
5.551115123125783e-17

打印结果将会产生一个用户友好的显示格式但并不能完全解决问题,因为与硬件相关的浮点数运算在精度方面有内在的缺陷:

>>> print(0.1+0.1+0.1-0.3)
5.551115123125783e-17

不过使用小数对象,结果能够改正:

>>> from decimal import Decimal
>>> Decimal('0.1') +Decimal('0.1') +Decimal('0.1')-Decimal('0.3')
Decimal('0.0')

能够通过调用在decimal模块中的Decimal的构造函数创建一个小数对象,并传入一个字符串,这个字符串有我们希望在结果中显示的小数位数。

当不同精度的小数在表达式中混编时,Python自动升级为小数位数最多的:

>>> Decimal('0.1') +Decimal('0.10') +Decimal('0.10')-Decimal('0.30')
Decimal('0.00')

通过decimal.Decimal.from_float(1.25)形式的调用能够从一个浮点对象创建一个小数对象。这一转换是精确的,但有时候会产生较多的位数。

设置全局精度

decimal模块中的其他工具可以用来设置所有小数数值的精度、设置错误处理等。

这个模块中的一个上下文对象允许指定精度(小数位数)和舍入模式(舍去、进位等)。该精度全局性地适用于调用线程中创建的所有小数:

>>> import decimal
>>> decimal.Decimal(1) / decimal.Decimal(7)
Decimal('0.1428571428571428571428571429')
>>> decimal.getcontext().prec = 4
>>> decimal.Decimal(1) / decimal.Decimal(7)
Decimal('0.1429')

小数上下文管理器

可使用上下文管理器语句来重新设置临时精度。在语句退出后,精度又重新设置为初始值:

>>> import decimal
>>> decimal.Decimal('1.00') / decimal.Decimal('3.00')
Decimal('0.3333333333333333333333333333')
>>> with decimal.localcontext() as ctx:
...     ctx.prec = 2
...     decimal.Decimal('1.00') / decimal.Decimal('3.00')
...
Decimal('0.33')
>>> decimal.Decimal('1.00') / decimal.Decimal('3.00')
Decimal('0.3333333333333333333333333333')

小数类型在实际中仍然很少用到,请参考Python的标准库手册和交互式帮助来了解更多细节。

分数类型

分数,它实现了一个有理数对象。它明确地保留一个分子和一个分母,从而避免了浮点数学的某些不精确性和局限性。

基本知识

分数是前面小节所介绍的已有的小数固定精度类型的“近亲”,它们都可以通过固定小数位数和指定舍入或截断策略来控制数值精度。

分数以类似于小数的方式使用,它也存在于模块中;导入其构造函数并传递一个分子和一个分母就可以产生一个分数。

举例:

>>> import fractions
>>> x = fractions.Fraction(1,3)
>>> y = fractions.Fraction(4,6)
>>> x
Fraction(1, 3)
>>> y
Fraction(2, 3)
>>> print(y)
2/3
>>>

创建了分数,它可以像平常一样用于数学表达式中: 

>>> x +y
Fraction(1, 1)
>>> x - y
Fraction(-1, 3)
>>> x * y
Fraction(2, 9)
>>> Fraction('.25')

 分数对象也可以从浮点数字符串来创建,这和小数很相似:

>>> fractions.Fraction('.25')
Fraction(1, 4)
>>> fractions.Fraction('1.25')
Fraction(5, 4)
>>> fractions.Fraction('.25') + fractions.Fraction('1.25')
Fraction(3, 2)

数值精度

注意,这和浮点数类型的数学有所区别,那是受到浮点数硬件的底层限制的约束。相比较而言,这里与对浮点数对象执行的操作是相同的,注意它们有限的精度:

>>> a = 1/3.0
>>> b = 4/6.0
>>> a
0.3333333333333333
>>> b
0.6666666666666666
>>> a + b
1.0
>>> a-b
-0.3333333333333333
>>> a * b
0.2222222222222222

对于那些用内存中给定的有限位数无法精确表示的值,浮点数的局限尤为明显。分数和小数都提供了得到精确结果的方式,虽然要付出一些速度的代价。

在下面的例子中(重复前一小节的),浮点数并没有准确地给出期望的0的答案,但其他的两种类型都做到了:

>>> 0.1 + 0.1 +0.1 -0.3
5.551115123125783e-17
>>> from fractions import Fraction  #注意这里导入与上面import fractions的区别及使用区别
>>> Fraction(1,10) + Fraction(1,10) + Fraction(1,10) - Fraction(3,10)
Fraction(0, 1)
>>> from decimal import Decimal
>>> Decimal('0.1') +Decimal('0.1') +Decimal('0.1')-Decimal('0.3')
Decimal('0.0')

分数和小数都能够提供比浮点数更直观和准确的结果,它们以不同的方式做到这点(使用有理数表示以及通过限制精度):

>>> 1 / 3
0.3333333333333333
>>> Fraction(1,3)
Fraction(1, 3)
>>> import decimal
>>> decimal.getcontext().prec = 2
>>> decimal.Decimal(1) / decimal.Decimal(3)
Decimal('0.33')

实际上,分数保持精确性,并且自动简化结果。继续前面的交互例子:

>>> (1 / 3) + (6 / 12)
0.8333333333333333
>>> Fraction(6,12)
Fraction(1, 2)
>>> Fraction(1,3) + Fraction(6,12)
Fraction(5, 6)
>>> decimal.Decimal(str(1/3)) + decimal.Decimal(str(6/12))
Decimal('0.83')
>>> 1000.0/ 1234
0.8103727714748784
>>> Fraction(1000,1234)
Fraction(500, 617)

转换和混合类型

为了支持分数转换,浮点数对象有一个方法,能够产生它们的分子和分母比,分数有一个from_float方法,并且float接受一个Fraction作为参数。

*是一种特殊的语法,它把一个元组扩展到单个的参数中

>>> (2.5).as_integer_ratio()    #浮点对象方法
(5, 2)
>>> f = 2.5
>>> z = Fraction(*f.as_integer_ratio())    #浮点转化为Fraction方法,等于 Fraction(5,2)
>>> z
Fraction(5, 2)
>>> x = Fraction(1,3)
>>> x
Fraction(1, 3)
>>> x + z
Fraction(17, 6)
>>> float(x)    #Fraction转为float
0.3333333333333333
>>> float(z)
2.5
>>> float(x + z)
2.8333333333333335
>>> 17 / 6
2.8333333333333335
>>> Fraction.from_float(1.75)        #float转为Fraction 浮点转为分数
Fraction(7, 4)
>>> Fraction(*(1.75).as_integer_ratio())
Fraction(7, 4)

表达式中允许某些类型的混合,尽管Fraction有时必须手动地传递以确保精度。

>>> x
Fraction(1, 3)
>>> x +2    #Fraction + int -> Fraction
Fraction(7, 3)
>>> x + 2.0    #Fraction + float -> Fraction
2.3333333333333335
>>> x + (1./3)    #Fraction + float -> Fraction
0.6666666666666666
>>> x + (4./3)
1.6666666666666665
>>> x + Fraction(4,3)    #Fraction + Fraction -> Fraction
Fraction(5, 3)

警告:尽管可以把浮点数转换为分数,在某些情况下,这么做的时候会有不可避免的精度损失,因为这个数字在其最初的浮点形式下是不精确的。

当需要的时候,我们可以通过限制最大分母值来简化这样的结果:

>>> 4.0/3
1.3333333333333333
>>> (4.0/3).as_integer_ratio()    #浮点存在精度丢失
(6004799503160661, 4503599627370496)

>>> x
Fraction(1, 3)
>>> a = x + Fraction(*(4.0/3).as_integer_ratio())
>>> a
Fraction(22517998136852479, 13510798882111488)

>>> 22517998136852479 / 13510798882111488.    #5/3 近尾
1.6666666666666667

>>> a.limit_denominator(10)    #简化为最接近的分数
Fraction(5, 3)

了解更多可以查阅Python库手册以及其他的文档。

;