相关文章推荐
个性的跑步机  ·  2024-01-06 dlopen ...·  4 月前    · 
傻傻的甘蔗  ·  [解决异常] spring batch ...·  1 年前    · 

Python四舍五入与保留小数(精确舍入与保留)

本文深入讲解Python3中 四舍五入、截断保留 与 保留小数位数、取整 ,帮助Python初学者排坑!
总结不易,转载请标明出处: blog.csdn.net/weixin_41

0. 前言(吐槽)

作为一个Python初学者,偶然遇到需要利用Python3进行四舍五入的操作,就想着查一查有没有什么简单快捷的小函数一步到位。这不查不得了,一查就晕倒,网上的帖子和博客漫天飞,闭着眼睛瞎吹的文章一大堆,为了弄懂这一块,费了很多时间和精力。
刚开始很迷,毕竟是个Python小白,咱也不知道人家说的到底对不对,那就多看几个呗,看看咋回事,可谁成想越看越乱。
一开始基本都是“round函数可进行四舍五入运算”,然而我验证了一下发现round压根不行;
又看到有人说用格式控制字符“%.xf”直接就四舍五入到保留位数,emmm就仗着我是小白瞎告诉我呗;
再后来有个稍微好一点的,说用decimal模块的Decimal()+quantize(),还举了俩例子验证了下,emmm还自己把自己给验证错了,还说“原因未知,精度不高,慎用”…
于是我就自己去查了Python3.8.2的官方文档,看了decimal模块和round函数的说明,才恍然大悟。

好了,不多说,上干货!

1. 四舍五入(精确)

1.1 四舍五入并保留x位小数——用decimal模块中的Decimal方法(精度高)

① 数据存储需为字符串型

Decima(a)中的a需要是 字符串形式的数字 ,而不能直接是浮点型数字,因为浮点型数据本身就不是精确的。Why?

假设你输入的是0.1+0.1+0.1,你以为是0.3(是你臆想的十进制运算得到0.3),但在计算机存储中是以二进制浮点数存储的(计算机会自动将你的十进制0.3转化为二进制0.3,但是计算机的二进制存储可不像0.3这么精确),其实它对应的是十进制的0.300000000031或其他,因此并不是精确的。

>>> 0.1 + 0.1 +0.1 == 0.3
False
>>> 0.1 + 0.1 + 0.1
0.30000000000000004

而使用字符串存储输入的数据就可以精确存储输入的数据,即输入是多少输出就是多少。

② 模块及函数引入

导入decimal模块,则调用模块中函数时需用 模块名.函数名(参数) 的形式''

>>> import decimal
>>> a = "1.345"
>>> a_t = Decimal(a).quantize(Decimal("0.01"), rounding = "ROUND_HALF_UP")
>>> print(a_t)
1.35

导入decimal模块中的Decimal函数,则调用时只需用该模块中的 函数名(参数) 的形式

>>> from decimal import Decimal
>>> a = "1.345"
>>> #保留几位小数由像第二个括号中的几位小数决定,即保留两位小数,精确到0.01
>>> #如果想要四舍五入保留整数,那么第二个括号中就应该为"1."
>>> a_t = Decimal(a).quantize(Decimal("0.01"), rounding = "ROUND_HALF_UP")
>>> print(a_t)
1.35

③ rounding的舍入方式

decimal模块中默认rounding=ROUND_HALF_EVEN(可以通过getcontext().rounding命令查看), 即银行家式舍入法——四舍六入五成双式舍入法。

要想使用decimal模块中的Decimal()+quantize()方法实现精确的四舍五入,需要指定rounding=ROUND_HALF_UP——四舍五入法。

④ rounding舍入方式的指定

需要指出的是,如果仅从decimal模块中引入Decimal方法,那么对rounding = “ROUND_HALF_UP”应该在quantize()方法内指出,如下:

>>> from decimal import Decimal# 引用decimal模块中的Decimal方法
>>> a = '1.345'
>>> b = '2.345' # 需要用字符串存储要处理的小数
>>> # quantize内需要指出rounding为四舍五入方式
>>> a2 = Decimal(a).quantize(Decimal("0.01"), rounding = "ROUND_HALF_UP")
>>> b2 = Decimal(b).quantize(Decimal("0.01"), rounding = "ROUND_HALF_UP")
>>> print(a2)
>>> print(b2)
2.35

如果想单独使用getcontext().rounding = “ROUND_HALF_UP”来指定舍入方式为四舍五入方式,那就不能只引进Decimal方法,而需要引用整个decimal模块,如下:

>>> import decimal # 引用decimal模块,下面调用模块中的函数时不要忘记加decimal.前缀
>>> a = "1.345"
>>> b = "2.345" # a, b以字符串存储想要处理的小数
>>> A = 1.345
>>> B = 2.345   # A, B以浮点型存储想要处理的小数
>>> # 单独指出rounding为四舍五入方式
>>> decimal.getcontext().rounding = "ROUND_HALF_UP"# 不要忘记decimal.的前缀
>>> # quantize内无需再指出rounding
>>> a2 = decimal.Decimal(a).quantize(decimal.Decimal("0.01"))
>>> b2 = decimal.Decimal(b).quantize(decimal.Decimal("0.01"))
>>> A2 = decimal.Decimal(A).quantize(decimal.Decimal("0.01"))
>>> B2 = decimal.Decimal(B).quantize(decimal.Decimal("0.01"))
>>> print("a2 = ", a2)
a2 =  1.35
>>> print("b2 = ", b2)
b2 =  2.35
>>> print("A2 = ", A2)
A2 =  1.34
>>> print("B2 = ", B2)
B2 =  2.35

1.2 四舍五入后取整

① 使用Decimal方法,指定保留位数的括号内为”%1.”即可

>>> from decimal import Decimal
>>> a = '1.5'
>>> b = '2.5'
>>> #保留几位小数由像第二个Decimal括号中的几位小数决定,即保留两位小数,精确到0.01
>>> #如果想要四舍五入保留整数,那么第二个括号中就应该为"1."
>>> a_int = Decimal(a).quantize(Decimal("1."), rounding = "ROUND_HALF_UP")
>>> b_int = Decimal(b).quantize(Decimal("1."), rounding = "ROUND_HALF_UP")
>>> print(a_int)
>>> print(b_int)
3

② 使用int(a+0.5)方法(int为向0取整)

>>> a = 1.5
>>> b = 2.5
>>> a_int_roundup = int(a + 0.5)  # 对浮点数a先+0.5,再截断取整int,可实现四舍五入
>>> print("a_int = %d" % a)  # 对浮点数a直接用%d的格式控制符来控制输出整形数据,输出的a_int为截断的整形数据(并未四舍五入)
a_int = 1
>>> print("a_int_roundup = %d" % a_int_roundup)   # 输出四舍五入后的整形数
a_int_roundup = 2

凡是使用float型数据存储和处理,都不能达到像decimal模块这样的高精度

以下2—3的精度均不能实现精确的五入(四舍六入可实现)

2. 非精确的舍入

2.1 print("%.xf" % a)形式

>>> a = 1.15
>>> b = 1.25
>>> c = 1.35
>>> A = 1.345
>>> B = 2.345
>>> print("a_1 = %.1f" % a)
a_1 = 1.1
>>> print("b_1 = %.1f" % b)
b_1 = 1.2
>>> print("c_1 = %.1f" % c)
c_1 = 1.4
>>> print("A_2 = %.2f" % A)
A_2 = 1.34
>>> print("B_2 = %.2f" % B)
B_2 = 2.35

可见,对于利用格式控制字符来实现保留一定的小数位数并不能实现精确的四舍五入,也不是截断式保留。

Note: 对一个float型数据a,用print(“%d” % a),其结果是输出截断浮点数的整形数据。

2.2 format()形式

可针对 利用变量x指定保留位数 时,用format()形式,如:

>>> import math
>>> n, x = map(int, input().split()) # 一行输入两个数据,底数为n,保留位数为x
>>> a = n ** math.pi# 计算n的π次方
>>> print("%.{}f".format(x) % a)
156.993

2.3 round函数(精度低,尽量避免使用)

关于round函数,初期看了很多网上的帖子和博客,我也是醉了,有说是“四舍五入”规则的,有说是“四舍六入五成双”规则的,然而在我试验了仅仅几个数据之后就发现,压根没有这么简单。

>>> round(2.675,2)
>>> round(1.5)
>>> round(2.5)
>>> round(5.215,2)
5.21

就这几个例子就首先可以断定,round不是四舍五入的舍入方式了。


再来看一下什么是“四舍六入无成双”的原则,下面是维基百科和360百科的介绍:
维基百科——奇进偶舍
360百科——四舍六入五成双

但是当我输入以下几个例子时,发现round函数并不是这么回事。

>>> round(5.215,2)
>>> round(5.225,2)
>>> round(5.675,2)
>>> round(5.275,2)
5.28

如round(5.215,2)是保留两位小数,而原数据第3位是5,且后面没有有效数字,第2位是1,为奇数,按照“奇进偶舍”的原则,结果应该是5.22,但是实际却是输出5.21;
再比如round(5.675,2)是保留两位小数,而原数据第3位是5,且后面没有有效数字,第2位是7,为奇数,按照“奇进偶舍”的原则,结果应该是5.68,但是实际却是输出5.67;
再比如round(5.275,2),和round(2.675,2)相比小数点后第2位没有改变,仅改变了小数点后第1位,第2位仍为奇数,但是这里保留两位小数的结果却有了进位。

综上,可以看出, round函数既不是“四舍五入”的原则,也不是“四舍六入无成双”的原则。

那么到底是怎样的原则呢?

  • round函数的原则: (大前提——数值靠近哪边往取保留值)
    以下是Python3.8.2官方标准库文档的说明:
  • round函数的另一局限性:
    当希望保留指定位数的小数的末尾有多个连续的0时,会从后往前舍掉无用的0。即使指定了保留的位数也无法保留这些0,但是指定保留位数的情况下舍掉无用的0后至少也会保留到小数点后1位,而不会得到一个整数,如:
>>> round(1.2000,3)
>>> round(1.000,2)
1.0

对于round函数的大坑,可以参考一下这个 链接

友情提醒: 除非对精确度没什么要求,否则尽量避开用round()函数。如果对精度有要求,最好使用decimal模块。

3. 截断保留n位小数

① 用 浮点数处理 ——先放大指定的倍数n倍→然后取整int→再除以和放大倍数相等的倍数n倍

>>> print(int(1.23456 * 10000) / 10000)
1.2345

② 以 字符串形式 直接截断保留指定小数位数

# 以字符串形式截断保留指定小数位数
str, n = input(), int(input()) # 输入的小数以字符串形式存入str中,指定的保留位数以整数存入n中
for i in range(0, len(str)):
      if str[i] == '.':
            print(str[i], end = '')
            for j in range(1, n+1):
                  print(str[i+j], end = '')
            break
      else:
            print(str[i], end = '')

4. 一步取整方法

4.1 向上/下取整(ceil(a)函数与floor(a)函数)

import math
# 向上取整
>>> import math
>>> print("math.ceil(2.3) => ", math.ceil(2.3))
math.ceil(2.3) =>  3
>>> print("math.ceil(2.6) => ", math.ceil(2.6))
math.ceil(2.6) =>  3
# 向下取整
>>> print("math.floor(2.3) => ", math.floor(2.3))