Python学习精粹018:浮点数及其精确运算

浮点数是指带有小数点的数字,如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

  • 14
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值