1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
# This file is part of TALER
# (C) 2017 Taler Systems SA
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# @author Marcello Stanisci
# @version 0.0
# @repository https://git.taler.net/copylib.git/
# This code is "copylib", it is versioned under the Git repository
# mentioned above, and it is meant to be manually copied into any project
# which might need it.
class BadAmount(Exception):
def __init__(self, faulty_str):
self.faulty_str = faulty_str
class Amount:
# How many "fraction" units make one "value" unit of currency
# (Taler requires 10^8). Do not change this 'constant'.
@staticmethod
def FRACTION():
return 10 ** 8
@staticmethod
def MAX_VALUE():
return (2 ** 53) - 1
def __init__(self, currency, value=0, fraction=0):
# type: (str, int, int) -> Amount
assert(value >= 0 and fraction >= 0)
self.value = value
self.fraction = fraction
self.currency = currency
self.__normalize()
assert(self.value <= Amount.MAX_VALUE())
# Normalize amount
def __normalize(self):
if self.fraction >= Amount.FRACTION():
self.value += int(self.fraction / Amount.FRACTION())
self.fraction = self.fraction % Amount.FRACTION()
# Parse a string matching the format "A:B.C"
# instantiating an amount object.
@classmethod
def parse(cls, amount_str):
exp = '^\s*([-_*A-Za-z0-9]+):([0-9]+)\.([0-9]+)\s*$'
import re
parsed = re.search(exp, amount_str)
if not parsed:
raise BadAmount(amount_str)
value = int(parsed.group(2))
fraction = 0
for i, digit in enumerate(parsed.group(3)):
fraction += int(int(digit) * (Amount.FRACTION() / 10 ** (i+1)))
return cls(parsed.group(1), value, fraction)
# Comare two amounts, return:
# -1 if a < b
# 0 if a == b
# 1 if a > b
@staticmethod
def cmp(a, b):
assert a.currency == b.currency
if a.value == b.value:
if a.fraction < b.fraction:
return -1
if a.fraction > b.fraction:
return 1
return 0
if a.value < b.value:
return -1
return 1
# Add the given amount to this one
def add(self, a):
assert self.currency == a.currency
self.value += a.value
self.fraction += a.fraction
self.__normalize()
# Subtract passed amount from this one
def subtract(self, a):
assert self.currency == a.currency
if self.fraction < a.fraction:
self.fraction += Amount.FRACTION()
self.value -= 1
if self.value < a.value:
raise ValueError('self is lesser than amount to be subtracted')
self.value -= a.value
self.fraction -= a.fraction
# Dump string from this amount, will put 'ndigits' numbers
# after the dot.
def stringify(self, ndigits):
assert ndigits > 0
ret = '%s:%s.' % (self.currency, str(self.value))
f = self.fraction
for i in range(0, ndigits):
ret += str(int(f / (Amount.FRACTION() / 10)))
f = (f * 10) % (Amount.FRACTION())
return ret
# Dump the Taler-compliant 'dict' amount
def dump(self):
return dict(value=self.value,
fraction=self.fraction,
currency=self.currency)
|