views:

496

answers:

3

python decimal comparison

>>> from decimal import Decimal
>>> Decimal('1.0') > 2.0
True

I was expecting it to convert 2.0 correctly, but after reading thru PEP 327 I understand there were some reason for not implictly converting float to Decimal, but shouldn't in that case it should raise TypeError as it does in this case

>>> Decimal('1.0') + 2.0
Traceback (most recent call last):
  File "<string>", line 1, in <string>
TypeError: unsupported operand type(s) for +: 'Decimal' and 'float'

so does all other operator / - % // etc

so my questions are

  1. is this right behavior? (not to raise exception in cmp)
  2. What if I derive my own class and right a float converter basically Decimal(repr(float_value)), are there any caveats? my use case involves only comparison of prices

System details: Python 2.5.2 on Ubuntu 8.04.1

+1  A: 

If it's "right" is a matter of opinion, but the rationale of why there is no automatic conversion exists in the PEP, and that was the decision taken. The caveat basically is that you can't always exactly convert between float and decimal. Therefore the conversion should not be implicit. If you in your application know that you never have enough significant numbers for this to affect you, making classes that allow this implicit behaviour shouldn't be a problem.

Also, one main argument is that real world use cases doesn't exist. It's likely to be simpler if you just use Decimal everywhere.

Lennart Regebro
I edited first question a bit, by right I mean not raising exception if it can't convert in comparison
Anurag Uniyal
Aha, I see. Yes, that has a different rationale, and I agree that raising an exception seems more explicit. But again it's a matter of taste.
Lennart Regebro
+2  A: 

The greater-than comparison works because, by default, it works for all objects.

>>> 'abc' > 123
True

Decimal is right merely because it correctly follows the spec. Whether the spec was the correct approach is a separate question. :)

Only the normal caveats when dealing with floats, which briefly summarized are: beware of edge cases such as negative zero, +/-infinity, and NaN, don't test for equality (related to the next point), and count on math being slightly inaccurate.

>>> print (1.1 + 2.2 == 3.3)
False
Roger Pate
'abc' > 123 raises a TypeError exception in Python 3. Which is the Right Thing to do, IMHO.
Tim Pietzcker
+7  A: 

Re 1, it's indeed the behavior we designed -- right or wrong as it may be (sorry if that trips your use case up, but we were trying to be general!).

Specifically, it's long been the case that every Python object could be subject to inequality comparison with every other -- objects of types that aren't really comparable get arbitrarily compared (consistently in a given run, not necessarily across runs); main use case was sorting a heterogeneous list to group elements in it by type.

An exception was introduced for complex numbers only, making them non-comparable to anything -- but that was still many years ago, when we were occasionally cavalier about breaking perfectly good user code. Nowadays we're much stricter about backwards compatibility within a major release (e.g. along the 2.* line, and separately along the 3.* one, though incompatibilities are allowed between 2 and 3 -- indeed that's the whole point of having a 3.* series, letting us fix past design decisions even in incompatible ways).

The arbitrary comparisons turned out to be more trouble than they're worth, causing user confusion; and the grouping by type can now be obtained easily e.g. with a key=lambda x: str(type(x)) argument to sort; so in Python 3 comparisons between objects of different types, unless the objects themselves specifically allow it in the comparison methods, does raise an exception:

>>> decimal.Decimal('2.0') > 1.2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: Decimal() > float()

In other words, in Python 3 this behaves exactly as you think it should; but in Python 2 it doesn't (and never will in any Python 2.*).

Re 2, you'll be fine -- though, look to gmpy for what I hope is an interesting way to convert doubles to infinite-precision fractions through Farley trees. If the prices you're dealing with are precise to no more than cents, use '%.2f' % x rather than repr(x)!-)

Rather than a subclass of Decimal, I'd use a factory function such as

def to_decimal(float_price):
    return decimal.Decimal('%.2f' % float_price)

since, once produced, the resulting Decimal is a perfectly ordinary one.

Alex Martelli
+1 for 2, indeed we are limited to cents, i have edited Q1. , IMO cmp operators should also raise exception
Anurag Uniyal
@Anurag, let me edit the answer to expand on this.
Alex Martelli