第一次写python,真是蛇年学python的节奏。
在本程序中想进行如下一个循环,并在最后一层中进行一个if判断:当ini_allocation中得元素之和为1时进行下面的part。
1 for i in range(0,11):
2 ini_allocation[1] = 0.0
3
4 for j in range(0,11):
5 ini_allocation[2] = 0.0
6
7 for k in range(0,11):
8 ini_allocation[3] = 0.0
9
10 for ii in range(0,11):
11 if ini_allocation[2]+ini_allocation[3]+ini_allocation[1]+ini_allocation[0] == 1.0:
#每个ini_allocation在循环末尾都会递增0.1
在运行的过程中,发现ini_allocation无法遍历到[0.0, 0.0, 0.2, 0.8]以上,也就是ini_allocation[3]递增到0.8时,if语句的逻辑值已经为false。
为此,继续采用以下代码进行debug
1 for ii in range(0,11):
2 if ini_allocation == [0.0, 0.0, 0.2, 0.8]:
3 print "here"
4 if ini_allocation[2]+ini_allocation[3]+ini_allocation[1]+ini_allocation[0] == 1.0:
5 print "true"
6 else:
7 print "false"
在terminal中输出结果,既没有“here”,也没有“true”。ini_allocation[3]根本不等于0.8啊,且ini_allocation之和不为1.0. 额,难道0.2+0.8不等于1.0么?
虽然python能很好地支持浮点数运算,但是python中浮点数是用二进制分数进行表示的。以上代码中得浮点数是由十进制分数表示,因此在其转换为二进制分数时面临着尾数上的截断误差。为什么会有截断误差呢?首先要从十进制小数转换为二进制小数的方法说起,
已知十进制小数B, 可以将B展开为2的负指数幂的级数,有
B=a*(2^-1)+b*(2^-2)+c*(2^-3)+...
那么将B转换为二进制小数时,只需要进行以下的步骤,其中A代表的是二进制小数的小数部分,i从小数第一位开始计数:
B = B*2
i = 1
LOOP
IF B > 1
A[i]=1
ELSE
A[i]=0
i++
END
大部分小数B不能由2的负指数幂的有限级数表示,如0.7,其二进制小数位无线循环小数。但是计算机的位数有限,无法存储无限位的小数,因此会在小数末尾位将不能存储的部分进行截断,产生截断误差。
最初的程序中,从0.0递增到1.0,不免会遇到0.5,0.7这类二进制下无限循环小数,产生截断误差。而python只会呈现(display)出这些小数的近似值,因此我们的ini_allocation[3]可能和0.8不等,而且理论上误差是随意的。
如何解决这样的问题呢?对于精度要求不高的程序,如本程序只要求精确到小数点第一位,那么我们调用decimal模块提高浮点数运算的精度,然后将精确位以后的小数位给“人工截断”
from decimal import *
利用round函数进行截断操作,该函数第一个变量是需要截断的浮点数,第二个是精确的位数。
1 sum2 = Decimal(ini_allocation[0])+Decimal(ini_allocation[1])+Decimal(ini_allocation[2])+Decimal(ini_allocation[3]) # float calculation
2 sum2 = round(sum2,1) # reduce the precision to 1 deci
通过这样,就消除浮点数对本程序的影响了,这样0.8+0.2=1.0了。
References:
http://docs.python.org/2/tutorial/floatingpoint.html the float-number in python
http://docspy3zh.readthedocs.org/en/latest/tutorial/floatingpoint.html the float-number in python
decimal and round()
http://baike.baidu.com/view/1426817.htm 10-binary to 2-binary