用python进行运算时,如果会出现浮点数,有时候会需要对小数精度进行控制,主要方法有以下几种:
1、round()
round()是python的内置方法, round()如果只有一个数作为参数,不指定位数的时候,返回的是一个整数,而且是最靠近的偶数整数(python2.7版本则是四舍五入,这个针对的是python3版本)。如果有两个参数则最多保留精度为第二个参数大小。
In [1]: 4 - 3.6
Out[1]: 0.3999999999999999
In [2]: round(4 - 3.6)
Out[2]: 0.0
In [3]: round(4-3.6,2)
Out[3]: 0.4
In [4]: round(4-3.6,5)
Out[4]: 0.4
In [5]: round(2.55555,3)
Out[5]: 2.556
In [6]: round(2.555,30)
Out[6]: 2.555
In [7]: round(2.5) #注意这个从python3开始是向偶数看齐,即整数部分为偶数时舍弃小数部分,为奇数时进一位
Out[7]: 3.0 #python3输出为2
In [8]: round(3.5)
Out[8]: 4.0
python文档说明如下:
round(number[, ndigits])
Return the floating point value number rounded to ndigits digits after the decimal point. If ndigits is omitted, it defaults to zero. The result is a floating point number. Values are rounded to the closest multiple of 10 to the power minus ndigits; if two multiples are equally close, rounding is done away from 0 (so, for example, round(0.5) is 1.0 and round(-0.5) is -1.0).
Note
The behavior of round() for floats can be surprising: for example, round(2.675, 2) gives 2.67 instead of the expected 2.68. This is not a bug: it’s a result of the fact that most decimal fractions can’t be represented exactly as a float. See Floating Point Arithmetic: Issues and Limitations for more information.
注意:round第二个参数可以为负数,这是用在整数位四舍五入的时候:
In [222]: round(12345, -1)
Out[222]: 12340
In [223]: round(12345, -2)
Out[223]: 12300
In [224]: round(12345, -3)
Out[224]: 12000
In [225]: round(15689, -4)
Out[225]: 20000
In [226]: round(15689, -3)
Out[226]: 16000
In [227]: round(15689.3, -3)
Out[227]: 16000.0
2、使用格式化
In [9]: '%.2f'%3.1415926
Out[9]: '3.14'
In [11]: float('%.3f'%1.234567890)
Out[11]: 1.235
Python默认的是17位精度,也就是小数点后16位,但是这里有一个问题,就是当我们的计算需要使用更高的精度(超过16位小数)的时候该怎么做呢?
方法1:继续像上面使用格式化
In [12]: '%.30f'%(1.0/3)
Out[12]: '0.333333333333333314829616256247'
方法2:使用decimal模块,配合使用其中的getcontext
In [1]: from decimal import Decimal, getcontext
In [2]: Decimal(1) / Decimal(3)
Out[2]: Decimal('0.3333333333333333333333333333')
In [3]: getcontext().prec = 10
In [4]: Decimal(1) / Decimal(3)
Out[4]: Decimal('0.3333333333')
In [5]: len(str(_))
Out[5]: 12 #有2个是前面的0和小数点,所以恰好是10位
In [6]: print getcontext()
Context(prec=10, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, capitals=1, flags=[Inexact, Rounded], traps=[DivisionByZero, Overflow, InvalidOperation])
In [239]: from decimal import localcontext
In [240]: a = Decimal('1.3')
In [241]: b = Decimal('1.7')
In [242]: a / b
Out[242]: Decimal('0.7647058823529411764705882353')
In [243]: print (a/b)
0.7647058823529411764705882353
In [244]: with localcontext() as ctx:
...: ctx.prec = 3
...: print (a/b)
...:
0.765
In [245]: with localcontext() as ctx:
...: ctx.prec = 50
...: print (a/b)
...:
0.76470588235294117647058823529411764705882352941176
具体见:https://docs.python.org/2.7/library/decimal.html#module-decimal
另:注意下这种情况:
In [247]: nums = [1.23e+18, 1, -1.23e+18]
In [248]: sum(nums) #注意这个,输出根本不是预想中的那样
Out[248]: 0.0
此时需要math
模块:
In [254]: import math
In [255]: math.fsum(nums)
Out[255]: 1.0
In [256]: math.fsum?
Docstring:
fsum(iterable)
Return an accurate floating point sum of values in the iterable.
Assumes IEEE-754 floating point arithmetic.
Type: builtin_function_or_method
原理详解部分:
我们知道,将一个小数转化为二进制表示的方式是,不断的乘2,取其中的整数部分。例如
(1) 0.625*2 = 1.25, 整数部分为1,小数部分为0.25
(2) 0.25 * 2 = 0.5 , 整数部分为0,小数部分为0.5
(3) 0.5 * 2 = 1 , 整数部分为1,小数部分为0
所以0.625的二进制表示就是0.101。
然而有些小数,例如0.4,并不能够精确的转化为二进制表示,用上面的这种方法计算:
(1) 0.4*2=0.8 整数部分为0,小数部分为0.8
(2) 0.8*2=1.6 整数部分为1,小数部分为0.6
(3) 0.6*2=1.2 整数部分为1,小数部分为0.2
(4) 0.2*2=0.4 整数部分为0,小数部分为0.4
(5) 0.4*2=0.8 整数部分为0,小数部分为0.8
(6) 0.8*2=1.6 整数部分为1,小数部分为0.6
(7) 0.6*2=1.2 整数部分为1,小数部分为0.2
……
所以0.4转化为二进制,应该是0.0110… 这样一个无限循环小数。
计算机的内存、cpu寄存器等等这些硬件单元都是有限的,只能表示有限位数的二进制位,因此存储的二进制小数就会和实际转换而成的二进制数有一定的误差。(你可以试着将0.3转化为二进制表示,也将出现一个循环小数。)
实际上,大多数情况下,小数在计算机中是以一种类似科学计数法的形式表示的,具体的可以参考一下其他的资料。但即便如此,仍然存在误差。
浮点数是用机器上浮点数的本机双精度(64 bit)表示的。提供大约17位的精度和范围从-308到308的指数。和C语言里面的double类型相同。Python不支持32bit的单精度浮点数。所以在python中不建议直接将两个浮点数进行大小比较,或者做精确的计算,往往会得到意想不到的结果。当然,如果非要用,可以参考decimal模块的相关内容。如果程序需要精确控制区间和数字精度,可以考虑使用numpy扩展库。
另外记录个链接:http://justjavac.com/codepuzzle/2012/11/11/codepuzzle-float-who-stole-your-accuracy.html