浮点数是指带有小数点的数字,如3.1415。一个带有小数点的数字,即使小数部分为0,亦为浮点数,如0.0, 5.0。
一、取值范围
如下所示:
>>> import sys
>>> print(sys.float_info.min)
2.2250738585072014e-308
>>> print(sys.float_info.max)
1.7976931348623157e+308
可知,浮点类型的取值范围为2.2250738585072014e-308~1.7976931348623157e+308。
执行以下语句:
>>> import sys
>>> print(sys.float_info)
sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)
可获得关于浮点类型(float)的信息,其中,max_exp属性表示以e为底数的幂运算的最大次幂,这里为e的1024次方;max_10_exp属性表示以10为底数的幂运算的最大次幂,这里为10的308次方;min_exp属性表示以e为底数的幂运算的最大次幂,此处为e的-1021次方;min_10_exp属性表示以10为底数的幂运算的最小次幂,此处为10的-307次方;mant_dig属性表示浮点数的精度;epsilon属性表示两个浮点数之间的最小差异;radix属性表示指数的底数,此处为2。
接着试试:
>>> print(pow(10.0, 309))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OverflowError: (34, 'Result too large')
将导致溢出错误(OverflowError: (34, 'Result too large')),运算结果超出了浮点类型的取值范围。
二、精确运算
如下所示:
>>> print(10.0/3)
3.3333333333333335
为何会出现这样的结果?
这是因为Python将一个实数(包括有限小数和无限小数)存储为IEEE 754标准的双精度数格式。双精度数在内存中占用8字节64位来存储,其中:1位用于存储符号(0或1,即正或负);11位用于存储指数,即2的幂,为小数点移动的位数;52位用于存储位数,是小数点右边的二进制位数,定义了浮点数的精度。如下图所示:
图3-1 IEEE 754标准的双精度数格式
请看下面的步骤:
1. 将十进制无限循环小数3.3333...转换为二进制数,为更清晰地说明问题,这里保留了小数点后53位:
2. 将该二进制数规范化为浮点表示法,即在小数点左边使用唯一的非零数码1,如下所示:
3. 对尾数M用“0舍1入”方法进行舍入处理:要保留小数点后的N位小数,如果N+ 1位为0,则直接舍去N位之后的小数;如果N + 1位为1,则在N位上加1,并舍去N位之后的小数。此处,尾数要保留52位,第53位为1,则在52位的0上加1变为1,并舍去52位之后的小数,如下:
4. 双精度数格式的指数在内存中占用11位,使用偏移量1023(,其中,指数在内存中的存储单元大小为11位)。因此,指数为1 + 1023 = 1024,十进制数1024转换为二进制数为10000000000。
这时,符号为0(正数),指数为10000000000,尾数为1010101010101010101010101010101010101010101010101011,合并起来,以IEEE 754标准双精度数格式存储在计算机中的数字为:
0100000000001010101010101010101010101010101010101010101010101011
5. 要将以IEEE 754双精度数格式存储的数字还原为十进制,可按照以下步骤:
(1)分别找到符号位(S)、指数位(E)、尾数位(M),如下:
(2)符号位(S)为0,意味着符号为正;
(3)位移量为指数(E)- 偏移量 = 1024 - 1023 = 1,对尾数去规范化,小数点右移一位为:
(4)将去规范化后的二进制数转换为十进制数的绝对值,其中,整数部分为:
小数部分为:
绝对值为3.3333333333333335
(5)加上符号(正号),所以该数字为3.3333333333333335。
如上所述,在计算机硬件中,浮点数以基数为2(二进制)的小数进行表示。然而,大多数的十进制小数并不能精确地表示为二进制小数,只能在计算机中近似地以二进制浮点数进行储存。
通过字符串格式化,限制浮点数的有效位数来输出,并不能从根本上解决其精确度。
使用Python内置的format()函数保留小数点后20位。
如下所示,
>>> print("{:.20}".format(10/3))
3.3333333333333334814
接下来试试:
>>> 0.1 + 0.1 + 0.1
0.30000000000000004
>>> 0.2 + 0.2 + 0.2
0.6000000000000001
>>> 0.3 + 0.3 + 0.3
0.8999999999999999
>>> 0.4 + 0.4 + 0.4
1.2000000000000002
>>> 0.1 + 0.2 + 0.3
0.6000000000000001
>>> 0.1 + 0.2 + 0.2
0.5
再看:
>>> 0.1 + 0.2 + 0.3 == 0.6
False
>>> 0.1 + 0.2 + 0.2 == 0.5
True
>>>
对此使用Python的内置函数round()进行四舍五入,也不能解决。
只保留小数点后一位,如下所示:
>>> round(0.1, 1) + round(0.2, 1) + round(0.3, 1)
0.6000000000000001
虽然,它们不能精确表示所要代表的实际值,但是,可以使用math模块的isclose()函数,根据给定的相对和绝对容差来比较两个数是否为接近的,如下:
math.isclose(x, y, rel_tol = 1e-09, abs_tol = 0.0)
其中:
相对容差(rel_tol),是相对于两个数的绝对值之间允许的最大差值,必须大于0,且其默认为le-09,即确保两个数在大约9位十进制数字内相同。假设允许0.01%的容差,则设为0.0001。
绝对容差(abs_tol),是最小绝对容差,对于接近0时尤为有用,且其必须至少为0。
如有x、y两个数,如无错误发生,使用isclose()函数进行比较的结果为:abs(x -y) <= max(rel_tol * max(abs(x), abs(y)), abs_tol) 。
试一下:
>>> import math
>>> math.isclose((0.1 + 0.2 + 0.3), 0.6, rel_tol = 1e-09, abs_tol = 0.0)
True
>>> math.isclose((0.1 + 0.2 + 0.3), 0.6, rel_tol = 0.0001, abs_tol = 0.0)
True
>>> math.isclose((0.1 + 0.2 + 0.3), 0.6)
True
或者,可再考虑使用round()函数来对浮点数的运算结果进行近似比较。
如下所示:
>>> round((0.1 + 0.2 + 0.3), 1) == round(0.6, 1)
True
此外,还可考虑使用Python内置的decimal模块或fractions模块来实现浮点数的高精度运算。
使用decimal模块进行浮点数的运算时,通过 getcontext() 查看当前上下文,并在必要时设定精度,即可指定小数点后的有效位数(默认为27位)。
如下所示,
>>> import decimal
>>> decimal.Decimal(str(10.0)) / decimal.Decimal(str(3.0))
Decimal('3.333333333333333333333333333')
>>> from decimal import *
>>> getcontext().prec = 50
>>> Decimal(str(10.0)) / Decimal(str(3.0))
Decimal('3.3333333333333333333333333333333333333333333333333')
>>> from decimal import Decimal
>>> from decimal import getcontext
>>> getcontext().prec = 1
>>> Decimal('0.1') + Decimal('0.2') + Decimal('0.3') == Decimal('0.6')
True
>>> 0.12 + 0.17 + 0.31
0.6000000000000001
>>> 0.12 + 0.17 + 0.31 == 0.6
False
>>> getcontext().prec = 10
>>> Decimal('0.12') + Decimal('0.17') + Decimal('0.31') == Decimal('0.6')
True
Python内置的fractions模块支持分数运算,其分数的实例可由一对整数(作为分子、分母)、一个分数,或一个字符串构建而成。
如下所示:
>>> from fractions import Fraction
>>> from decimal import Decimal
>>> Fraction(10, 3)
Fraction(10, 3)
>>> Fraction(20, 6)
Fraction(10, 3)
>>> Fraction(30, -18)
Fraction(-5, 3)
>>> Fraction('20/6')
Fraction(10, 3)
>>> Fraction(20/6)
Fraction(7505999378950827, 2251799813685248)
>>> Fraction(-.123)
Fraction(-553942754166571, 4503599627370496)
>>> Fraction('0.5')
Fraction(1, 2)
>>> Fraction('3.333333333333333333333333333')
Fraction(3333333333333333333333333333, 1000000000000000000000000000)
>>> Fraction('3e-8')
Fraction(3, 100000000)
>>> Fraction(Decimal(str(10.0 / 3.0)))
Fraction(6666666666666667, 2000000000000000)
>>> 0.1 + 0.2
0.30000000000000004
>>> Fraction.from_float(0.1) + Fraction.from_float(0.2)
Fraction(10808639105689191, 36028797018963968)
>>> format(Fraction.from_float(0.1) + Fraction.from_float(0.2), '.40g')
'0.3000000000000000166533453693773481063545'
>>> format(Fraction.from_float(0.1) + Fraction.from_float(0.2), '.17g')
'0.32'
>>> format(Fraction.from_float(0.1) + Fraction.from_float(0.2), '.16g')
'0.3'
>>> format(Fraction.from_float(10.0/3.0), '.40g')
'3.333333333333333481363069950020872056484'
>>> format(Fraction.from_float(10.0/3.0), '.16g')
'3.333333333333333'
可以使用max_denominator()方法来指定分母的最大值,并找出给定浮点数的有理数近似值。
如下所示:
>>> Fraction(-.12331).limit_denominator()
Fraction(-12331, 100000)
>>> Fraction(-.12331).limit_denominator(100)
Fraction(-9, 73)
进一步地,可以使用Python内置的sum()函数来减少求和过程中的精度损失,它会使用扩展的精度进行中间过程的计算,使错误不会累计到影响最终结果,以更好地保持总体精确度。
如下所示:
>>> sum([0.1, 0.2, 0.3]) == 0.6
True
更进一步,使用Python内置math模块的fsum()函数也可在计算过程中跟踪“丢失的数位”,并在添加到总计值时,只执行一次舍入。
如下所示:
>>> import math
>>> math.fsum([0.1, 0.2, 0.3]) == 0.6
True
三、运算方法
在Python中,可用type函数获取对象的数据类型。
如下所示:
>>> import math
>>> print(type(10))
<class 'int'>
>>> print(type(10**100))
<class 'int'>
>>> print(type(10.0))
<class 'float'>
>>> print(type(math.pi)) #@01
<class 'float'>
>>> print(type('hello'))
<class 'str'>
其中,在#@01处,使用了math模块中的数学常量pi(圆周率)。
类似于整数,浮点数也支持如下运算(其中,x、y为任意浮点数):
运算 | 说明 | 示例 |
x + y | x 和 y 的和 | 1.2 + 2.3 = 3.5 |
x - y | x 和 y 的差 | 2.3 - 1.2 = 1.0999999999999999 |
x * y | x 和 y 的乘积 | 1.2 + 2.3 = 2.76 |
x / y | x 和 y 的商 | 3.0 / 6.0 = 0.5 |
x // y | x 和 y 的商数 | 3.0 / 6.0 = 0.0 |
x % y | x / y 的余数 | 5.5 % 2.0 = 1.5 |
-x | x 取反 | -(-3.0) = 3.0 |
+x | x 不变 | +(-3.0) = -3.0 |
abs(x) | x 的绝对值或大小 | import math math.fabs(-3.0) |
int(x) | 将 x 转换为整数 | int(3.1415926) = 3 |
float(x) | 将 x 转换为浮点数 | float(3) = 3.0 |
divmod(x, y) | (x // y, x % y) | divmod(-5.0, 2.0) = (-3.0, 1.0) |
pow(x, y) | x 的 y 次幂 | pow(2.0, 4.0) = 16.0 |
x ** y | x 的 y 次幂 | 2.0 ** 4.0 = 16.0 |
在此,使用dir()函数来查看与float相关的所有属性和方法。
如下所示:
>>> dir(float)
['__abs__', '__add__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getformat__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__int__', '__le__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__pos__', '__pow__', '__radd__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rmod__', '__rmul__', '__round__', '__rpow__', '__rsub__', '__rtruediv__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', 'as_integer_ratio', 'conjugate', 'fromhex', 'hex', 'imag', 'is_integer', 'real']
关于对浮点数进行比较运算,以及幂、平方根、绝对值等运算,亦类似于整数,在此不再赘述。
如下所示:
>>> from math import math
>>> pow(math.pi, abs(-math.pi))
36.4621596072079
>>> math.sqrt(math.pi)
1.7724538509055159
>>> pow(math.pi, abs(-math.pi)) >= math.sqrt(math.pi)
True