之前在学习Django中ORM的时候,一直不明白怎么有一个数据类型叫Decimal。直到最近在一个项目中遇到了小数点计算的问题,发现要解决小数计算的精度问题,还真得用到这个Decimal不可。


文章目录

  • 问题描述
  • 解决办法
  • 四舍五入
  • Decimal


问题描述

浮点数在内存中存储的时候因为存储机制的原因,天然存在精度丢失的现象,具体原因这里不赘述了

a=2.2
a*3
Out[16]: 6.6000000000000005

本来期望的结果是6.6,结果小数点后多出来一长串。如果是对数字不敏感的项目其实是没问题的,但是如果涉及到金融领域,例如电商的购物车价格计算,银行余额的计算,小数点后10几位多个5也都是不能容忍的。

所以有能力精确进行浮点数计算还是有必要的。

解决办法

四舍五入

四舍五入应该算是最容易想到的,只要规定四舍五入到小数点后1位,就可以解决上面的问题。python中也有一个内建的round()方法可以来完成这一功能,其中带两个参数分别是浮点数以及小数点后的位数。

round(1.8)
Out[22]: 2
round(1.2)
Out[23]: 1
round(1.2452,2)
Out[24]: 1.25

但是这个函数似乎并不完全按照四舍五入的规则来的

round(1.5)
Out[25]: 2
round(2.5)
Out[26]: 2

后来查了一下才发现,如果一个数字处在两个整数中间的时候,round方法会返回离的最近的偶数。

看来四舍五入的方法在python中不是很靠谱

不过要注意,四舍五入和格式化输出还不是一回事,python中的格式化输出使用的是format()方法

Decimal

之所以会出现浮点数精度问题,归根结底还是因为原生计算的时候是利用二进制存储结构来完成计算。如果要追求绝对的精确,就应该按照人的思维,十进制从低位到高位依次计算。Decimal就是这么做的,但这也就要求数字不能再以浮点数的类型存储,而应该用字符串来表示

from decimal import Decimal
Decimal('2.2')*3
Out[30]: Decimal('6.6')
print(_)
6.6

注意,Decimal()方法的参数必须得是一个字符串,计算完以后的结果还是一个Decimal对象,直接用float()转为和字符串内容相等的浮点数

float(Decimal('6.6'))
Out[34]: 6.6

可以看到计算不会再有精度问题,但是再次强调这种方式准确性高,但是是以牺牲运算性能为代价的

简单计算就可以利用Decimal()方法来代替原生的浮点数计算来完成,如果是在Django中就可以直接在model中声明一个DecimalField用来存储浮点数,以后计算的时候就不用再次引入Decimal()方法了。