New Old Money

In my last post I posted my first attempt at writing Python code to do calculations with pre-decimal currency. With a lot of help from Ben Brumfield I’ve rewritten it so that it now does a lot more with less code. The classes and functions have been completely rearranged, everything is easier to read, and there is more scope for dealing with uncertainty. This is yet another example of the benefits of blogging. Without Ben’s input I’d still be using some pretty mediocre code, but by posting my first attempt on the blog and brainstorming with readers I’ve made a vast improvement in only a couple of days. More details below.

First the all-new OldMoney class:

class OldMoney:

    def __init__(self, pence=0, shillings=0, pounds=0):
        self.aspence = pence + (shillings * 12) + (pounds * 240)

    def toLsd(self):
        return Lsd(self.aspence)

    def __add__(self, other):
        return OldMoney(self.aspence + other.aspence)

    def __sub__(self, other):
        return OldMoney(self.aspence - other.aspence)

    def __eq__(self, other):
        return self.aspence == other.aspence

    def __ne__(self, other):
        return self.aspence != other.aspence

    def __lt__(self, other):
        return self.aspence < other.aspence

    def __gt__(self, other):
        return self.aspence > other.aspence

    def __le__(self, other):
        return self.aspence < = other.aspence

    def __ge__(self, other):
        return self.aspence >= other.aspence

Everything here is much simpler than it was before. The amount is now stored as a total of pence rather than being converted at every operation as this saves a lot of function calls. One potential drawback is that an integer will probably run out of storage space at around 8 million pounds, but I’m hoping I won’t reach that limit (the limit would be double if you could make Python integers unsigned but I don’t think you can). I decided to reverse the order of the arguments in the constructor so that it can take a pence total or separate figures for pounds, shillings and pence.

Two instances of OldMoney can be put into a RangeMoney object, which holds a minimum and maximum value where an exact amount is unkown:

class RangeMoney:

    def __init__(self, firstmoney, secondmoney=None):
        self.lowermoney = firstmoney
        if secondmoney:
            self.uppermoney = secondmoney
        else:
            self.uppermoney = firstmoney

    def __add__(self, other):
        return RangeMoney(self.lowermoney + other.lowermoney,
self.uppermoney + other.uppermoney)

The constructor takes one or two OldMoney objects. If you only pass one the same value will be assigned to minimum and maximum. This is useful for adding a range value to an exact value. The addition operator function adds the two parts of two RangeMoney objects and returns a single object.

Next, the MetaOldMoney class. This is the biggest innovation. It can store any one of the following:

  1. An exact value
  2. A range where the absolute minimum and maximum are known
  3. A minimum with no known maximum.
class MetaOldMoney:

    def __init__(self, money=None, moneytype=None):
        self.exactmoney = None
        self.rangemoney = None
        self.minmoney = None
        if moneytype == 'exact':
            self.exactmoney = money
        elif moneytype == 'range':
            self.rangemoney = money
        elif moneytype == 'min':
            self.minmoney = money

    def __add__(self, other):

        if self.exactmoney and other.exactmoney:
            return MetaOldMoney(self.exactmoney +
other.exactmoney, moneytype='exact')
        elif self.rangemoney and other.rangemoney:
            return MetaOldMoney(self.rangemoney +
other.rangemoney, moneytype='range')
        elif self.rangemoney and other.exactmoney:
            return MetaOldMoney(self.rangemoney +
RangeMoney(other.exactmoney), moneytype='range')
        elif self.exactmoney and other.rangemoney:
            return MetaOldMoney(RangeMoney(self.exactmoney) +
other.rangemoney, moneytype='range')
        elif self.minmoney and other.exactmoney:
            return MetaOldMoney(self.minmoney +
other.exactmoney, moneytype='min')
        elif self.minmoney and other.rangemoney:
            return MetaOldMoney(self.minmoney +
other.rangemoney.lowermoney, moneytype='min')
        elif self.exactmoney and other.minmoney:
            return MetaOldMoney(self.exactmoney +
other.minmoney, moneytype='min')
        elif self.rangemoney and other.minmoney:
            return MetaOldMoney(self.rangemoney.lowermoney +
other.minmoney, moneytype='min')
        elif self.minmoney and other.minmoney:
            return MetaOldMoney(self.minmoney +
other.minmoney, moneytype='min')

The constructor can take either an OldMoney object or a RangeMoney object, along with a keyword to indicate which of the three cases applies. Any two MetaOldMoney objects can be added together regardless of what they contain, and a new object will be returned with the correct level of certainty. For exampe:

exact + exact = exact

exact + range = range

range + min = min

exact + min = min

Then a simple little function to get a sum from a sequence of MetaOldMoney objects:

def sumMetaOldMoney(monies):
    total = MetaOldMoney(OldMoney(), moneytype='exact')
    for x in monies:
        total += x
    return total

That’s about it. One gotcha I discovered is that if a Python variable doesn’t exist then you can’t check whether it exists. If x has never been initialized then the condition “if x” will give you an error rather than returning False. If you have conditions which depend on whether variables exist or not you need to initialize the “non-existent” variables with the value None. I seem to remember it wasn’t like this in PHP.

Finally there’s a class called Lsd (don’t laugh, it’s obvious what I meant) which can be used to convert the results of calculations back to pounds/shillings/pence figures:

class Lsd:

    def __init__(self, pence=0, shillings=0, pounds=0):
        self.pounds = pounds
        self.shillings = shillings
        self.pence = pence

        #normalize by converting to pence and back
        temppence = self.toPence()
        self.pounds = int(temppence/240)
        leftforshillings = temppence % 240
        self.shillings = int(leftforshillings/12)
        self.pence = leftforshillings % 12

    def toPence(self):
        return self.pence + (self.shillings * 12) +
(self.pounds * 240)

    def toOldMoney(self):
        return OldMoney(self.toPence())

OldMoney objects have a function which can convert to Lsd objects. I haven’t really used this class for anything yet but I think it should be useful for printing results in human readable from or writing them to XML.

After some testing and ironing out a few errors everything seems to work. The next step is to make some decisions about how to represent uncertainty in the XML, then rewrite the code that imports XML and feeds it into money objects. I never thought I’d be able to love a language that has no curly braces but I actually really like Python now.

Permanent link to this post

Digital History — posted by Gavin Robinson, 5:02 pm, 24 December 2007

No Comments »

RSS feed for comments on this post.

TrackBack URI

Leave a comment

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

If your comment does not appear, it has been held for moderation. Please do not submit it again.