45

What class should I use for representation of money to avoid most rounding errors?

Should I use Decimal, or a simple built-in number?

Is there any existing Money class with support for currency conversion that I could use?

Any pitfalls that I should avoid?

GameFreak
  • 2,881
  • 7
  • 34
  • 38
Esteban Küber
  • 36,388
  • 15
  • 79
  • 97
  • I always thought that currency conversion is just multiplication. – SilentGhost Sep 10 '09 at 17:55
  • 3
    @SilentGhost: Yes and no. You have to keep in mind *how* are you going to use the values that you have. How do you do when you payed U$S2000 + AR$6300 + €1500 last year and this year you payed U$S4000 + AR$1200 + €500? There are many things that you have to take into account, so a `Money` object would need to save the historical value *and* current value. – Esteban Küber Sep 10 '09 at 18:13
  • 9
    Pitfall to avoid: using floating point numbers. See Office Space. –  Dec 31 '11 at 12:12

6 Answers6

39

Never use a floating point number to represent money. Floating numbers do not represent numbers in decimal notation accurately. You would end with a nightmare of compound rounding errors, and unable to reliably convert between currencies. See Martin Fowler's short essay on the subject.

If you decide to write your own class, I recommend basing it on the decimal data type.

I don't think python-money is a good option, because it wasn't maintained for quite some time and its source code has some strange and useless code, and exchanging currencies is simply broken.

Try py-moneyed. It's an improvement over python-money.

Thiago Chaves
  • 9,218
  • 5
  • 28
  • 25
  • 3
    decimal is based on a floating point, I'm curious how it fixes the problem – xenoterracide Jan 24 '20 at 21:32
  • 2
    How it fixes or works around floating point problems is described in the first section of the Python docs for decimal: https://docs.python.org/3/library/decimal.html Hope that helps. – NeverCast Dec 09 '20 at 01:26
17

Just use decimal.

S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • 9
    decimal isn’t suitable because of the way it handles precision. Suppose you’re handling amounts less than $10 bn with a precision of $0.01, so you tell the decimal library your numbers will be no more than 12 digits long. Now you have two problems: your code can’t handle hyper-inflation; and $0.01 will be displayed as '0.010000000000'. – Pitarou Jun 04 '17 at 10:30
  • @Pitarou Forgive my ignorance, but why would you ever reduce the precision, especially in an app that might encounter hyper-inflation? And how do you get that output for `$0.01`? I tried `decimal.getcontext().prec = 12; print(decimal.Decimal('0.01'))` but it prints `0.01` with no trailing zeros. Even if you did get that output, why wouldn't you round it before display? – wjandrea Dec 14 '21 at 23:31
10

http://code.google.com/p/python-money/ "Primitives for working with money and currencies in Python" - the title is self explanatory :)

wjandrea
  • 28,235
  • 9
  • 60
  • 81
juckobee
  • 1,239
  • 1
  • 12
  • 20
  • 5
    Looking at http://code.google.com/p/python-money/source/browse/trunk/money/Money.py I can see that they do use `Decimal` for internal representation :) – Esteban Küber Sep 10 '09 at 18:22
5

You might be interested in QuantLib for working with finance.

It has built in classes for handling currency types and claims 4 years of active development.

Jon W
  • 15,480
  • 6
  • 37
  • 47
5

You could have a look at this library: python-money. Since I've no experience with it I cannot comment on its usefullness.

A 'trick' you could employ to handle currency as integers:

  • Multiply by 100 / Divide by 100 (e.g. $100,25 -> 10025) to have a representation in 'cents'
ChristopheD
  • 112,638
  • 29
  • 165
  • 179
5

Simple, light-weight, yet extensible idea:

class Money():

    def __init__(self, value):
        # internally use Decimal or cents as long
        self._cents = long(0)
        # Now parse 'value' as needed e.g. locale-specific user-entered string, cents, Money, etc.
        # Decimal helps in conversion

    def as_my_app_specific_protocol(self):
        # some application-specific representation

    def __str__(self):
        # user-friendly form, locale specific if needed

    # rich comparison and basic arithmetics
    def __lt__(self, other):
        return self._cents < Money(other)._cents
    def __add__(self, other):
        return Money(self._cents + Money(other)._cents)

You can:

  • Implement only what you need in your application.
  • Extend it as you grow.
  • Change internal representation and implementation as needed.
andruso
  • 1,955
  • 1
  • 18
  • 26