计算机使用定点格式和浮点格式来表示数值,浮点数无法精确地表示数值,导致在浮点数运算的过程中会有误差,比如:
>>> 0.1 + 0.2 == 0.3
False
计算机的浮点数运算误差和计算机底层的二进制表示方式有关,无论使用什么语言来进行浮点数运算,都会遇到精度的问题。
要解决精度问题,可以通过模拟运算或者允许很小范围的误差。
定点数和浮点数
顾名思义,定点数是指小数点位置是固定的,浮点数是指小数点位置是不固定的。
定点数
用定点数表示整数的时候,小数点的位置是固定在最右边的。用定点数表示小数的时候一般小数点固定在最左边。
计算机能够准确地表示一定范围内的整数。整数分为无符号整数和有符号整数,对于使用n位存储的无符号整数,最小整数所有位为0,最大整数所有位为1,所以能存储的整数范围是0到;一般所有的机器都是以补码的形式表示有符号整数的,对于使用n位存储的有符号整数,最高位是符号位,符号位为1表示负数,符号位位0表示正数,对应的十进制的整数是,最小负数符号位为1其余位为0,最大正数符号位为0,其他位为1,所以能存储的整数范围是到。
浮点数
定点数能表示的数的范围是有限的,为了能表示更大范围的数字,需要使用浮点数。
使用浮点数表示时,数字被表示为,其中M是尾数,E是阶码,R是阶码的基数,R为2,随着M和E的值的不同,小数点的位置是变化的(浮动的)。
这里只简单描述IEEE浮点标准,以了解为什么浮点数无法精确地表示数值,其实数值被编码的方式比本文所描述的要复杂一些。
IEEE浮点标准中数的表示形式是:
其中s是符号,E是阶码,M是尾数。
IEEE浮点标准中的规格化的32位(单精度)浮点数的表示为:
这个公式对应E的值既不是全为0,又不是全为1时的情况,127是偏置,等于,8是E的位数。
非规格化的单精度浮点数表示为:
这个公式对应的E值全为0的情况。
因为对于一定范围内的整数,都可以使用或准确表示出来,所以定点数能准确地表示一定范围内的整数,但是对于一定范围内的实数,有的数是不可以使用这种形式表示的,所以浮点数无法准确地表示数值,无法被该公式表示的值只能被近似地表示。
精度问题的解决方法
以Python语言为例,使用Python进行浮点数运算时,也会遇到浮点数精度的问题。
>>> 0.1
0.1
这里打印出的内容是0.1是Python自己做了四舍五入,打印出的内容不是实际存储在计算机的内容。在计算机存储中,是不能精确表示的,存储的是一个接近的值:,即0.1000000000000000055511151231257827021181583404541015625,当我们输出0.1的时候,直接输出存储在机器中的精确值,是非常不直观的,所以会通过四舍五入直接输出0.1。想要知道一个浮点数的精确的值的时候,可以使用float.as_integer_ratio()方法。
>>> (0.1).as_integer_ratio()
(3602879701896397, 36028797018963968)
在机器存储中,0.1和0.2都不能被精确的表示,都有误差,两者相加后也会有误差。
>>> 0.1 + 0.2 == 0.3
False
模拟计算
对于需要精确的十进制表示的情况,可以使用 decimal模块,它实现了十进制运算,适用于记账应用等需要高精度数据的应用。
>>> from decimal import Decimal
>>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
True
还可以使用实现了基于有理数的运算的 fractions模块。
>>> from fractions import Fraction
>>> Fraction(1, 10) + Fraction(1, 5) == Fraction(3, 10)
True
允许很小范围的误差
自己定义一个误差的值,比如0.0000001,小于这个误差就认为没有误差。
>>> def __eq__(a, b):
... return (a - b) < 0.0000001
...
>>> __eq__(0.1 + 0.2, 0.3)
True
或者使用math包的isclose函数:
>>> from math import isclose
>>> isclose(0.1 + 0.2, 0.3)
True