summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--talersurvey/survey/amount.py131
1 files changed, 131 insertions, 0 deletions
diff --git a/talersurvey/survey/amount.py b/talersurvey/survey/amount.py
new file mode 100644
index 0000000..9a6318a
--- /dev/null
+++ b/talersurvey/survey/amount.py
@@ -0,0 +1,131 @@
+# This file is part of TALER
+# (C) 2017 TALER SYSTEMS
+#
+# 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.1
+# @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 CurrencyMismatch(Exception):
+ pass
+
+class BadFormatAmount(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 BadFormatAmount(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):
+ if a.currency != b.currency:
+ raise CurrencyMismatch()
+ 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
+
+ def set(self, currency, value=0, fraction=0):
+ self.currency = currency
+ self.value = value
+ self.fraction = fraction
+
+ # Add the given amount to this one
+ def add(self, a):
+ if self.currency != a.currency:
+ raise CurrencyMismatch()
+ self.value += a.value
+ self.fraction += a.fraction
+ self.__normalize()
+
+ # Subtract passed amount from this one
+ def subtract(self, a):
+ if self.currency != a.currency:
+ raise CurrencyMismatch()
+ 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)