2023-08-20 12:36:00 +00:00
|
|
|
from enum import Enum
|
|
|
|
from typing import ClassVar, Literal
|
|
|
|
|
|
|
|
|
|
|
|
class NumberingSystem(Enum):
|
|
|
|
SHORT = (
|
|
|
|
(15, "quadrillion"),
|
|
|
|
(12, "trillion"),
|
|
|
|
(9, "billion"),
|
|
|
|
(6, "million"),
|
|
|
|
(3, "thousand"),
|
|
|
|
(2, "hundred"),
|
|
|
|
)
|
|
|
|
|
|
|
|
LONG = (
|
|
|
|
(15, "billiard"),
|
|
|
|
(9, "milliard"),
|
|
|
|
(6, "million"),
|
|
|
|
(3, "thousand"),
|
|
|
|
(2, "hundred"),
|
|
|
|
)
|
|
|
|
|
|
|
|
INDIAN = (
|
|
|
|
(14, "crore crore"),
|
|
|
|
(12, "lakh crore"),
|
|
|
|
(7, "crore"),
|
|
|
|
(5, "lakh"),
|
|
|
|
(3, "thousand"),
|
|
|
|
(2, "hundred"),
|
|
|
|
)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def max_value(cls, system: str) -> int:
|
|
|
|
"""
|
|
|
|
Gets the max value supported by the given number system.
|
|
|
|
|
|
|
|
>>> NumberingSystem.max_value("short") == 10**18 - 1
|
|
|
|
True
|
|
|
|
>>> NumberingSystem.max_value("long") == 10**21 - 1
|
|
|
|
True
|
|
|
|
>>> NumberingSystem.max_value("indian") == 10**19 - 1
|
|
|
|
True
|
|
|
|
"""
|
2024-01-16 08:39:54 +00:00
|
|
|
match system_enum := cls[system.upper()]:
|
2023-08-20 12:36:00 +00:00
|
|
|
case cls.SHORT:
|
|
|
|
max_exp = system_enum.value[0][0] + 3
|
|
|
|
case cls.LONG:
|
|
|
|
max_exp = system_enum.value[0][0] + 6
|
|
|
|
case cls.INDIAN:
|
|
|
|
max_exp = 19
|
|
|
|
case _:
|
|
|
|
raise ValueError("Invalid numbering system")
|
|
|
|
return 10**max_exp - 1
|
|
|
|
|
|
|
|
|
|
|
|
class NumberWords(Enum):
|
2023-09-16 22:12:31 +00:00
|
|
|
ONES: ClassVar[dict[int, str]] = {
|
2023-08-20 12:36:00 +00:00
|
|
|
0: "",
|
|
|
|
1: "one",
|
|
|
|
2: "two",
|
|
|
|
3: "three",
|
|
|
|
4: "four",
|
|
|
|
5: "five",
|
|
|
|
6: "six",
|
|
|
|
7: "seven",
|
|
|
|
8: "eight",
|
|
|
|
9: "nine",
|
|
|
|
}
|
|
|
|
|
2023-09-16 22:12:31 +00:00
|
|
|
TEENS: ClassVar[dict[int, str]] = {
|
2023-08-20 12:36:00 +00:00
|
|
|
0: "ten",
|
|
|
|
1: "eleven",
|
|
|
|
2: "twelve",
|
|
|
|
3: "thirteen",
|
|
|
|
4: "fourteen",
|
|
|
|
5: "fifteen",
|
|
|
|
6: "sixteen",
|
|
|
|
7: "seventeen",
|
|
|
|
8: "eighteen",
|
|
|
|
9: "nineteen",
|
|
|
|
}
|
|
|
|
|
2023-09-16 22:12:31 +00:00
|
|
|
TENS: ClassVar[dict[int, str]] = {
|
2023-08-20 12:36:00 +00:00
|
|
|
2: "twenty",
|
|
|
|
3: "thirty",
|
|
|
|
4: "forty",
|
|
|
|
5: "fifty",
|
|
|
|
6: "sixty",
|
|
|
|
7: "seventy",
|
|
|
|
8: "eighty",
|
|
|
|
9: "ninety",
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def convert_small_number(num: int) -> str:
|
|
|
|
"""
|
|
|
|
Converts small, non-negative integers with irregular constructions in English (i.e.,
|
|
|
|
numbers under 100) into words.
|
|
|
|
|
|
|
|
>>> convert_small_number(0)
|
|
|
|
'zero'
|
|
|
|
>>> convert_small_number(5)
|
|
|
|
'five'
|
|
|
|
>>> convert_small_number(10)
|
|
|
|
'ten'
|
|
|
|
>>> convert_small_number(15)
|
|
|
|
'fifteen'
|
|
|
|
>>> convert_small_number(20)
|
|
|
|
'twenty'
|
|
|
|
>>> convert_small_number(25)
|
|
|
|
'twenty-five'
|
|
|
|
>>> convert_small_number(-1)
|
|
|
|
Traceback (most recent call last):
|
|
|
|
...
|
|
|
|
ValueError: This function only accepts non-negative integers
|
|
|
|
>>> convert_small_number(123)
|
|
|
|
Traceback (most recent call last):
|
|
|
|
...
|
|
|
|
ValueError: This function only converts numbers less than 100
|
|
|
|
"""
|
|
|
|
if num < 0:
|
|
|
|
raise ValueError("This function only accepts non-negative integers")
|
|
|
|
if num >= 100:
|
|
|
|
raise ValueError("This function only converts numbers less than 100")
|
|
|
|
tens, ones = divmod(num, 10)
|
|
|
|
if tens == 0:
|
|
|
|
return NumberWords.ONES.value[ones] or "zero"
|
|
|
|
if tens == 1:
|
|
|
|
return NumberWords.TEENS.value[ones]
|
|
|
|
return (
|
|
|
|
NumberWords.TENS.value[tens]
|
|
|
|
+ ("-" if NumberWords.ONES.value[ones] else "")
|
|
|
|
+ NumberWords.ONES.value[ones]
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def convert_number(
|
|
|
|
num: int, system: Literal["short", "long", "indian"] = "short"
|
|
|
|
) -> str:
|
|
|
|
"""
|
|
|
|
Converts an integer to English words.
|
|
|
|
|
|
|
|
:param num: The integer to be converted
|
|
|
|
:param system: The numbering system (short, long, or Indian)
|
|
|
|
|
|
|
|
>>> convert_number(0)
|
|
|
|
'zero'
|
|
|
|
>>> convert_number(1)
|
|
|
|
'one'
|
|
|
|
>>> convert_number(100)
|
|
|
|
'one hundred'
|
|
|
|
>>> convert_number(-100)
|
|
|
|
'negative one hundred'
|
|
|
|
>>> convert_number(123_456_789_012_345) # doctest: +NORMALIZE_WHITESPACE
|
|
|
|
'one hundred twenty-three trillion four hundred fifty-six billion
|
|
|
|
seven hundred eighty-nine million twelve thousand three hundred forty-five'
|
|
|
|
>>> convert_number(123_456_789_012_345, "long") # doctest: +NORMALIZE_WHITESPACE
|
|
|
|
'one hundred twenty-three thousand four hundred fifty-six milliard
|
|
|
|
seven hundred eighty-nine million twelve thousand three hundred forty-five'
|
|
|
|
>>> convert_number(12_34_56_78_90_12_345, "indian") # doctest: +NORMALIZE_WHITESPACE
|
|
|
|
'one crore crore twenty-three lakh crore
|
|
|
|
forty-five thousand six hundred seventy-eight crore
|
|
|
|
ninety lakh twelve thousand three hundred forty-five'
|
|
|
|
>>> convert_number(10**18)
|
|
|
|
Traceback (most recent call last):
|
|
|
|
...
|
|
|
|
ValueError: Input number is too large
|
|
|
|
>>> convert_number(10**21, "long")
|
|
|
|
Traceback (most recent call last):
|
|
|
|
...
|
|
|
|
ValueError: Input number is too large
|
|
|
|
>>> convert_number(10**19, "indian")
|
|
|
|
Traceback (most recent call last):
|
|
|
|
...
|
|
|
|
ValueError: Input number is too large
|
|
|
|
"""
|
|
|
|
word_groups = []
|
|
|
|
|
|
|
|
if num < 0:
|
|
|
|
word_groups.append("negative")
|
|
|
|
num *= -1
|
|
|
|
|
|
|
|
if num > NumberingSystem.max_value(system):
|
|
|
|
raise ValueError("Input number is too large")
|
|
|
|
|
|
|
|
for power, unit in NumberingSystem[system.upper()].value:
|
|
|
|
digit_group, num = divmod(num, 10**power)
|
|
|
|
if digit_group > 0:
|
|
|
|
word_group = (
|
|
|
|
convert_number(digit_group, system)
|
|
|
|
if digit_group >= 100
|
|
|
|
else convert_small_number(digit_group)
|
|
|
|
)
|
|
|
|
word_groups.append(f"{word_group} {unit}")
|
|
|
|
if num > 0 or not word_groups: # word_groups is only empty if input num was 0
|
|
|
|
word_groups.append(convert_small_number(num))
|
|
|
|
return " ".join(word_groups)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
import doctest
|
|
|
|
|
|
|
|
doctest.testmod()
|
|
|
|
|
|
|
|
print(f"{convert_number(123456789) = }")
|