为什么你需要少看垃圾博客以及如何在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里面,直接使用round1.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,需要精确到小数点后两位,那么就要看小数点后第三位:

  1. 如果d小于5,直接舍去
  2. 如果d大于5,直接进位
  3. 如果d等于5:
    1. d后面没有数据,且c为偶数,那么不进位,保留c
    2. d后面没有数据,且c为奇数,那么进位,c变成(c + 1)
    3. 如果d后面还有非0数字,例如实际上小数为a.bcdef,此时一定要进位,c变成(c + 1)

关于奇进偶舍,有兴趣的同学可以在维基百科搜索这两个词条:数值修约奇进偶舍

所以,round给出的结果如果与你设想的不一样,那么你需要考虑两个原因:

  1. 你的这个小数在计算机中能不能被精确储存?如果不能,那么它可能并没有达到四舍五入的标准,例如1.115,它的小数点后第三位实际上是4,当然会被舍去。
  2. 如果你的这个小数在计算机中能被精确表示,那么,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.1250.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不能被精确储存,请在这篇文章下面留言,如果想知道的同学多,我就写一篇文章来说明。

原文地址:https://www.cnblogs.com/xieqiankun/p/the_truth_of_round.html

时间: 2024-08-01 18:02:20

为什么你需要少看垃圾博客以及如何在Python里精确地四舍五入的相关文章

常看的博客

常看的博客: http://blog.isming.me/ http://stormzhang.com/ Android开源项目汇总 http://www.wandoujia.com/apps/com.cjg.android

(转)VS2015基础 指定一个或多个项目执行 - 心少朴的博客

慈心积善融学习,技术愿为有情学.善心速造多好事,前人栽树后乘凉.我今于此写经验,愿见文者得启发. 这个解决方案下,有两个项目, 看到黑体的project了吗?它就是指定执行的项目. 这两个项目的代码分别是,空间的名字就是项目的名字. 但是,建立之后 改变了空间的名字不会改变项目的名字,改了项目的名字不会改变空间的名字. using System; using System.Collections.Generic; using System.Linq; using System.Text; usi

cnblogs博客下载-cnblogs博客导出-cnblogs博客备份工具-基于python

http://blog.csdn.net/infoworld/article/details/19547723 以下代码是基于infoworld的csdn备份python代码修改的cnblogs博客备份,但是和infoworld的界面不匹配,只能够用在python里面.python确实有意思,开发很快,怪不得这么流行. #! encoding=utf-8 #cnblogs博客备份,使用方法:修改最下面的url和output,然后执行就可以了. import urllib2 import re i

iOS开发必看的博客汇总

OneV's Den http://onevcat.com/ 沉船家园 http://beyondvincent.com/ NSHipster http://nshipster.cn/ Limboy 没有网络不剩 http://limboy.me 唐巧的技术博客 http://blog.devtang.com/ Lex iOS notes http://ios.lextang.com/ 念茜的博客 http://nianxi.net/ Xcode Dev http://blog.xcodev.c

最近需要看的博客

JS相关 jQuery中事情的动态绑定 http://www.cnblogs.com/zengda/p/4542229.html?utm_source=tuicool http://www.cnblogs.com/renshengrucha/p/4542857.html?utm_source=tuicool JS性能方面--内存管理及ECMAScript5 Object的新属性方法 http://www.cnblogs.com/liyunhua/p/4534445.html?utm_source

51CTO博客专栏汇总贴,听说看了博客专栏的人工资都涨了~~

新上专栏 专栏名称:负载均衡高手炼成记简介:老板要省钱,要求用负载均衡部署linux集群网站?负载均衡是衡量初中级以上运维技术水平的重要标尺?负载均衡是普通运维人员很难有机会接触和系统学习的知识?本专栏依托作者十余年IT运维经验,从入门到实操,手把手教你构建和运行不同场景下负载均衡以及日常维护.专栏地址:http://blog.51cto.com/cloumn/detail/6 热门专栏 专栏名称:老司机网络运维干货集锦(含路由交换安全Qos优化)简介:此专栏通过"网络路由篇",&qu

博客第一弹—聊聊HTML里的head部分

HTML(HyperText Markup Language),即超文本标记语言.它的结构包括head部分和body部分,其中head部分用于描述网页的一些关键信息,这些信息本身不作为内容来显示,但对网页的解析和显示至关重要,比如网页的标题.链接.元数据等. 1:<title></title>网页的标题,显示在浏览器选项卡上面的文字. 实例:<title>这是我的第一个网页</title> 2:<link> 标签定义文档与外部资源的关系,常见的用

随笔不是博客

博客的一个目的是给别人看的,而随笔只有一个记录的目的,所以随笔当然不算博客. 为什么至今未写一篇博客,因为自知水平很差,写不了可供大家学习的博客,既然写不了,那为什么要强行写,首先博客是要费心思和时间的,假如你对一个知识点的理解从本质上就是错误的,那么你再认真输出一遍岂不错上加错,而且人都有一种护短思维,从心理就会觉得自己的是对的,这太可怕了,更可怕的是你可能因此误导很多朋友,所以,这种损人不利己的行为为什么要支持? 希望大环境能少一点垃圾博客,实在不明白安装个软件配置个环境为什么也能算博客.

前端值得看的7个博客

1. 汤姆大叔 “汤姆大叔”的博客在cnblogs上长期位列第二,可见他的受欢迎程度.他功底深厚.思想很有见地,对问题的本质看得很透彻.除了纯前端的技术究外,还有很多对软件架构.设计模式等方面的文章.特别推荐两大系列: <深入理解Javascript系列>http://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html <MVC之前的那点事儿系列>http://www.cnblogs.com/TomXu/p/3756794.h