#!/usr/bin/env python3
+"""An amount of money (USD) represented as an integral count of
+cents."""
+
import re
-from typing import Optional, TypeVar, Tuple
+from typing import Optional, Tuple
import math_utils
-T = TypeVar('T', bound='CentCount')
-
-
class CentCount(object):
"""A class for representing monetary amounts potentially with
- different currencies.
+ different currencies meant to avoid floating point rounding
+ issues by treating amount as a simple integral count of cents.
"""
- def __init__ (
- self,
- centcount,
- currency: str = 'USD',
- *,
- strict_mode = False
- ):
+ def __init__(self, centcount, currency: str = 'USD', *, strict_mode=False):
self.strict_mode = strict_mode
if isinstance(centcount, str):
ret = CentCount._parse(centcount)
if not currency:
self.currency: Optional[str] = None
else:
- self.currency: Optional[str] = currency
+ self.currency = currency
def __repr__(self):
a = float(self.centcount)
a = round(a, 2)
s = f'{a:,.2f}'
if self.currency is not None:
- return '%s %s' % (s, self.currency)
+ return f'{s} {self.currency}'
else:
- return '$%s' % s
+ return f'${s}'
def __pos__(self):
return CentCount(centcount=self.centcount, currency=self.currency)
if isinstance(other, CentCount):
if self.currency == other.currency:
return CentCount(
- centcount = self.centcount + other.centcount,
- currency = self.currency
+ centcount=self.centcount + other.centcount,
+ currency=self.currency,
)
else:
raise TypeError('Incompatible currencies in add expression')
if isinstance(other, CentCount):
if self.currency == other.currency:
return CentCount(
- centcount = self.centcount - other.centcount,
- currency = self.currency
+ centcount=self.centcount - other.centcount,
+ currency=self.currency,
)
else:
raise TypeError('Incompatible currencies in add expression')
raise TypeError('can not multiply monetary quantities')
else:
return CentCount(
- centcount = int(self.centcount * float(other)),
- currency = self.currency
+ centcount=int(self.centcount * float(other)),
+ currency=self.currency,
)
def __truediv__(self, other):
raise TypeError('can not divide monetary quantities')
else:
return CentCount(
- centcount = int(float(self.centcount) / float(other)),
- currency = self.currency
+ centcount=int(float(self.centcount) / float(other)),
+ currency=self.currency,
)
def __int__(self):
if isinstance(other, CentCount):
if self.currency == other.currency:
return CentCount(
- centcount = other.centcount - self.centcount,
- currency = self.currency
+ centcount=other.centcount - self.centcount,
+ currency=self.currency,
)
else:
raise TypeError('Incompatible currencies in sub expression')
raise TypeError('In strict_mode only two moneys can be added')
else:
return CentCount(
- centcount = int(other) - self.centcount,
- currency = self.currency
+ centcount=int(other) - self.centcount,
+ currency=self.currency,
)
__rmul__ = __mul__
if other is None:
return False
if isinstance(other, CentCount):
- return (
- self.centcount == other.centcount and
- self.currency == other.currency
- )
+ return self.centcount == other.centcount and self.currency == other.currency
if self.strict_mode:
raise TypeError("In strict mode only two CentCounts can be compared")
else:
def __ge__(self, other):
return self > other or self == other
- def __hash__(self):
- return self.__repr__
+ def __hash__(self) -> int:
+ return hash(self.__repr__)
- CENTCOUNT_RE = re.compile("^([+|-]?)(\d+)(\.\d+)$")
- CURRENCY_RE = re.compile("^[A-Z][A-Z][A-Z]$")
+ CENTCOUNT_RE = re.compile(r"^([+|-]?)(\d+)(\.\d+)$")
+ CURRENCY_RE = re.compile(r"^[A-Z][A-Z][A-Z]$")
@classmethod
def _parse(cls, s: str) -> Optional[Tuple[int, str]]:
return None
@classmethod
- def parse(cls, s: str) -> T:
+ def parse(cls, s: str) -> 'CentCount':
chunks = CentCount._parse(s)
if chunks is not None:
return CentCount(chunks[0], chunks[1])