# Test-driven Python learning

Bugs are nice because I can learn from them. Recently I found a bug that taught me about floats in Python and how not to use them. I found it thanks to an automated test.

### The test

In a legal contract I had to print a monetary amount in a format similar to

`MXN \$11,600.00 (eleven thousand, six hundred pesos 00/100 cents)`

My tests check the LaTeX source of the resulting PDF, looking for the correctly formatted string `11,600.00`, which represents an amount of `11,000` something plus a VAT of 16%, and also for the string `eleven thousand, six hundred`.

It essentially goes like this:

``````from app.models import LeaseAgreement

def test_contract_states_rent_plus_vat():
"""Test correct statement of rent amount with and without IVA (VAT (USt (MWSt)))."""

contract = LeaseAgreement.build(
rent_amount=Decimal(10000.00)
)

tex = contract.render_tex()

assert '10,000.00' in tex
assert '11,600.00' in tex
assert 'eleven thousand, six hundred' in tex
``````

The first two asserts did not fail, the third one did. My code actually printed

`MXN \$11,600.00 (eleven thousand, five hundred and ninety-nine pesos 00/100 cents)`

### The bug

Turns out I had misused Python's `Decimal` class to calculate the amount plus VAT. Like this:

``````>>> x = Decimal(10000) * Decimal(1.16)
``````

Printing the number in the desired format works fine.

``````>>> print('{:,.2f}'.format(x))
11,600.00
``````

But rounding down for only printing the amount without decimal places does not.

``````>>> num2words(int(x))
'eleven thousand, five hundred and ninety-nine'
``````

That is because floats are not exact.

``````>>> Decimal(1.16)
Decimal('1.1599999999999999200639422269887290894985198974609375')
>>> int(Decimal(1.16)*10000)
11599
``````

### The fix

Working with monetary amounts that round to 2 digits requires care. Thanks to Rami and others, I now know that I should have used e.g.

``````Decimal('1.16')
``````

and

``````Decimal('1.16').quantize(Decimal('.01'), rounding=ROUND_HALF_UP)
``````

when apropriate.