Source code for ctnx.number

# -*- coding: utf-8 -*-

from typing import Union, AnyStr, Generator
from decimal import Decimal, InvalidOperation


[docs]def num_to_words(n: Union[int, float, AnyStr, Decimal]) -> str: """Convert a number to Vietnamese words. Convert a number to its Vietnamese formal spoken form. It supports long numbers (both integers and decimals). Parameters ---------- n : int, float, Decimal or str The number to be converted. If `n` is a str, it will be converted to a Decimal object. Returns ------- str The spoken form of the number Raises ------ TypeError If the input's type is neither int, float, str nor Decimal ValueError If the input string does not represent a valid number """ digits = ('không', 'một', 'hai', 'ba', 'bốn', 'năm', 'sáu', 'bảy', 'tám', 'chín', 'mười') levels = ('đơn vị', 'nghìn', 'triệu') def partition(string: str, n: int) -> Generator: """Split a string from right to left to chunks of size n""" length = len(string) mod = length % n if mod != 0: yield string[0:mod] for i in range(mod, length, n): yield string[i:i+n] def per_digit(n): return [digits[int(s)] for s in str(n)] def per_thousand(n, linh=False): tarr = [] if 100 <= n <= 999: n1, n2 = divmod(n, 100) tarr.append(digits[n1]) tarr.append('trăm') if 1 <= n2 <= 9: tarr.append('linh') n = n2 if 1 <= n <= 9: if linh: tarr.append('linh') tarr.append(digits[n]) elif n <= 99 and n != 0: n1, n2 = divmod(n, 10) ele = digits[n2] if n1 == 1: tarr.append('mười') else: tarr.append(digits[n1]) tarr.append('mươi') if n2 == 1: ele = 'mốt' elif n2 == 4: ele = 'tư' if n2 == 5: ele = 'lăm' if ele != 'không': tarr.append(ele) return tarr def per_billions(part: str): length = len(part) tarr = [] for part in partition(part, 3): pn = int(part) if pn != 0: if part[0] == '0' and 1 <= pn <= 99: tarr.append('không trăm') linh = True else: linh = False tarr.extend(per_thousand(pn, linh)) thous = ((length - 1) // 3) % 3 if thous > 0: tarr.append(levels[thous]) length -= 3 return tarr text_list = [] if isinstance(n, str): try: n = Decimal(n) except InvalidOperation as e: raise ValueError(f"'{n}' is not a valid number.") elif not isinstance(n, (int, float, Decimal)): raise TypeError('The first parameter must be an integer or a float.') if int(n) == 0: text_list.append('không') elif int(n) < 0: text_list.append('âm') n = abs(n) num_str = str(n) decimal_str = '' if '.' in num_str: is_decimal = True int_str, decimal_str = num_str.split('.') num_str = int_str else: is_decimal = False length = len(num_str) for part in partition(num_str, 9): part_num = int(part) if part_num != 0: text_list.extend(per_billions(part)) bilis = (length - 1) // 9 text_list.extend(['tỉ'] * bilis) length -= 9 if is_decimal: text_list.append('phẩy') if decimal_str != '0': decimal_str = decimal_str.rstrip('0') if 2 <= len(decimal_str) <= 3: dec_int = int(decimal_str) if decimal_str[0] == '0' and 1 <= dec_int <= 99: text_list.append('không trăm') text_list.extend(per_thousand(dec_int)) else: text_list.extend(per_digit(int(decimal_str))) text_list = list(filter(None, text_list)) return ' '. join(text_list)