美文网首页我爱编程「Python3学习笔记」读书笔记
「Python3学习笔记」读书笔记--float类型

「Python3学习笔记」读书笔记--float类型

作者: Lovemma | 来源:发表于2018-06-21 23:04 被阅读0次

    本文为「Python3学习笔记」一书的读书总结。

    在Python中 float 类型默认存储双精度浮点数(也就是其他语言中的 double ),可一表达16到17位浮点数。

    >>> 1/3
    0.3333333333333333
    >>> 0.1234567890123456789
    0.12345678901234568
    

    从实现方式上来看,浮点数是以二进制的方式来存储十进制数的近似值。这就可能导致执行的结果与预期不符合,造成不一致缺陷。所以,在对精度有严格要求的场合,应该选择使用固定精度类型,如:decimal.Decimal 。

    可通过 float.hex 方法输出实际存储值的十六进制格式的字符串,来查看执行结果为何不同;换句话说,也可以通过改方式实现浮点值的精确传递,避免精度丢失。

    >>> 0.1 * 3 == 0.3
    False
    >>> (0.1 * 3).hex()
    '0x1.3333333333334p-2'
    >>> (0.3).hex()     # 显然两个存储的内容并不相同
    '0x1.3333333333333p-2'
    
    >>> s = (1/3).hex()
    >>> float.fromhex(s)        # 反向转换回浮点数
    0.3333333333333333
    

    对于简单的比较操作,可尝试将浮点数的精度限制在有效的精度内,如:使用 round函数,但round函数在实现上有一定的问题,这里更加准确的问题是使用 decimal.Decimal 模块。

    >>> round(0.1 * 3, 2) == round(0.3, 2)
    True
    >>> round(0.1, 2) * 3 == round(0.3, 2)
    False
    

    decimal.Decimal模块

    与 float 这种基于硬件的二进制浮点类型相比,decimal.Decimal 是用十进制实现的,它能准确的表达十进制数和运算,不存在二进制相似值的问题,它最高可提供28位有效精度。

    >>> (0.1 + 0.1 + 0.1 - 0.3) == 0        # 二进制近似值计算结果与十进制预期不符合
    False
    >>> from decimal import Decimal
    >>> (Decimal('0.1') + Decimal('0.1') + Decimal('0.1') - Decimal('0.3')) == 0
    True
    

    而在创建 Decimal 实例的时候,应该传入一个准确数值,如:整数或者字符串等。如果传入的是 float 类型,那么在传入之前,其精度就已经丢失了。

    >>> Decimal(0.1)
    Decimal('0.1000000000000000055511151231257827021181583404541015625')
    >>> Decimal('0.1')
    Decimal('0.1')
    

    在需要的时候,可以通过上下文修改 Decimal 默认的28位精度或用 localcontext修改某个区域的精度。

    >>> from decimal import Decimal, getcontext, localcontext
    
    >>> getcontext()
    Context(prec=28, ...)
    >>> getcontext().prec = 2
    >>> Decimal(1) / Decimal(3)
    Decimal('0.33')
    
    >>> with localcontext() as ctx:
    ...     print(getcontext().prec)
    ...     getcontext().prec = 3
    ...     print(getcontext().prec)
    ...     print(Decimal(1) / Decimal(3))
    ...
    2
    3
    0.333
    >>> getcontext().prec
    2
    

    Decimal 虽然有着准确的精度,但它的运算速度会慢很多,所以除非有明确的需求,否则还是不要用 Decimal 代替 float 是用。

    round 函数

    因为精度和近似值问题,在使用round函数对 float 类型的值进行“四舍五入”的操作存在不确定性,结果会有一些不易察觉的陷阱。

    >>> round(0.5)      # 五舍
    0
    >>> round(1.5)      # 五入
    2
    

    这是因为round函数的算法规则是按临近的数字的距离远近来考虑是否进位的,如:以 0.4 为例,其舍入后相邻的数字分别是 0 和 1 ,从距离上来看自然是离 0 更近一些,所以“四舍五入”的结果为 0。如此一来,“四舍六入”就是确定的,相关问题都集中在两边距离相等的5是否进位上了。

    对于5是否进位,首先要考虑的是它后面是否还有小数位。如果有,那么左右距离自然是不想等的,这种情况肯定是会进位的。

    >>> round(0.5)
    0
    >>> round(0.500001)
    1
    

    如果没有,就要看进位后是整数还是浮点数了。如果是整数,就取临近的偶数。

    >>> round(0.5)
    0
    >>> round(1.5)
    2
    

    不同的Python版本,规则存在着差异性,如:在Python2.7中,round(2.5) 返回的是3.0。

    而进位后,如果依旧是浮点数的话,那事情就变得有点莫名其妙了。有的文章中说的是要看数字5前一位小数的奇偶行来判断是否进位,而事实上并非如此。

    >>> round(1.25, 1)      # 偶舍
    1.2
    >>> round(1.245, 2)     # 偶入
    1.25
    >>> round(2.675, 2)     # 下面都是奇数7,但却有舍有进
    2.67
    >>> round(2.375, 2)
    2.38
    

    对此,Python官方文档Floating Point Arithmetic: Issues and Limitations宣称着并非错误,而是事出有因。我们可以改用 Decimal ,按需选取可控的进位方案。

    转换

    在 Python 中将整数或字符串转换为浮点数很简单,而且 Python 还会自动处理字符串内的正负号和空白符。只是超出有效精度时,结果与字符串内容存在差异。

    >>> float(100)
    100.0
    >>> float('-100.123')
    -100.123
    >>> float('\t -100.123\n')
    -100.123
    >>> float('1.23E2')
    123.0
    >>> float('0.12345678901234567890')     # 超出精度
    0.12345678901234568
    

    反过来,将浮点数转换为整数时,有多种方案可供选择。

    1. 可直接截掉小数部分
    >>> int(2.6), int(-2.6)
    (2, -2)
    
    >>> from math import trunc
    >>> trunc(2.6), trunc(-2.6)
    (2, -2)
    
    1. 分别向大小两个方向取临近整数
    >>> from math import floor, ceil
    >>> floor(2.6), floor(-2.6)     # 向小数字方向取最近整数
    (2, -3)
    >>> ceil(2.6), ceil(-2.6)           # 向大数字方向取最近整数
    (3, -2)
    

    从博客搬运到简书:原文链接

    相关文章

      网友评论

        本文标题:「Python3学习笔记」读书笔记--float类型

        本文链接:https://www.haomeiwen.com/subject/trkhyftx.html