Compare decimals in python

若如初见. 提交于 2019-12-10 18:08:44

问题


I want to be able to compare Decimals in Python. For the sake of making calculations with money, clever people told me to use Decimals instead of floats, so I did. However, if I want to verify that a calculation produces the expected result, how would I go about it?

>>> a = Decimal(1./3.)
>>> a
Decimal('0.333333333333333314829616256247390992939472198486328125')
>>> b = Decimal(2./3.)
>>> b
Decimal('0.66666666666666662965923251249478198587894439697265625')
>>> a == b
False
>>> a == b - a
False
>>> a == b - Decimal(1./3.)
False

so in this example a = 1/3 and b = 2/3, so obviously b-a = 1/3 = a, however, that cannot be done with Decimals.

I guess a way to do it is to say that I expect the result to be 1/3, and in python i write this as

Decimal(1./3.).quantize(...)

and then I can compare it like this:

(b-a).quantize(...) == Decimal(1./3.).quantize(...)

So, my question is: Is there a cleaner way of doing this? How would you write tests for Decimals?


回答1:


You are not using Decimal the right way.

>>> from decimal import *

>>> Decimal(1./3.)                  # Your code
Decimal('0.333333333333333314829616256247390992939472198486328125')

>>> Decimal("1")/Decimal("3")       # My code
Decimal('0.3333333333333333333333333333')

In "your code", you actually perform "classic" floating point division -- then convert the result to a decimal. The error introduced by floats is propagated to your Decimal.

In "my code", I do the Decimal division. Producing a correct (but truncated) result up to the last digit.


Concerning the rounding. If you work with monetary data, you must know the rules to be used for rounding in your business. If not so, using Decimal will not automagically solve all your problems. Here is an example: $100 to be share between 3 shareholders.

>>> TWOPLACES = Decimal(10) ** -2

>>> dividende = Decimal("100.00")
>>> john = (dividende / Decimal("3")).quantize(TWOPLACES)
>>> john
Decimal('33.33')
>>> paul = (dividende / Decimal("3")).quantize(TWOPLACES)
>>> georges = (dividende / Decimal("3")).quantize(TWOPLACES)
>>> john+paul+georges
Decimal('99.99')

Oups: missing $.01 (free gift for the bank ?)




回答2:


Your verbiage states you want to to monetary calculations, minding your round off error. Decimals are a good choice, as they yield EXACT results under addition, subtraction, and multiplication with other Decimals.

Oddly, your example shows working with the fraction "1/3". I've never deposited exactly "one-third of a dollar" in my bank... it isn't possible, as there is no such monetary unit!

My point is if you are doing any DIVISION, then you need to understand what you are TRYING to do, what the organization's policies are on this sort of thing... in which case it should be possible to implement what you want with Decimal quantizing.

Now -- if you DO really want to do division of Decimals, and you want to carry arbitrary "exactness" around, you really don't want to use the Decimal object... You want to use the Fraction object.

With that, your example would work like this:

>>> from fractions import Fraction
>>> a = Fraction(1,3)
>>> a
Fraction(1, 3)
>>> b = Fraction(2,3)
>>> b
Fraction(2, 3)
>>> a == b
False
>>> a == b - a
True
>>> a + b == Fraction(1, 1)
True
>>> 2 * a == b
True

OK, well, even a caveat there: Fraction objects are the ratio of two integers, so you'd need to multiply by the right power of 10 and carry that around ad-hoc.

Sound like too much work? Yes... it probably is!

So, head back to the Decimal object; implement quantization/rounding upon Decimal division and Decimal multiplication.




回答3:


Floating-point arithmetics is not accurate :

Decimal numbers can be represented exactly. In contrast, numbers like 1.1 and 2.2 do not have exact representations in binary floating point. End users typically would not expect 1.1 + 2.2 to display as 3.3000000000000003 as it does with binary floating point

You have to choose a resolution and truncate everything past it :

>>> from decimal import *
>>> getcontext().prec = 6
>>> Decimal(1) / Decimal(7)
Decimal('0.142857')
>>> getcontext().prec = 28
>>> Decimal(1) / Decimal(7)
Decimal('0.1428571428571428571428571429')

You will obviously get some rounding error which will grow with the number of operations so you have to choose your resolution carefully.




回答4:


There is another approach that may work for you:

  • Continue to do all your calculations in floating point values
  • When you need to compare for equality, use round(val, places)

For example:

>>> a = 1./3
>>> a
0.33333333333333331
>>> b = 2./3
>>> b
0.66666666666666663
>>> b-a
0.33333333333333331
>>> round(a,2) == round(b-a, 2)
True

If you'd like, create a function equals_to_the_cent():

>>> def equals_to_the_cent(a, b):
...   return round(a, 2) == round(b, 2)
...
>>> equals_to_the_cent(a, b)
False
>>> equals_to_the_cent(a, b-a)
True
>>> equals_to_the_cent(1-a, b)
True


来源:https://stackoverflow.com/questions/17314028/compare-decimals-in-python

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!