2,399 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")
MAX_SAFE = 9007199254740991def _exponent(unit: str) -> int:
"""Powers of ten between each unit and basis points."""if unit == "basis-points":
return0if unit == "percent":
return2if unit == "ratio":
return4raise 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. """ifnot isinstance(value, str) ornot 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 < 0else body[:point] + body[point + 1 :]
position = (len(body) if point < 0else 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
def to_basis_points(value: str, unit: RateUnit) -> int:
"""An integer number of basis points, refusing a rate that is not whole."""
text = convert_rate(value, unit, "basis-points")
if"."in text:
raise ValueError("%s %s is not a whole number of basis points" % (value, unit))
bp = int(text)
if bp > MAX_SAFE or bp < -MAX_SAFE:
raise ValueError("%s %s is too large for integer basis points" % (value, unit))
return bp
def from_basis_points(basis_points: int, unit: RateUnit) -> str:
"""Render integer basis points in another unit."""if isinstance(basis_points, bool) ornot isinstance(basis_points, int):
raise TypeError("basis_points must be an integer, received %r" % (basis_points,))
return convert_rate(str(basis_points), "basis-points", unit)