精确的小数-decimal进阶

原载于https://mp.weixin.qq.com/s/fQVLYFyk4XyIT9ksiG8kUA



上一篇文章中提到的decimal的一般用法。其实再深入一层思考,事情并没有这么简单。

在小数运算过程当中,需要考虑的问题还有:

  • 用多少位来表示一个小数的有效数字?在超过有效数字位数的时候是直接丢掉最后一位还是四舍五入?比如说3.1415926,是想要用到小数后两位呢还是后三位呢?用后三位的话我用3.141还是3.142呢?

  • 用科学计数法的方式来表示小数的话,指数最大值是多少?超过了能表示的最大数值的话会如何?

  • 在运算的时候可能产生除以零的错误;和一个无效的数值进行计算,比如说有些数值用'-'和'NA'来表示的,跟它们进行计算会产生哪种错误提示?

这篇文章就是来厘清这些问题的。


2.3  decimal的精度和数值范围

在decimal中,有效数字就是精度。例如,一个使用科学计数法表示的小数,1.2345e-3,一共有5个有效数字,所以它的精度是5,而-3就是它的指数。

decimal的精度和指数的范围可以通过Decimal的getcontex()获取。

>>> from decimal import *
>>> getcontext()
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, capitals=1, flags=[], traps=[InvalidOperation, Overflow, DivisionByZero])

上面的代码中,使用getcontext()方法得到一片Context内容。其中prec是精度,是 28位;而Emax是指数最大值,是999999999;Emin是指数最小值,是-999999999。

这里有一点需要注意,当把一个字符串表示的数转换成decimal数的时候,python并不会用这个的精度和数值范围来限制它。该是什么数,内存里面存的还是那个数。如果这个数后续和其它数进行运算,产生的结果就是一个科学计数法表示的数,此时才会受到精度和指数范围的限制。


2.3.1 context的修改

刚才我们得到的context内容,可以直接进行修改的。

>>> c=getcontext()
>>> c.prec=2 
>>> c.Emax=2
>>> c
Context(prec=2, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=2, capitals=1, flags=[], traps=[InvalidOperation, Overflow, DivisionByZero])

上面的代码演示的就是修改精度和指数最大值。

一般情况下,context在设置一次之后,就不需要修改了,整个程序在进行小数运算的时候,都使用同样的精度和指数范围。


2.3.2  context起作用了

刚才说到,当一个字符串表示的小数转化为decimal数的时候,context设置的精度和指数范围对它没有影响。内存里面还是我输入的那个数。在上面的演示当中,context设置了2位数的精度和最大指数是2,此时我构建一个99999的数,也是没有问题的。

>>> Decimal('99999')
Decimal('99999')

context会在运算中产生结果的时候对精度和最大指数进行限制。

>>> Decimal('99999')+1
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/decimal.py", line 1209, in __add__
 ans = ans._fix(context)
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/decimal.py", line 1676, in _fix
 ans = context._raise_error(Overflow, 'above Emax', self._sign)
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/decimal.py", line 3872, in _raise_error
 raise error(explanation)
decimal.Overflow: above Emax

上述的演示对99999这个数进行加1操作,可以看到产生了一个溢出的错误


如果想要在构建Decimal的时候对转换的数就有限制,那么就需要用到context的create_decimal()方法来构建一个Decimal

>>> getcontext().create_decimal('99999')
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/decimal.py", line 3937, in create_decimal
 return d._fix(self)
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/decimal.py", line 1676, in _fix
 ans = context._raise_error(Overflow, 'above Emax', self._sign)
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/decimal.py", line 3872, in _raise_error
 raise error(explanation)
decimal.Overflow: above Emax

上述的演示当中使用getcontext()的create_decimal()来构造一个decimal数,马上就产生了一个溢出的错误。这个方法可以用在对输入数据有严格要求的地方。


2.3.3 超过精度的处理

当运算后产生的数据超过了decimal的精度的时候,decimal会自动进行处理。还是使用刚才设置的context,精度是2。当99和0.1相加的时候,产生了99.1,需要用三位有效数字来表示,比设置的多一位,此时最后一位就需要处理。演示如下:

>>> Decimal('99')+Decimal('0.1')
Decimal('99')

getcontext()的rounding值就是超精度时的处理方法。

>>> getcontext()
Context(prec=2, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=2, capitals=1, flags=[Rounded, Inexact, Overflow], traps=[InvalidOperation, Overflow, DivisionByZero])

可以从上面的演示看到默认的rounding设置是ROUND_HALF_EVEN,在产生超精度的情况时,会取距离最近的数字,如果和两个相邻的数字距离一样,则选择偶数。这是我们平常所理解的,就是当最后一位小于5是取小的,大于5的时候取大的,等于5的时候取偶数。99.4取99,99.6取100,99.5取偶数也是100。

常用的rounding设置还有:

  • ROUND_CEILING

  • ROUND_FLOOR

  • ROUND_HALF_DOWN

  • ROUND_HALF_EVEN

  • ROUND_HALF_UP

  • ROUND_UP

  • ROUND_DOWN

  • ROUND_05UP

其中CEILING和FLOOR是按照数字大小来取,也就是向上取和向下取。而UP和DOWN是按照和0的距离来取,也就是远离0和靠近0来取的,在负数的时候需要注意。

通过getcontext().rounding,可以修改成自己想要的rounding设置。


2.3.4 溢出以及其它异常

在decimal运算过程中,会产生各种异常。最常见的异常可能是溢出、除以零、非法的运算。当碰到这些异常的时候,都需要停止运算并且把错误原因给附上。这些需要捕获的异常,就存在getcontext().traps里。

还是通过查看getcontext()

>>> getcontext()
Context(prec=2, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=2, 
capitals=1, flags=[Rounded, Inexact, Overflow], 
traps=[InvalidOperation, Overflow, DivisionByZero])

从上面的演示中可以看到,默认的traps已经包含了Overflow,DivisionByZero,InvalidOperation。这里同样可以通过getcontext().traps来添加异常。比如说,如果发生了rounding,结果已经不再精确了,可以添加Inexact这个异常。


本文完


猜你喜欢

转载自blog.csdn.net/weixin_42407546/article/details/80912668