from typing import Any, List #: Largest integer a JavaScript number holds exactly; beyond it the three #: languages disagree about what number was passed. MAX_MAGNITUDE = 9007199254740991 _ONES = [ "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", ] _TENS = ["", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"] #: Short scale, as used in the UK since 1974: a billion is 10^9. _SCALES = ["", "thousand", "million", "billion", "trillion", "quadrillion"] def _below_hundred(n: int) -> str: if n < 20: return _ONES[n] unit = n % 10 return _TENS[n // 10] if unit == 0 else "%s-%s" % (_TENS[n // 10], _ONES[unit]) def _group(n: int) -> str: """One group of three digits, 1 to 999.""" hundreds, rest = divmod(n, 100) if hundreds == 0: return _below_hundred(rest) head = "%s hundred" % _ONES[hundreds] return head if rest == 0 else "%s and %s" % (head, _below_hundred(rest)) def _whole(value: Any) -> bool: # bool is an int in Python; True is not the number one here. return isinstance(value, int) and not isinstance(value, bool) def integer_to_words(value: int) -> str: """Spell out a whole number in British English words, cheque style: 1234 is "one thousand two hundred and thirty-four". """ if not _whole(value): raise TypeError("integerToWords needs a whole number, received %r" % (value,)) if value > MAX_MAGNITUDE or value < -MAX_MAGNITUDE: raise ValueError( "%d is out of range: integerToWords handles magnitudes up to %d" % (value, MAX_MAGNITUDE) ) if value == 0: return "zero" # Groups of three, least significant first. groups: List[int] = [] remaining = abs(value) while remaining > 0: groups.append(remaining % 1000) remaining //= 1000 words: List[str] = [] for i in range(len(groups) - 1, -1, -1): g = groups[i] if g == 0: continue text = _group(g) if i > 0: text += " " + _SCALES[i] elif len(groups) > 1 and g < 100: # British usage: "one thousand and one", "one million and fifty". # Only the final group gets it, and only without hundreds of its own. text = "and " + text words.append(text) body = " ".join(words) return "minus " + body if value < 0 else body