1 LOG10_CENTS_PER_EURO
= 2
2 CENTS_PER_EURO
= 10 ** LOG10_CENTS_PER_EURO
4 __all__
= ["Money", "LOG10_CENTS_PER_EURO", "CENTS_PER_EURO"]
7 def _is_ascii_digits(s
: str):
10 except UnicodeEncodeError:
16 """class for handling money, stored as an integer number of cents.
18 Note: float is not appropriate for dealing with monetary values due to
19 loss of precision and round-off error. Decimal has similar issues, but to
24 def __init__(self
, value
=None, *, cents
=None):
28 assert isinstance(cents
, int)
29 elif isinstance(value
, Money
):
31 elif isinstance(value
, int):
32 cents
= value
* CENTS_PER_EURO
33 elif isinstance(value
, float):
34 raise TypeError("float is not an appropriate type"
35 " for dealing with money")
37 cents
= self
.from_str(value
).cents
41 def from_str(text
: str):
42 if not isinstance(text
, str):
43 raise TypeError("Can't use Money.from_str to "
44 "convert from non-str value")
45 parts
= text
.strip().split(".", maxsplit
=2)
47 negative
= first_part
.startswith("-")
48 if first_part
.startswith(("-", "+")):
49 first_part
= first_part
[1:]
52 elif _is_ascii_digits(first_part
):
53 euros
= int(first_part
)
55 raise ValueError("invalid Money string: characters after sign and"
56 " before first `.` must be ascii digits")
58 raise ValueError("invalid Money string: too many `.` characters")
62 raise ValueError("invalid Money string: missing digits")
64 elif _is_ascii_digits(parts
[1]):
65 shift_amount
= LOG10_CENTS_PER_EURO
- len(parts
[1])
67 raise ValueError("invalid Money string: too many digits"
69 cents
= int(parts
[1]) * (10 ** shift_amount
)
71 raise ValueError("invalid Money string: characters"
72 " after `.` must be ascii digits")
73 elif first_part
== "":
74 raise ValueError("invalid Money string: missing digits")
77 cents
+= CENTS_PER_EURO
* euros
80 return Money(cents
=cents
)
83 retval
= "-" if self
.cents
< 0 else ""
84 retval
+= str(abs(self
.cents
) // CENTS_PER_EURO
)
85 cents
= abs(self
.cents
) % CENTS_PER_EURO
88 retval
+= str(cents
).zfill(LOG10_CENTS_PER_EURO
)
91 def __lt__(self
, other
):
92 return self
.cents
< Money(other
).cents
94 def __le__(self
, other
):
95 return self
.cents
<= Money(other
).cents
97 def __eq__(self
, other
):
98 return self
.cents
== Money(other
).cents
100 def __ne__(self
, other
):
101 return self
.cents
!= Money(other
).cents
103 def __gt__(self
, other
):
104 return self
.cents
> Money(other
).cents
106 def __ge__(self
, other
):
107 return self
.cents
>= Money(other
).cents
110 return f
"Money({repr(str(self))})"
113 return bool(self
.cents
)
115 def __add__(self
, other
):
116 cents
= self
.cents
+ Money(other
).cents
117 return Money(cents
=cents
)
119 def __radd__(self
, other
):
120 cents
= Money(other
).cents
+ self
.cents
121 return Money(cents
=cents
)
123 def __sub__(self
, other
):
124 cents
= self
.cents
- Money(other
).cents
125 return Money(cents
=cents
)
127 def __rsub__(self
, other
):
128 cents
= Money(other
).cents
- self
.cents
129 return Money(cents
=cents
)
131 def __mul__(self
, other
):
132 if not isinstance(other
, int):
133 raise TypeError("can't multiply by non-int")
134 cents
= self
.cents
* other
135 return Money(cents
=cents
)
137 def __rmul__(self
, other
):
138 if not isinstance(other
, int):
139 raise TypeError("can't multiply by non-int")
140 cents
= other
* self
.cents
141 return Money(cents
=cents
)