查看原文
其他

decimal 进行无误差浮点数计算

张学人 Stata and Python数据分析 2022-03-15

暑期Stata培训班报名倒计时咯!!!

接力线上的网课培训,我们在今夏又开始新一轮的线下培训啦!8月4日至12日,爬虫俱乐部期待与您的相遇!培训具体内容详见推文《暑期Stata编程技术定制培训班》。

有问题,不要怕!点击推文底部“阅读原文”下载爬虫俱乐部用户问题登记表并按要求填写后发送至邮箱statatraining@163.com,我们会及时为您解答哟~

喜大普奔~爬虫俱乐部的github主站正式上线了!我们的网站地址是:https://stata-club.github.io,粉丝们可以通过该网站访问过去的推文哟~

好消息:爬虫俱乐部隆重推出数据定制及处理业务啦,您有任何网页数据获取及处理方面的难题,请发邮件至我们邮箱statatraining@163.com,届时会有俱乐部资深高级会员为您排忧解难!


我们在使用电脑做小数之间的计算时,偶尔会遇到计算结果的错误,并且错误误差并不大,如正确答案是2,结果却显示2.00000000001,这一问题并不是程序错误造成的,而在于二进制表示浮点数存在天然缺陷。这是什么意思呢?我们在python中有没有方法规避?小编和大家一起来探索这个内容。


错误重现


我们在Python交互环境中输入如下代码:

In [2]: 1.1 * 1.1
Out[2]: 1.2100000000000002

可以看到,显然1.1 * 1.1 的结果是1.21,但是程序给出了错误的结果,这是为什么呢?错误的原因其实不在代码上,而在于非常底层的浮点数二进制表示上。


浮点数的表示方法


什么是浮点数呢?通俗来说就是小数。我们知道,二进制是很方便地表示十进制整数的,如整数25 = 16 + 8 + 1. 因此25的二进制表示就是00011001,只要给的二进制位数足够多,就可以精确地表示任意一个整数。对于一个byte的空间(8个二进制位),可以表示0 - 255的整数(00000000 - 11111111)

但是小数怎么用二进制表示呢?————将小数部分与整数部分分别表示,我们假设用2个byte来储存一个小数7.375,则整数部分表示为00000111 (1+2+4,二进制的三个1分别表示2的二次方、一次方、零次方),小数部分表示为01100000(0.25 + 0.125,二进制的两个1分别表示2的负二次方和负三次方,相加得0.375),因此该数字表示为00000111 01100000.

看起来一切都很完美,但一个坏消息是,这种表示方法,无法精确地表示某些十进制小数,如0.1就无法被二进制精确表示,用来存储小数部分的空间越大,则表示得越精确,但永远无法准确表示。

这样一来似乎问题变得“无解”,我们无法精确计算一个数字的原因竟然是,计算机无法精确表示1.1这样的浮点数,这一问题在计算精度要求比较低时,尚不会有严重问题,但当某些特殊环境下,如计算一个每月持续上浮10%的股票时,这一误差会被迅速放大,给模型带来巨大的困扰。


decimal:十进制数计算模块


令人惊喜的是,Python提供了专门的模块来解决这个问题,即decimal模块,decimal的含义是十进制,顾名思义,decimal模块用于在计算机中存储“真正的十进制数”。decimal模块解决浮点数表达的思路是,抛弃二进制计算方式,直接以十进制的方式直接进行计算,将小数以“字符串”的形式存储起来,当使用该“字符串”进行计算时,得到的依然是“字符串”(也就是Decimal类的实例)。但悲喜总是相伴而生,当我们在电脑上使用十进制方法进行计算时,其速度自然远远不如二进制数的计算速度。但如果遇到对计算精度要求非常高的需求时,必要的效率上的牺牲是非常值得的。


使用范例


1. 生成decimal类型的小数

In [1]: import decimal

In [2]: a = decimal.Decimal('1.1')

In [3]: a * a
Out[3]: Decimal('1.21')

我们使用decimal模块中的类Decimal初始化了1.1这个小数,并生成名为a的Decimal对象,接着输出a * a,返回的依旧是一个Decimal对象,值为1.21

2. 特殊值表示

除了普通的数字值,Decimal 还可以表示很多特殊值,包括正负无穷大值、“不是一个数”(NaN)

In [11]: decimal.Decimal('NaN') ,decimal.Decimal('-NaN')
Out[11]: (Decimal('NaN'), Decimal('-NaN'))

In [12]: decimal.Decimal('Infinity') ,decimal.Decimal('-Infinity')
Out[12]: (Decimal('Infinity'), Decimal('-Infinity'))

In [13]: decimal.Decimal('Infinity') + 1
Out[13]: Decimal('Infinity')

In [14]: decimal.Decimal('-Infinity') + 1
Out[14]: Decimal('-Infinity')

如我们所见,Decimal类可以初始化“非数值”与“无穷”两个实例。

在Decimal('Infinity')加1后,结果仍为Decimal('Infinity'),这从侧面印证了无穷大的性质。

In [15]: decimal.Decimal('Infinity') == decimal.Decimal('NaN')
Out[15]: False

In [16]: decimal.Decimal('0') != decimal.Decimal('NaN')
Out[16]: True

Decimal('NaN')并不表示任意一个数,它表示的是“非数字”这个概念本身,因此令Decimal('NaN')与任意一个Decimal实例比较大小都会引起报错

3. 精度设置

Decimal通过使用上下文方法getcontext()来设置该次程序Decimal实例的精度范围。

In [17]: d = decimal.Decimal('0.123456')

In [18]: decimal.getcontext().prec = 3

In [19]: d
Out[19]: Decimal('0.123456')

In [20]: d * 1
Out[20]: Decimal('0.123')

可以看到,首先设置一个精度达6位的数字d,然后指定今后的Decimal实例精度为3,则d精度不变,而以d进行的任何运算返回的新的实例的精度均为3.

   


注:此推文中的图片及封面(除操作部分的)均来源于网络!如有雷同,纯属巧合!

以上就是今天给大家分享的内容了,说得好就赏个铜板呗!有钱的捧个钱场,有人的捧个人场~。另外,我们开通了苹果手机打赏通道,只要扫描下方的二维码,就可以打赏啦!

应广大粉丝要求,爬虫俱乐部的推文公众号打赏功能可以开发票啦,累计打赏超过1000元我们即可给您开具发票,发票类别为“咨询费”。用心做事,只为做您更贴心的小爬虫。第一批发票已经寄到各位小主的手中,大家快来给小爬虫打赏呀~


                                   




             文字编辑:赵宇亮

  技术总编:刘贝贝

往期推文推荐:

1.爬虫俱乐部新版块--和我们一起学习Python

2.hello,MySQL--Stata连接MySQL数据库

3.hello,MySQL--odbcload读取MySQL数据

4.再爬俱乐部网站,推文目录大放送!

5.用Stata生成二维码—我的心思你来扫

6.Mata中的数据导出至Excel

7.谈谈图形中坐标设置的技巧

8.如何输出某个关键词在字符串中的所有位置?

9.想看什么书?Stata君帮你寻!——爬取中南财大图书馆书目信息

10.爬虫俱乐部隆重推出网上直播课程第一季




关于我们

微信公众号“爬虫俱乐部”分享实用的stata命令,欢迎转载、打赏。爬虫俱乐部是由李春涛教授领导下的研究生及本科生组成的大数据分析和数据挖掘团队。

此外,欢迎大家踊跃投稿,介绍一些关于stata的数据处理和分析技巧。

投稿邮箱:statatraining@163.com

投稿要求:
1)必须原创,禁止抄袭;
2)必须准确,详细,有例子,有截图;
注意事项:
1)所有投稿都会经过本公众号运营团队成员的审核,审核通过才可录用,一经录用,会在该推文里为作者署名,并有赏金分成。
2)邮件请注明投稿,邮件名称为“投稿”+“推文名称”。
3)应广大读者要求,现开通有偿问答服务,如果大家遇到关于stata分析数据的问题,可以在公众号中提出,只需支付少量赏金,我们会在后期的推文里给予解答。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存