# Adding Decimals to classes with OOP + rounding to significant digits (ATM machine)

I’m learning OOP by trying to build a rudimentary banking ATM machine. It started out an an exercise for Fred Baptiste’s Udemy online courseware but I am now extending and building on top of the feature set just for fun.

Now I am endeavoring to add these features:

• Using Decimals instead of Floats
• “Bankers Rounding” (“half-way” amounts should be rounded up) to 2 significant digits for all transactions

This is what I am trying to accomplish today. This might sound weird but based on my testing, I’m not sure if I have succeeded. That’s why I need your help, Pythonistas!

The tutorial I am working with is titled “Python Decimal”. The tutorial demonstrates how to use decimals and rounding. Here is some sample coding running in my trusty Python REPL:

``````\$ bpython
bpython version 0.22.1 on top of Python 3.10.4 /usr/bin/python
>>> import decimal
>>> from decimal import Decimal
>>> ctx = decimal.getcontext()
>>> ctx.rounding = decimal.ROUND_HALF_UP
>>> x = Decimal('2.3456')
>>> x
Decimal('2.3456')
>>> round(x,2)
Decimal('2.35')
>>>
``````

So that works. Here is my script (reduced test case):

``````import decimal
from decimal import Decimal
from random import randint

class Account:

def __init__(self, first_name, last_name,starting_balance=0.00):
self.first_name = first_name
self.last_name = last_name
self.balance = round(Decimal(starting_balance),2)
self.transaction_id = randint(101,999)

def deposit(self, amount):
self.balance += round(Decimal(amount),2)
self.transaction_id += randint(101,999) - randint(101,999)
return f'D-{self.transaction_id}'

def withdraw(self, amount):
self.balance -= round(Decimal(amount),2)
self.transaction_id += randint(101,999) - randint(101,999)
return f'W-{self.transaction_id}'
``````

Take note that in my script above I do not get a context or set the `rounding` to `ROUND_HALF_UP`. Here is me importing the script in my REPL and instantiating:

``````>>> import script
>>> BA = script.Account('Winston','Smith')
>>> BA.balance
Decimal('0.00')
>>> BA.deposit(0.505)
'D-547'
>>> BA.balance
Decimal('0.51')
>>>
``````

Here my script is rounding half up even without specifying `.getcontext()`. So my script is rounding up when I am expecting it to round down (Python’s default).

Here are my questions for all of you:

First of all, why is my script working when it shouldn’t be?

Secondly, for these two lines:

``````ctx = decimal.getcontext()
ctx.rounding = decimal.ROUND_HALF_UP
``````
• …the `ctx` variable is an instantiation of the `.getcontext()` function or class method located somewhere inside the `decimal` package as described in the official docs. Where in this understanding am I correct or incorrect?
• In the next line, `ctx.rounding` is declared based on the `decimal` package’s setting to `ROUND_HALF_UP` (among a few the other options as covered in the official Python docs). Is this correct? [/list]

My third (and perhaps my most important) question is: How and why does the above `ctx` instantiation have any bearing on the subsequent lines in the REPL (if any)?:

``````>>> x = Decimal('2.3456')
>>> x
Decimal('2.3456')
>>> round(x,2)
Decimal('2.35')
>>>
``````

My final question is, where in my OOP script should I place / invoke the `.getcontext()` method from the `decimal` package? Inside the scope of the `Account` class or outside? Or would it be better suited beneath a dunder main declaration at the bottom of the script?

I guess overall here I am just struggling to grasp how, when, or where `.getcontext()` is used or if it is even necessary at all. Could you Pythonistas kindly clarify?

In most of your tests you hand in a string. But for the .505 one, you hand in a float. That float is not exactly .505 but slightly bigger. So the rounding goes up in all contexts.

The problem with `round` is explained in an official document.
https://docs.python.org/3/library/functions.html?highlight=round#round

As @BowlOfRed pointed out, you should use a string or a tuple to construct a `Decimal`.

``````>>> import decimal
>>> from decimal import Decimal
>>>
>>> ctx=decimal.getcontext()
>>> ctx.rounding
'ROUND_HALF_EVEN'
>>> Decimal(0.505)
Decimal('0.50500000000000000444089209850062616169452667236328125')
>>> round(Decimal(0.505), 2)
Decimal('0.51')
>>> round(Decimal('0.505'), 2)
Decimal('0.50')
``````

If `ndigits` of `round` is greater than 1, it may not function as even rounding. In that case, `numpy` is one of the options that always returns an evenly rounded value.

``````>>> import numpy as np
>>> round(0.505, 2)
0.51
>>> np.round(0.505, 2)
0.5
``````