菜鸟IT的博客 >> Python
【计算有问题,仅用于错误案例分析】二级制运算的精度丢失,精度误差,导致小数点后四舍五不入,decimal.Context(prec=5,rounding=decimal.ROUND_HALF_UP).create_decimal(str(x)) | 无法求浮点数小数保留小数点的问题
# -*- coding: UTF-8 -*-
# 使用round函数,round函数本身的四舍五入原则是对的。
# 只不过背后的精度问题,导致你表面眼睛看到的是四舍五不入。
# 二进制保存的值有点精度误差导致的。
import decimal
x=110.315
print("查看",x,"使用round函数保留2位小数之后的样子:",round(x,2))
print("查看",x,"的真实样子:",decimal.Decimal(x))
y=1002.145
print("查看",y,"使用round函数保留2位小数之后的样子:",round(y,2))
print("查看",y,"的真实样子:",decimal.Decimal(y))
print("换一种方法对付(不够动态,不够灵活,因为prec参数的值包含了小数点左边的位数):",x,end="→")
print(decimal.Context(prec=3,rounding=decimal.ROUND_HALF_UP).create_decimal(str(x)))
print("换一种方法对付(不够动态,不够灵活,因为prec参数的值包含了小数点左边的位数):",y,end="→")
print(decimal.Context(prec=3,rounding=decimal.ROUND_HALF_UP).create_decimal(str(y)))
# 但是上面函数里的prec是把小数点前面的数字部分的位数全部包含了。
# 这个就需要做判断了。
print("输出x的长度:",len(str(x)))
print("输出y的长度:",len(str(y)))
# 先对x进行分割切片,使用split()函数
# 这种对小数的切片,
x_FenGe=str(x).split(".")
y_FenGe=str(y).split(".")
print(x_FenGe)
print(y_FenGe)
# 比如我想对x保留3位小数,
# 那么我在上面的prec后面的数字应该是切割后小数点前面的部分的位数,加上我想保留的。
# 先计算分割后的列表的第1个元素的位数。
x_XsdLeftWS=len(x_FenGe[0])
y_XsdLeftWS=len(y_FenGe[0])
print("小数点",x,"的左边的数字位数:",x_XsdLeftWS)
print("小数点",y,"的左边的数字位数:",y_XsdLeftWS)
print("换一种方法对付(已经变的灵活,能正常保存我想要的小数点后面的位数了):",x,end="→")
print(decimal.Context(prec=3+x_XsdLeftWS,rounding=decimal.ROUND_HALF_UP).create_decimal(str(x)))
print("换一种方法对付(已经变的灵活,能正常保存我想要的小数点后面的位数了):",y,end="→")
print(decimal.Context(prec=3+y_XsdLeftWS,rounding=decimal.ROUND_HALF_UP).create_decimal(str(y)))
★★★★★——————————
输出结果:
查看 110.315 使用round函数保留2位小数之后的样子: 110.31
查看 110.315 的真实样子: 110.31499999999999772626324556767940521240234375
查看 1002.145 使用round函数保留2位小数之后的样子: 1002.14
查看 1002.145 的真实样子: 1002.14499999999998181010596454143524169921875
换一种方法对付(有隐患,因为prec参数的值包含了小数点左边的位数): 110.315→110
换一种方法对付(有隐患,因为prec参数的值包含了小数点左边的位数): 1002.145→1.00E+3
输出x的长度: 7
输出y的长度: 8
['110', '315']
['1002', '145']
小数点 110.315 的左边的数字位数: 3
小数点 1002.145 的左边的数字位数: 4
换一种方法对付(已经消除隐患,能正常保存我想要的小数点后面的位数了): 110.315→110.315
换一种方法对付(已经消除隐患,能正常保存我想要的小数点后面的位数了): 1002.145→1002.145
进程已结束,退出代码0
最后总结:
要导入库
import decimal
然后
y=decimal.Context(prec=3+len(str(x).split(".")[0]),rounding=decimal.ROUND_HALF_UP).create_decimal(str(x))
其中上面的3就是保留3位小数
x就是开始的小数。
################################################################
于2022年3月25日,偶然一次测试中,发现大问题。怪我自己没测试好。
原始参考代码:
y=decimal.Context(prec=3,rounding=decimal.ROUND_HALF_UP).create_decimal(str(x))
我改进的代码:
z=decimal.Context(prec=3+len(str(x).split(".")[0]),rounding=decimal.ROUND_HALF_UP).create_decimal(str(x))
我多来几个案例,分析下吧:
# ——————————————————
比如x=0.05477
输出结果↓
用于测试的原始小数: 0.05477
我之前参考的代码算出来的结果: 0.0548
我改进我参考的代码算出来的结果: 0.05477
# ——————————————————
再试试 x=1.05477
输出结果↓
用于测试的原始小数: 1.05477
我之前参考的代码算出来的结果: 1.05
我改进我参考的代码算出来的结果: 1.055
# ——————————————————
再试试 x=123.05477
输出结果↓
用于测试的原始小数: 123.05477
我之前参考的代码算出来的结果: 123
我改进我参考的代码算出来的结果: 123.055
# ——————————————————
再试试 x=100.05477
输出结果↓
用于测试的原始小数: 100.05477
我之前参考的代码算出来的结果: 100
我改进我参考的代码算出来的结果: 100.055
# ——————————————————
再试试 x=2.05477
输出结果↓
用于测试的原始小数: 2.05477
我之前参考的代码算出来的结果: 2.05
我改进我参考的代码算出来的结果: 2.055
# ——————————————————
再试试 x=2.00007
输出结果↓
用于测试的原始小数: 2.00007
我之前参考的代码算出来的结果: 2.00
我改进我参考的代码算出来的结果: 2.000
# ——————————————————
再试试 x=2.01007
输出结果↓
用于测试的原始小数: 2.01007
我之前参考的代码算出来的结果: 2.01
我改进我参考的代码算出来的结果: 2.010
# ——————————————————
再试试 x=2.00107
输出结果↓
用于测试的原始小数: 2.00107
我之前参考的代码算出来的结果: 2.00
我改进我参考的代码算出来的结果: 2.001
# ——————————————————
再试试 x=2.00607
输出结果↓
用于测试的原始小数: 2.00607
我之前参考的代码算出来的结果: 2.01
我改进我参考的代码算出来的结果: 2.006
# ——————————————————
再试试 x=0.00667
输出结果↓
用于测试的原始小数: 0.00667
我之前参考的代码算出来的结果: 0.00667
我改进我参考的代码算出来的结果: 0.00667
################################################################
我要的最终目的是写1个函数,强制的、正确四舍五入的、保留3位小数。
如果你给1个小数,比如100.00153要求保留3位小数。
那就正确的四舍五入,给3位小数,100.002
但是,经过我的测试,。大约1的小数都正常得出结果。
但是小于1的小数,就失效了。
————————————————————
#######################网站上再次找到的文章#########################################
参考链接:https://blog.csdn.net/haeasringnar/article/details/105471581
# ——————————————
# 使用decimal 和 getcontext 提高精度
from decimal import Decimal, getcontext
print('未指定精度')
print(Decimal(0.1) + Decimal(0.2)) # 发现使用decimal 计算时,小数后面计算的更加的精确
print(Decimal(1.234) + Decimal(1.2))
print('指定精度')
getcontext().prec = 4 # 指定精度,包含非零整数部分
print(Decimal(0.1) + Decimal(0.2)) # 再次输出,小数精度会发生改变。值得注意的是,它不会抹除0
print(Decimal(1.234) + Decimal(1.2))
————————————————
# 使用round 提高精度
# 语法 round(val, num) 参数为传入的数据和指定的小数点位数
print(round(0.1 + 0.2, 3)) # 指定小数点保留的位数,当最右边存在0时,自动抹除,进位采用四舍五入
print(round(1.234, 2))
print(round(1.235, 2))
############## round 函数也存在精度丢失问题 ↓############################
参考文章:https://blog.csdn.net/u013946150/article/details/116194440
问题
一般的四舍五入操作都是使用内置的round方法,但有时候会出现问题。比如
In [1]: round(2.675,2)
Out[2]: 2.67
为什么不是2.68呢?那是因为float精度缺失导致的。
In [3]: Decimal(2.675)
Out[4]: Decimal('2.67499999999999982236431605997495353221893310546875')
你会发现,2.675其实是2.6749999999…,四舍五入可不是2.67么?那这个问题怎么解决呢?
方法
可以使用str.format来格式化数字实现四舍五入
from decimal import Decimal
In [4]: '{:.2f}'.format(Decimal('2.675'))
Out[5]: '2.68'
############## round 函数也存在精度丢失问题 ↑############################
############## 当我换成保留3位小数时,问题依旧 ↓############################
上面这个方法依然不行,比如我要保留3位小数
源码如下:
from decimal import Decimal
x=1.0545
# 原先参考的代码
y= '{:.3f}'.format(Decimal(str(x)))
print(y)
print(y,"的输出结果的数据类型:",type(y))
print("试试再转成浮点数:",float(y))
# ————————
输出结果:
1.054
1.054 的输出结果的数据类型: <class 'str'>
试试再转成浮点数: 1.054
菜鸟IT博客[2021.10.16-19:56] 访问:2068