from .math_rational import multiply_rational, rational, rational_to_integer from .math_round_div import RoundingMode from .money_amount import Money, money from .money_convert_types import ExchangeRate from .money_currency_digits import currency_digits def convert_money(amount: Money, rate: ExchangeRate, mode: RoundingMode) -> Money: """Convert ``amount`` into the rate's quote currency. Exact up to one final rounding into the quote currency's minor unit; the rate is a fraction in major units and each currency's decimal places are looked up rather than assumed to be two. """ if amount.currency != rate.base: raise ValueError("exchange rate converts %s to %s, not %s" % (rate.base, rate.quote, amount.currency)) r = rational(rate.rate.numerator, rate.rate.denominator) if r.numerator <= 0: raise ValueError("exchange rate must be positive") from_digits = currency_digits(rate.base) to_digits = currency_digits(rate.quote) # minor units of quote per minor unit of base: rate x 10^to_digits / 10^from_digits scale = rational(10**to_digits, 10**from_digits) per_minor = multiply_rational(r, scale) exact = multiply_rational(rational(amount.minor, 1), per_minor) return money(rational_to_integer(exact, mode), rate.quote)