decimal保留小数点后三位 不进行四舍五入_从 0.1 加 0.2 不等于 0.3 谈 Python 浮点数的前世今生...

1. 0.1加0.2不等于0.3?

什么?0.1 加 0.2 不等于 0.3?你确定没有搞错?真的,这是千真万确的事实。不仅 Python 如此,所有浮点数规范遵从IEEEE754二进制浮点数算术标准(ANSI/IEEE Std 754-1985)的编程语言,比如 C,同样如此(如果想在C环境中验证的话,请使用 double 类型)。在我们在Python 的 IDLE 中验证一下:

>>> 0.1 + 0.2 == 0.3False>>> 0.1 + 0.20.30000000000000004

为什么会这样呢?0.1 + 0.2 和 0.3 究竟有什么差别?让我们用 Python 的方式来探究一番吧。我们知道,Python 的世界里,万物皆对象,浮点数也是如此,浮点数对象的方法里面究竟隐藏着怎样的秘密呢?

>>> a = 0.1 + 0.2>>> b = 0.3>>> a.hex()'0x1.3333333333334p-2'>>> b.hex()'0x1.3333333333333p-2'

我们用对象 a 表示 0.1 + 0.2,用对象 b 表示 0.3, 浮点数对象的 hex() 方法,返回的是浮点数的十六进制表示。p-2,意思是为小数点左移2位,如果p+3,则表示右移3位。上面的代码,清晰显示了 a 和 b 的十六进制表示确实不一样。将 a 和 b 的 hex() 翻译成二进制是这样的:

331484222322dcedd8343767148a2f30.png

2. 为什么要使用浮点数?

天地茫茫,宇宙无限,星际距离动辄以万亿公里计。然若专注于微观世界,人类认知已经进入了纳米范围之内(氢原子基态的电子轨道半径0.0528纳米)。如果使用定点数表示如此巨大的动态范围,势必耗费巨大的存储资源,因此,浮点数就应运而生了。所谓浮点数,简单理解,就是科学计数法。比如:

3ecb690d7d806d8d11a3e52cd38aa5a7.png

Python 的浮点数为双精度浮点数,遵从IEEEE754二进制浮点数算术标准(ANSI/IEEE Std 754-1985),使用8个字节共64位表示一个浮点数,其中:

  • 符号位:1位
  • 指数位:11位
  • 尾数位:52位

我们来看看双精度浮点数的有关信息。

>>> import sys>>> sys.float_infosys.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)
Python 的双精度浮点数,可以表示的值域范围: 21b6dbaaca8219a225077f4b4fc1220d.png

1位符号位表示浮点数的正负,很容易理解;11位指数,对应的底数是2,可以表示的整数范围应该是-1024~1023,为什么 sys.float_info 显示的 min_exp 和 max_exp 却分别是 -1021 和 1024 呢?为什么尾数是看起来这么奇怪呢?

3. 浮点数的二进制和十进制是怎样转换的?

我们知道,二进制整数从低位到高位,权重分别是1、2、4、8等;二进制小数也类似,从小数点后第1位像右,权重分别是0.5、0.25、0.125、0.0625等。据此,可以很容易地将浮点数的二进制转换成十进制。比如,0b0.1,代表十进制的0.5,0b0.01,代表十进制的0.25,0b0.11,代表十进制的0.75。

5865240042283323aee985819c7e7146.png

我们以十进制浮点数 9.25 为例,看看浮点数对象是如何存储的。9.25  写成二进制是:

0b1001.01 

小数点左移3位,整数位只保留1位(类似于十进制的科学记数法):

0b1.00101

现在的尾数是00101,转成十六进制(需要后补3个0变成8位)是0x28。因为小数点左移3位,指数是3。我们来验证一下:

>>> a = 9.25>>> a.hex()'0x1.2800000000000p+3'

结果和我们手工转换完全一致。双精度浮点数的尾数,因为默认整数位有一个1,所以双精度浮点数所能表示的最大数的指数,也就从1023变成了1024。当52位尾数全部位1,指数位1024时,代表的最大数是21024,变成以10为底的数,尾数应当是:

>>> import math>>> math.log10(math.pow(2,1000)) + math.log10(math.pow(2,24)) # log10(2^1024)308.25471555991675>>> _ - 3080.25471555991674677>>> math.pow(10, _)1.7976931348623277

这与 sys.float_info 显示的尾数 1.7976931348623157 几乎完全一致。双精度浮点数所能表示的最小数的指数和尾数,和刚才的推导基本相似,我们就不再赘述了。

下面给出浮点数的二进制和十进制互转函数:

# -*- coding: utf-8 -*-import mathdef decimal_bin(number, precision=52):    """十进制浮点数转二进制字符串,precision为二进制小数位位数"""        num_int = int(number)    num_dec = number - num_int        s = ''    while num_dec > 0 and len(s)        num_dec *= 2        if num_dec >= 1:            s += '1'            num_dec -= 1        else:            s += '0'     return '%s.%s'%(bin(num_int), s)    def bin_decimal(bin_str):    """二进制字符串转十进制浮点数"""        num_int, num_dec = bin_str.split('.')        s = int(num_int, base=2)    for i, bit in enumerate(num_dec):        if bit == '1':            s += math.pow(2, -(i+1))            return s
4. 如何实现0.1加0.2等于0.3?

通过刚才的分析,我们已经认识到了浮点数的本质:在有限的精度内,我们可以精确的设置浮点数对象的值,但一旦参与运算,因为精度误差的原因,将会导致计算结果和预期的数学结果不能完全一致。那么,在浮点运算时,我们有没有方法实现计算结果和预期的数学结果保持一致呢?答案是:有!万能的 Python 已经为我们准备了模块 decimal,专门解决这个问题。请看演示:

>>> from decimal import Decimal>>> a = 0.1>>> b = 0.2>>> c = float(Decimal(str(a)) + Decimal(str(b)))>>> c0.3

decimal 模块还有很多功能,有兴趣的同学可以在网上找到更多的学习资料。

c3350df8cbca3a5db80d0e2bd1828525.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值