From b270db02e8d7b6a25b661c1b144d6541935794ed 2017-06-29 20:54:16 From: Brett Smith Date: 2017-06-29 20:54:16 Subject: [PATCH] historical: Ledger conversions show enough rate precision to stay balanced. --- diff --git a/oxrlib/commands/historical.py b/oxrlib/commands/historical.py index 47c141a8f8509b3a1e8a5e3bc15b376bb1f23750..bf04e7bc03c735d412efb2208e98ca17917da2ab 100644 --- a/oxrlib/commands/historical.py +++ b/oxrlib/commands/historical.py @@ -28,6 +28,10 @@ class Formatter: fmt = fmt + ' ¤¤' return babel.numbers.format_currency(amount, code, fmt, currency_digits=currency_digits) + def currency_decimal(self, amount, currency): + amt_s = babel.numbers.format_currency(amount, currency, '###0.###') + return decimal.Decimal(amt_s) + def format_rate(self, rate): return "{:g}".format(rate) @@ -61,10 +65,12 @@ class LedgerFormatter(Formatter): self.rate_prec = rate_precision self.denomination = denomination - def normalize_rate(self, rate): + def normalize_rate(self, rate, prec=None): + if prec is None: + prec = self.rate_prec _, digits, exponent = rate.normalize().as_tuple() - # Return ``self.rate_prec`` nonzero digits of precision, if available. - prec = self.rate_prec - min(0, exponent + len(digits)) + # Return ``prec`` nonzero digits of precision, if available. + prec -= min(0, exponent + len(digits)) quant_to = '1.{}'.format('0' * prec) try: qrate = rate.quantize(decimal.Decimal(quant_to)) @@ -76,11 +82,13 @@ class LedgerFormatter(Formatter): def format_rate(self, rate): return str(self.normalize_rate(rate)) - def format_ledger_rate(self, rate, curr): - nrate = self.normalize_rate(rate) - rate_s = self.format_currency(nrate, curr, currency_digits=False) + def format_ledger_rate_raw(self, rate, curr): + rate_s = self.format_currency(rate, curr, currency_digits=False) return "{{={0}}} @ {0}".format(rate_s) + def format_ledger_rate(self, rate, curr): + return self.format_ledger_rate_raw(self.normalize_rate(rate), curr) + def format_rate_pair(self, from_curr, to_curr): from_amt = 1 to_amt = self.rate.convert(from_amt, from_curr, to_curr) @@ -100,9 +108,14 @@ class LedgerFormatter(Formatter): amt_s = self.format_currency(amount, currency) if denomination is None: return amt_s - else: - rate = self.rate.convert(1, currency, denomination) - return "{} {}".format(amt_s, self.format_ledger_rate(rate, denomination)) + full_rate = self.rate.convert(1, currency, denomination) + to_amt = self.currency_decimal(amount * full_rate, denomination) + for prec in itertools.count(self.rate_prec): + rate = self.normalize_rate(full_rate, prec) + got_amt = self.currency_decimal(amount * rate, denomination) + if (got_amt == to_amt) or (rate == full_rate): + break + return "{} {}".format(amt_s, self.format_ledger_rate_raw(rate, denomination)) def format_conversion(self, from_amt, from_curr, to_curr): to_amt = self.rate.convert(from_amt, from_curr, to_curr) diff --git a/tests/historical1.json b/tests/historical1.json index 6431839d34c1689f1db7e4be56cee0d0245cb077..a7fc7fa7cb315dc476ebd21ee8023d0111aa7404 100644 --- a/tests/historical1.json +++ b/tests/historical1.json @@ -7,6 +7,7 @@ "AED": 3.67246, "ALL": 144.529793, "ANG": 1.79, + "RUB": 57.0763, "USD": 1 } } diff --git a/tests/test_historical.py b/tests/test_historical.py index d68b803ac228c6e80929be2e22316942558bbf8a..fbf79ac97cba54470fa7ed0a3c5ae76309da743d 100644 --- a/tests/test_historical.py +++ b/tests/test_historical.py @@ -146,3 +146,16 @@ def test_from_denomination(historical1_responder, output): assert next(lines) == '$10.00\n' assert next(lines) == '1,445 ALL {=$0.006919} @ $0.006919\n' assert next(lines, None) is None + +def test_rate_precision_added_as_needed(historical1_responder, output): + config = build_config(historical1_responder, from_currency='RUB', + to_currency='USD', amount=63805, + ledger=True, denomination='USD') + lines = lines_from_run(config, output) + # 63,805 / 57.0763 (the RUB rate) == $1,117.89 + # But using the truncated rate: 63,805 * .01752 == $1,117.86 + # Make sure the rate is specified with enough precision to get the + # correct conversion amount. + assert next(lines) == '63,805.00 RUB {=$0.0175204} @ $0.0175204\n' + assert next(lines) == '$1,117.89\n' + assert next(lines, None) is None