python之关于round函数的bug

round()函数:此函数慎用!!!
作用:返回浮点数x的“奇进偶舍值”。

round() 方法的语法:
round( x [, n] )

参数:
x – 数值表达式。
n – 数值表达式,表示保留小数点后的位数。

返回值:
返回浮点数x的“奇进偶舍值”。

注意:-------------- 该方法和python版本有关。
阅读python的文档,里面是这么写的:
在python2.7的doc中,round()的最后写着,“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.” 保留值将保留到离上一位更近的一端(四舍六入),如果距离两端一样远(即要舍去或者进位的那一位是五),则保留到离0远的一边。所以round(0.5)会近似到1,而round(-0.5)会近似到-1。

但是到了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." 如果距离两端一样远,会保留到偶数的一边。比如round(0.5)和round(-0.5)都会保留到0,而round(1.5)会保留到2。

奇进偶舍: 又称为四舍六入五成双规则、银行进位法(Banker’s Rounding),是一种计数保留法,是一种数值修约规则(在进行具体的数字运算前,通过省略原数值的最后若干位数字,调整保留的末位数字,使最后所得到的值最接近原数值的过程。)。从统计学的角度,“奇进偶舍”比“四舍五入”更为精确:在大量运算时,因为舍入后的结果有的变大,有的变小,更使舍入后的结果误差均值趋于零。而不是像四舍五入那样逢五就进位,导致结果偏向大数,使得误差产生积累进而产生系统误差。“奇进偶舍”使测量结果受到舍入误差的影响降到最低。
我们发现,使用round()取整小数时,并不是想要的四舍五入,原因就在于取整规则是采用了奇进偶舍(四舍六入五成双)的方式,简单来说就是:整数部分为奇数,四舍五入;如果是偶数,就采用五舍六入的方式,而这个规则,就属于数值修约的规则。

下面我们用实例演示:

a = round(1.4)
b = round(1.5)
c = round(1.6)
d = round(2.4)
e = round(2.5)
f = round(2.6)
print('整数部分为奇数时:',a , b, c )
print('整数部分为偶数时:',d , e, f )

运行结果:
整数部分为奇数时: 1 2 2
整数部分为奇数时: 2 2 3

由以上结果发现,round函数在保留到整数时确实使用的奇进偶舍方法。

但是: 上面我们看到了浮点数去掉小数变成整数时是奇进偶舍法,下面来看看浮点数不保留到整数时是不是还是奇进偶舍法。

a = round(1.45,1)
b = round(2.45,1)
c = round(1.15,1)
d = round(2.15,1)
print(a)
print(b)
print(c)
print(d)
运行结果:
1.4
2.5
1.1
2.1

————>发现了什么????是不是跟你想的不一样
来分析分析:
如果round函数在不保留到整数的情况下:
假设1:如果它使用的是四舍五入法呢,结果是什么??----> a = 1.5,b=2.5,c=1.2,d=2.2;显然,结果不对啊,这种假设pass;
假设2:如果它使用的是奇进偶舍法呢,即整数部分为奇数,四舍五入;整数部分为偶数,就采用五舍六入的方式,结果是什么??----> a = 1.5,b=2.4,c=1.2,d=2.1;显然,结果也不对啊,这种假设pass;
假设3:如果它使用的是奇进偶舍法呢,但是,不是看的是整数部分,而是看保留到的那一位(上例子中看小数点后一位)该位为奇数,四舍五入;该位为偶数,就采用五舍六入的方式,结果是什么??----> a = 1.4,b=2.4,c=1.2,d=2.2;显然,结果也不对啊,这种假设还是pass;

那到底为什么会出现这种情况呢?原因在此:
这跟浮点数的精度有关。我们知道在机器中浮点数不一定能精确表达,在计算机中存入的是二进制数(0和1),因为一些浮点数在换算成一串1和0后可能是无限位的,机器已经做出了截断处理。那么在机器中保存的这个数字就比实际数字要小或者大。这就导致了结果不准确的原因。

对于该原因,还有一个很著名的例子,那就是:


python3中:0.1+0.2的结果不等于0.3


此处我用的是python3.6.6

a = 0.1
b = 0.2
print(a + b)
print(0.1 + 0.2)
print(eval('0.1 + 0.2'))
运行结果:
0.30000000000000004
0.30000000000000004
0.30000000000000004

是不是很意外!!!!,同样的例子还有:

a = 0.6
b = 0.7
print(a + b)
运行结果:
1.2999999999999998

a = 0.8
b = 0.9
print(a + b)
运行结果:
1.7000000000000002

那么,除非对精确度没什么要求,否则尽量避开用round()函数,所以round()函数要慎用!!!
近似计算我们还有其他的选择:

  1. 使用math模块中的一些函数,比如math.ceiling(天花板除法)。
  2. python自带整除,python2中是/,3中是//,还有div函数。
  3. 字符串格式化可以做截断使用,例如 “%.2f” %value(保留两位小数并变成字符串……如果还想用浮点数请披上float()的外衣)。
  4. 当然,对浮点数精度要求如果很高的话,请用decimal模块。

以下是对decimal模块的介绍:

decimal 模块:decimal意思为十进制,这个模块提供了十进制浮点运算支持
1.可以传递给Decimal整型或者字符串参数,但不能是浮点数据,因为浮点数据本身就不准确。
2.要从浮点数据转换为Decimal类型

from decimal import *
a = Decimal.from_float(12.222)
print(type(a), a)
运行结果:
<class 'decimal.Decimal'> 12.2219999999999995310417943983338773250579833984375

3.通过设定有效数字,限定结果样式

from decimal import *
getcontext().prec = 6  #保留六个有效数字
a = Decimal(1)/Decimal(7)
print(type(a), a)
运行结果:
<class 'decimal.Decimal'> 0.142857

4.四舍五入,保留几位小数

from decimal import *
a = Decimal('51.56501').quantize(Decimal('0.00'))
print(type(a), a)
运行结果:
<class 'decimal.Decimal'> 51.57

5.Decimal 结果转化为string

from decimal import *
a = str(Decimal('3.40').quantize(Decimal('0.0')))
print(type(a), a)
运行结果:
<class 'str'> 3.4

Python3中decimal处理计算精度问题示例:

#!/usr/bin/python3.6.6
# coding:utf-8
import decimal
from decimal import Decimal, getcontext

def demo():
  """
  取整问题:
  ROUND_CEILING 总是趋向无穷大向上取整
  ROUND_DOWN 总是趋向0取整
  ROUND_FLOOR 总是趋向负无穷大向下取整
  ROUND_HALF_DOWN 如果最后一个有效数字大于或等于5则朝0反方向取整;否则,趋向0取整
  ROUND_HALF_EVEN 类似于ROUND_HALF_DOWN,不过,如果最后一个有效数字值为5,则会检查前一位。
          偶数值会导致结果向下取整,奇数值导致结果向上取整
  ROUND_HALF_UP 类似于ROUND_HALF_DOWN,不过如果最后一位有效数字为5,值会朝0的反方向取整
  ROUND_UP 朝0的反方向取整
  ROUND_05UP 如果最后一位是0或5,则朝0的反方向取整;否则向0取整
  """

  # 1.常规计算
  getcontext().prec = 9
  r1 = Decimal(1) / Decimal(3)
  print("r1 ", r1) # r1 0.333333333

  # 2.但是getcontext().prec会包含小数点前面的所有长度,当前面长度有变化时并不能固定控制小数点后的位数
  r2 = Decimal(10) / Decimal(3)
  print("r2 ", r2) # r2 3.33333333

  # 3.想要固定控制小数点后面的位数则需要使用decimal.quantize(Decimal('0.00000000')),注意不能超过getcontext().prec的位数
  r3 = Decimal(1) / Decimal(3)
  print("r3 ", r3.quantize(Decimal('0.00000000'))) # r3 0.33333333
  r4 = Decimal(10) / Decimal(3)
  print("r4 ", r4.quantize(Decimal('0.00000000'))) # r4 3.33333333
  r5 = Decimal(10) / Decimal(str(1.5))
  print("r5 ", r5.quantize(Decimal('0.00000000'))) # r5 6.66666667

  # 4.向上取整
  getcontext().rounding = getattr(decimal, 'ROUND_CEILING') # 总是趋向无穷大向上取整
  r6 = Decimal(10) / Decimal(str(1.5)) # r6 6.66666667
  print("r6 ", r6.quantize(Decimal('0.00000000')))
  r7 = Decimal(10) / Decimal(3) # r7 3.33333334
  print("r7 ", r7.quantize(Decimal('0.00000000')))

  # 5.向下取整
  getcontext().rounding = getattr(decimal, 'ROUND_FLOOR') # 总是趋向无穷大向下取整
  r8 = Decimal(10) / Decimal(str(1.5)) # r8 6.66666666
  print("r8 ", r8.quantize(Decimal('0.00000000')))
  r9 = Decimal(10) / Decimal(3) # r9 3.33333333
  print("r9 ", r9.quantize(Decimal('0.00000000')))

if __name__ == '__main__':
  demo()

运行结果:
r1  0.333333333
r2  3.33333333
r3  0.33333333
r4  3.33333333
r5  6.66666667
r6  6.66666667
r7  3.33333334
r8  6.66666666
r9  3.33333333

你可能感兴趣的:(python,python笔(面)试题)