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
26 def __init__(self
, value
=None, *, cents
=None):
30 assert isinstance(cents
, int)
31 elif isinstance(value
, Money
):
33 elif isinstance(value
, int):
34 cents
= value
* CENTS_PER_EURO
35 elif isinstance(value
, float):
36 raise TypeError("float is not an appropriate type"
37 " for dealing with money")
39 cents
= self
.from_str(value
).cents
43 def from_str(text
: str):
44 if not isinstance(text
, str):
45 raise TypeError("Can't use Money.from_str to "
46 "convert from non-str value")
47 parts
= text
.strip().split(".", maxsplit
=2)
49 negative
= first_part
.startswith("-")
50 if first_part
.startswith(("-", "+")):
51 first_part
= first_part
[1:]
54 elif _is_ascii_digits(first_part
):
55 euros
= int(first_part
)
57 raise ValueError("invalid Money string: characters after sign and"
58 " before first `.` must be ascii digits")
60 raise ValueError("invalid Money string: too many `.` characters")
64 raise ValueError("invalid Money string: missing digits")
66 elif _is_ascii_digits(parts
[1]):
67 shift_amount
= LOG10_CENTS_PER_EURO
- len(parts
[1])
69 raise ValueError("invalid Money string: too many digits"
71 cents
= int(parts
[1]) * (10 ** shift_amount
)
73 raise ValueError("invalid Money string: characters"
74 " after `.` must be ascii digits")
75 elif first_part
== "":
76 raise ValueError("invalid Money string: missing digits")
79 cents
+= CENTS_PER_EURO
* euros
82 return Money(cents
=cents
)
85 retval
= "-" if self
.cents
< 0 else ""
86 retval
+= str(abs(self
.cents
) // CENTS_PER_EURO
)
87 cents
= abs(self
.cents
) % CENTS_PER_EURO
90 retval
+= str(cents
).zfill(LOG10_CENTS_PER_EURO
)
93 def __lt__(self
, other
):
94 return self
.cents
< Money(other
).cents
96 def __le__(self
, other
):
97 return self
.cents
<= Money(other
).cents
99 def __eq__(self
, other
):
100 return self
.cents
== Money(other
).cents
102 def __ne__(self
, other
):
103 return self
.cents
!= Money(other
).cents
105 def __gt__(self
, other
):
106 return self
.cents
> Money(other
).cents
108 def __ge__(self
, other
):
109 return self
.cents
>= Money(other
).cents
112 return f
"Money({repr(str(self))})"
115 return bool(self
.cents
)
117 def __add__(self
, other
):
118 cents
= self
.cents
+ Money(other
).cents
119 return Money(cents
=cents
)
121 def __radd__(self
, other
):
122 cents
= Money(other
).cents
+ self
.cents
123 return Money(cents
=cents
)
125 def __sub__(self
, other
):
126 cents
= self
.cents
- Money(other
).cents
127 return Money(cents
=cents
)
129 def __rsub__(self
, other
):
130 cents
= Money(other
).cents
- self
.cents
131 return Money(cents
=cents
)
133 def __mul__(self
, other
):
134 if not isinstance(other
, int):
135 raise TypeError("can't multiply by non-int")
136 cents
= self
.cents
* other
137 return Money(cents
=cents
)
139 def __rmul__(self
, other
):
140 if not isinstance(other
, int):
141 raise TypeError("can't multiply by non-int")
142 cents
= other
* self
.cents
143 return Money(cents
=cents
)