python3在使用round函数对浮点数进行四舍五入时结果经常不是我们预期想要得结果! 造成这样的原因是什么? 还得从计算机说起…
一、计算机中小数精度问题
在计算机中部分小数能被精确的保存,还有一部分小数不能被精确的保存。
小数转化为二进制
将一个小数转化为二进制表示的方式是,不断的乘2,取其中的整数部分。
能精确保存的小数
例如:0.625
(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.6
(1) 0.6 * 2=1.2 整数部分为1,小数部分为0.2
(2) 0.2 * 2=0.4 整数部分为0,小数部分为0.4
(3) 0.4 * 2=0.8 整数部分为0,小数部分为0.8
(4) 0.8 * 2=1.6 整数部分为1,小数部分为0.6
(5) 0.6 * 2=1.2 整数部分为1,小数部分为0.2
…
这就造成在计算机中很多二进制浮点中没有精确的表示。
二、python中round方法
再来看我们round()方法:
round() 方法返回浮点数x的四舍五入值。
print(round(0.175,2)) ##0.17 0.17499999999999998889776975374843459576368331909180
print(round(0.275,2)) ##0.28 0.27500000000000002220446049250313080847263336181641
print(round(0.375,2)) ##0.38 0.375
print(round(0.475,2)) ##0.47 0.47499999999999997779553950749686919152736663818359
print(round(0.575,2)) ##0.57 0.57499999999999995559107901499373838305473327636718
print(round(0.675,2)) ##0.68 0.67500000000000004440892098500626161694526672363281
print(round(0.775,2)) ##0.78 0.77500000000000002220446049250313080847263336181640
print(round(0.875,2)) ##0.88 0.875
print(round(0.975,2)) ##0.97 0.97499999999999997779553950749686919152736663818359
print(round(0.125,2)) ##0.12 0.125
##0.17 是运行代码以后的值
0.17499999999999998889776975374843459576368331909180 是0.175保存的值,我们0.175这个值并没有被精确的保存。
对于round(0.175,2)和round(0.275,2)运行以后分别得到0.12和0.28是因为计算机小数不精确而造成的.
那么0.875 和 0.125是保留精确的,为什么0.125的5被舍弃掉了,而0.875却被进位了?来我们接着看!
python3.5的doc中说:“values are rounded to the closest multiple of 10 to the power minus ndigits; if two multiples are equally close, rounding is done toward the even choice.”
如果距离两边一样远,会保留到偶数的一边。
???这是什么意思,了解了四舍六入五成双后你就明白了。
三、四舍六入五成双
四舍五入我们都知道但是四舍六入五成双是什么意思呢?
例如对于一个小数a.bcd,需要精确到小数点后两位,那么就要看小数点后第三位:
如果d小于5,直接舍去
如果d大于5,直接进位
如果d等于5:
- d后面没有数据,且c为偶数,那么不进位,保留c
- d后面没有数据,且c为奇数,那么进位,c变成(c + 1)
如果d后面还有非0数字,例如实际上小数为a.bcdef,此时一定要进位,c变成(c + 1)
举例:保留两位小数
9.8150=9.82
9.8250=9.82
四、小结
我们使用round方法的时候,首先要看小数在计算机中能不能被精确储存?如果不能,那么它可能并没有达到四舍五入的标准,例如0.175,它的小数点后第三位实际上是4,当然会被舍去。
如果你的这个小数在计算机中能被精确表示,那么,round采用的进位机制是奇进偶舍,所以这取决于你要保留的那一位,它是奇数还是偶数,以及它的下一位后面还有没有数据。
五、decimal模块
在做小数运算或者四舍五入时怎么避免,数据不精确的问题呢?这就要用到Decimal模块。
https://docs.python.org/zh-cn/3/library/decimal.html#rounding-modes
中文翻译的官方文档!
Decimal 对象
lass decimal.Decimal(value=“0”, context=None)
根据 value 构造一个新的 Decimal 对象。
value 可以是整数,字符串,元组,float ,或另一个 Decimal 对象。 如果没有给出 value,则返回 Decimal(‘0’)。 如果 value 是一个字符串,它应该在前导和尾随空格字符以及下划线被删除之后符合十进制数字字符串语法:
运算方面:
# -*- coding:utf-8 -*-
from decimal import *
number1 = 1.4-0.66666
number2 = Decimal('1.4') - Decimal('0.66666')
print(number1,number2) ##number1 = 0.7333399999999999 number2 = 0.73334
使用Decimal以后运算出来的是我们想要的值!另外我们传入的是字符串,不在是浮点数,因为部分浮点数保留不精确。
四舍五入:
origin_num = Decimal('0.145')
answer_num1 = origin_num.quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
answer_num2 = origin_num.quantize(Decimal('0.00'), rounding=ROUND_HALF_EVEN)
print(answer_num1,answer_num2) ##0.15 0.14
ROUND_HALF_UP 我们熟悉的四舍五入
ROUND_HALF_EVEN 四舍六入五成双
有错误的地方欢迎指正!