为什么你需要少看垃圾博客以及如何在Python里精确地四舍五入
今天又有一个Python初学者被中文技术博客中的垃圾文章给误导了。
这位初学者的问题是:
在Python中,如何精确地进行浮点数的四舍五入,保留两位小数?
如果你在Google或者百度上搜索,你会发现大量的来自CSDN或者简书上面的文章讲到这一点,但是他们的说法无外乎下面几种:
连例子都不举的垃圾文章
如下图所示,懒得吐槽。
使用round函数
他们举的例子为:
>>> round(1.234, 2)
1.23
这种文章,他只演示了四舍
,但是却没有演示五入
。所以如果你代码稍作修改,就会发现有问题:
>>> round(11.245, 2)
11.24
先放大再缩小
这种文章稍微好一点,知道多举几个例子:
然而这种文章也是漏洞百出,只要你多尝试几个数字就会发现问题,在Python 2和Python 3下面,效果是不一样的。先来看看Python 2下面的运行效果:
在Python 2里面,直接使用round
,1.125
精确到两位小数后为1.13
,而1.115
精确到两位小数后是1.11
。
再来看看Python 3下面的效果:
在Python 3下面,1.125
在精确到两位小数以后是1.12
。
他举的例子,在Python 3中先放大再缩小,也并不总是正确。
装逼货
还有一种装逼货,文章和先放大再缩小差不多,但是他还知道decimal
这个模块。
不过他的使用方法,大家看他吧
具体原因不详
????
不推荐使用这个方法
???
这种人要先装个逼,表示自己知道有这样一个库,但是用起来发现有问题,而且不知道原因,所以不建议大家使用。
decimal是专门为高精度计算用的模块,他竟然说不建议大家使用???
round到底出了什么问题?
骂完了,我们来说说,在Python 3里面,round
这个内置的函数到底有什么问题。
网上有人说,因为在计算机里面,小数是不精确的,例如1.115
在计算机中实际上是1.1149999999999999911182
,所以当你对这个小数精确到小数点后两位的时候,实际上小数点后第三位是4
,所以四舍五入,因此结果为1.11
。
这种说法,对了一半。
因为并不是所有的小数在计算机中都是不精确的。例如0.125
这个小数在计算机中就是精确的,它就是0.125
,没有省略后面的值,没有近似,它确确实实就是0.125
。
但是如果我们在Python中把0.125
精确到小数点后两位,那么它的就会变成0.12
:
>>> round(0.125, 2)
0.12
为什么在这里四舍
了?
还有更奇怪的,另一个在计算机里面能够精确表示的小数0.375
,我们来看看精确到小数点后两位是多少:
>>> round(0.375, 2)
0.38
为什么这里又五入
了?
因为在Python 3里面,round
对小数的精确度采用了四舍六入五成双
的方式。
如果你写过大学物理的实验报告,那么你应该会记得老师讲过,直接使用四舍五入,最后的结果可能会偏高。所以需要使用奇进偶舍
的处理方法。
例如对于一个小数a.bcd
,需要精确到小数点后两位,那么就要看小数点后第三位:
- 如果
d
小于5,直接舍去 - 如果
d
大于5,直接进位 - 如果
d
等于5:d
后面没有数据,且c为偶数
,那么不进位,保留cd
后面没有数据,且c为奇数
,那么进位,c变成(c + 1)- 如果
d
后面还有非0数字,例如实际上小数为a.bcdef
,此时一定要进位,c变成(c + 1)
关于奇进偶舍,有兴趣的同学可以在维基百科搜索这两个词条:数值修约
和奇进偶舍
。
所以,round
给出的结果如果与你设想的不一样,那么你需要考虑两个原因:
- 你的这个小数在计算机中能不能被精确储存?如果不能,那么它可能并没有达到四舍五入的标准,例如
1.115
,它的小数点后第三位实际上是4
,当然会被舍去。 - 如果你的这个小数在计算机中能被精确表示,那么,
round
采用的进位机制是奇进偶舍
,所以这取决于你要保留的那一位,它是奇数还是偶数,以及它的下一位后面还有没有数据。
如何正确进行四舍五入
如果要实现我们数学上的四舍五入,那么就需要使用decimal模块。
如何正确使用decimal模块呢?
看官方文档,不要看中文垃圾博客!!!
看官方文档,不要看中文垃圾博客!!!
看官方文档,不要看中文垃圾博客!!!
不要担心看不懂英文,Python已经推出了官方中文文档(有些函数的使用方法还没有翻译完成)。
我们来看一下:https://docs.python.org/zh-cn/3/library/decimal.html#decimal.Decimal.quantize
官方文档给出了具体的写法:
>>>Decimal('1.41421356').quantize(Decimal('1.000'))
Decimal('1.414')
那么我们来测试一下,0.125
和0.375
分别保留两位小数是多少:
>>> from decimal import Decimal
>>> Decimal('0.125').quantize(Decimal('0.00'))
Decimal('0.12')
>>> Decimal('0.375').quantize(Decimal('0.00'))
Decimal('0.38')
怎么结果和round
一样?我们来看看文档中quantize
的函数原型和文档说明:
这里提到了可以通过指定rounding
参数来确定进位方式。如果没有指定rounding
参数,那么默认使用上下文提供的进位方式。
现在我们来查看一下默认上下文中的进位方式是什么:
>>> from decimal import getcontext
>>> getcontext().rounding
'ROUND_HALF_EVEN'
如下图所示:
ROUND_HALF_EVEN
实际上就是奇进偶舍
!如果要指定真正的四舍五入,那么我们需要在quantize
中指定进位方式为ROUND_HALF_UP
:
>>> from decimal import Decimal, ROUND_HALF_UP
>>> Decimal('0.375').quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('0.38')
>>> Decimal('0.125').quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('0.13')
现在看起来一切都正常了。
那么会不会有人进一步追问一下,如果Decimal接收的参数不是字符串,而是浮点数会怎么样呢?
来实验一下:
>>> Decimal(0.375).quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('0.38')
>>> Decimal(0.125).quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('0.13')
那是不是说明,在Decimal的第一个参数,可以直接传浮点数呢?
我们换一个数来测试一下:
>>> Decimal(11.245).quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('11.24')
>>> Decimal('11.245').quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
Decimal('11.25')
为什么浮点数11.245
和字符串'11.245'
,传进去以后,结果不一样?
我们继续在文档在寻找答案。
官方文档已经很清楚地说明了,如果你传入的参数为浮点数,并且这个浮点值在计算机里面不能被精确存储,那么它会先被转换为一个不精确的二进制值,然后再把这个不精确的二进制值转换为等效的十进制值
。
对于不能精确表示的小数,当你传入的时候,Python在拿到这个数前,这个数就已经被转成了一个不精确的数了。所以你虽然参数传入的是11.245
,但是Python拿到的实际上是11.244999999999...
。
但是如果你传入的是字符串'11.245'
,那么Python拿到它的时候,就能知道这是11.245
,不会提前被转换为一个不精确的值,所以,建议给Decimal
的第一个参数传入字符串型的浮点数,而不是直接写浮点数。
总结,如果想实现精确的四舍五入,代码应该这样写:
from decimal import Decimal, ROUND_HALF_UP
origin_num = Decimal('11.245')
answer_num = origin_num.quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)
print(answer_num)
运行效果如下图所示:
特别注意,一旦要做精确计算,那么就不应该再单独使用浮点数,而是应该总是使用Decimal('浮点数')
。否则,当你赋值的时候,精度已经被丢失了,建议全程使用Decimal举例:
a = Decimal('0.1')
b = Decimal('0.2')
c = a + b
print(c)
最后,如果有同学想知道为什么0.125和0.375能被精确的储存,而1.115、11.245不能被精确储存,请在这篇文章下面留言,如果想知道的同学多,我就写一篇文章来说明。
为什么你需要少看垃圾博客以及如何在Python里精确地四舍五入的更多相关文章
- (转)VS2015基础 指定一个或多个项目执行 - 心少朴的博客
慈心积善融学习,技术愿为有情学.善心速造多好事,前人栽树后乘凉.我今于此写经验,愿见文者得启发. 这个解决方案下,有两个项目, 看到黑体的project了吗?它就是指定执行的项目. 这两 ...
- 博客换了,比较少用这个博客(http://loid.cf)
新博客地址: http://loid.cf/
- iOS开发必看的博客汇总
OneV's Den http://onevcat.com/ 沉船家园 http://beyondvincent.com/ NSHipster http://nshipster.cn/ Limboy ...
- 博客之初体验-----python初了解
---恢复内容开始--- 1.python2.x与python3.x的区别 (1) 2.x的默认编码是ASSIC码,不支持中文 (2) 3.x的默认编码是UNICODE,支持中文 (3) 2.x版本与 ...
- lua:值得看的博客资源 ...
凯奥斯 :https://blog.csdn.net/ecidevilin/article/category/6454847 https://blog.csdn.net/qinyuanpei/arti ...
- [整理]Python程序员面试前需要看的博客(持续整理)
基本素养 如何聪明的提问 面试方法 从面试官角度来告诉大家,哪些人能面试成功 如何在面试中介绍自己的项目经验 计算机系统 [面试] 迄今为止把同步/异步/阻塞/非阻塞/BIO/NIO/AIO讲的这么清 ...
- java入门到秃路线导航,元芳你怎么看?【教学视频+博客+书籍整理】
目录 一.Java基础 二.关于JavaWeb基础 三.关于数据库 四.关于ssm框架 五.关于数据结构与算法 六.关于开发工具idea 七.关于项目管理工具Mawen.Git.SVN.Gradle. ...
- twobin博客样式—“蓝白之风”
自暑假以来,囫囵吞枣一般蒙头栽入前端自学中,且不说是否窥探其道,却不自觉中提高了对网页版面设计的要求,乃至挑剔.一个设计清爽美观的网页能让读者心旷神怡,甚至没有了阅读疲劳:而一个设计粗劣嘈杂的网页实在 ...
- twobin博客样式
twobin博客样式—“蓝白之风” 自暑假以来,囫囵吞枣一般蒙头栽入前端自学中,且不说是否窥探其道,却不自觉中提高了对网页版面设计的要求,乃至挑剔.一个设计清爽美观的网页能让读者心旷神怡,甚至没有 ...
随机推荐
- SDL中 so库的使用
用到的项目:Tocy-Android-SDLv2 JAVA层:只有一个 SDLActivity.java 路径\Android-SDLv2\src\org\libsdl\app 项目简单分析: 默认在 ...
- 对spring框架的理解
spring框架的两大核心理念就是IOC和AOP,在面试的时候经常会被问到你对spring的理解.下面大致的说一下我对spring的理解. 一.IoC 1.1.什么是IoC 众所周知,IoC就是控制反 ...
- OSI七层网络模型浅析
OSI七层网络模型(从下往上): 物理层(Physical):设备之间的数据通信提供传输媒体及互连设备,为数据传输提供可靠的 环境.可以理解为网络传输的物理媒体部分,比如网卡,网线,集线器,中继器,调 ...
- web前端调试的消除缓存对更改页面的影响
平时调试网页的时候经常会短时间多次修改html和css文件,已达到最好的体验效果,但是有时候因为浏览器缓存的原因就导致虽然代码修改了,但是 页面还是没什么变化, 经常以为是自己代码修改的不对, 之后发 ...
- c++——大端序,小端序的排列问题
#include<iostream> using namespace std; union TestModel { int i; char ch; }; int main() { unio ...
- 编写一个简单的基于jmespath 的prometheus exporter
目的很简单,因为系统好多监控指标是通过json 暴露的,并不是标准的prometheus metrics 格式,处理方法 实际上很简单,我们可以基于jsonpath 解析json数据,转换为prome ...
- 【转载】Office软件自定义功能区不完全显示修复方法
转载地址:http://www.doudouxitong.net/guzhang/xitongjiqiao/2015/0603/8822.html 豆豆系统 Office是比较常用的办公软件,我们也会 ...
- mvc项目远程发布到windows server服务器
1.安装IIS的时候需要将这两个选项勾选起来 2.确保 管理服务委派 这个选项存在 3.添加委派规则 4.配置IIS管理用户,后续需要用这个用户进行发布连接 5.配置站点的IIS权限 选择刚才在前面设 ...
- vue实现原理
1.数据监控(data):监听data属性: new Vue之后内部扫描data属性值,用 Object.defineProperty(obj,name,{ set:value=>{ obj[_ ...
- Java五种单例区别
详细请参考如下链接: http://www.voidcn.com/article/p-shzgsluz-bqa.html https://blog.csdn.net/android_freshman/ ...