impl/python/convert_rate.py
1,558 bytes · the Python implementation · view raw
import re
from .math_basis_points_types import RateUnit
# Explicit [0-9]: Python's \d also matches other scripts' digits.
DECIMAL = re.compile(r"^-?[0-9]+(\.[0-9]+)?\Z")
def _exponent(unit: str) -> int:
"""Powers of ten between each unit and basis points."""
if unit == "basis-points":
return 0
if unit == "percent":
return 2
if unit == "ratio":
return 4
raise ValueError('unknown rate unit "%s"' % (unit,))
def convert_rate(value: str, from_unit: RateUnit, to_unit: RateUnit) -> str:
"""Convert a rate between percent, basis points and a ratio.
The units differ by powers of ten, so this moves the decimal point in the
text itself: exact for any input, with no float anywhere.
"""
if not isinstance(value, str) or not DECIMAL.match(value):
raise ValueError('"%s" is not a decimal number' % (value,))
shift = _exponent(from_unit) - _exponent(to_unit)
negative = value.startswith("-")
body = value[1:] if negative else value
point = body.find(".")
digits = body if point < 0 else body[:point] + body[point + 1 :]
position = (len(body) if point < 0 else point) + shift
if position > len(digits):
digits = digits + "0" * (position - len(digits))
if position < 0:
digits = "0" * (-position) + digits
position = 0
whole = digits[:position].lstrip("0") or "0"
fraction = digits[position:].rstrip("0")
text = whole + "." + fraction if fraction else whole
return "-" + text if negative and text != "0" else text