Статьи

Как: преобразовать десятичные числа в слова с помощью Python

Возможно, было бы лучше назвать эту статью «Как преобразовать числа в слова», но поскольку я говорю о валюте, я подумал, что использование десятичной дроби было более точным. Во всяком случае, пару лет назад я писал о том, как конвертировать числа в Python. Основная причина, по которой я возвращаюсь к этой теме, заключается в том, что мне пришлось делать это снова, и я обнаружил, что моего собственного примера довольно не хватает. Он не показывает, как на самом деле использовать его для преобразования чего-то вроде «10,12» в «десять долларов и двенадцать центов». Итак, я собираюсь показать вам, как это сделать в этой статье, а затем мы также рассмотрим некоторые альтернативы, которые мои читатели дали мне.

Вернуться к доске для рисования

 

Для начала мы возьмем оригинальный код и добавим несколько тестов в конце, чтобы убедиться, что он работает так, как мы хотим. Тогда я покажу вам немного другой способ сделать это. Наконец, мы рассмотрим два других проекта, которые также пытаются делать подобные вещи.

'''Convert number to English words
$./num2eng.py 1411893848129211
one quadrillion, four hundred and eleven trillion, eight hundred and ninety
three billion, eight hundred and forty eight million, one hundred and twenty
nine thousand, two hundred and eleven
$
 
Algorithm from http://mini.net/tcl/591
'''
 
# modified to exclude the "and" between hundreds and tens - mld
 
__author__ = 'Miki Tebeka <[email protected]>'
__version__ = '$Revision: 7281 $'
 
# $Source$
 
import math
 
# Tokens from 1000 and up
_PRONOUNCE = [
    'vigintillion',
    'novemdecillion',
    'octodecillion',
    'septendecillion',
    'sexdecillion',
    'quindecillion',
    'quattuordecillion',
    'tredecillion',
    'duodecillion',
    'undecillion',
    'decillion',
    'nonillion',
    'octillion',
    'septillion',
    'sextillion',
    'quintillion',
    'quadrillion',
    'trillion',
    'billion',
    'million',
    'thousand',
    ''
]
 
# Tokens up to 90
_SMALL = {
    '0' : '',
    '1' : 'one',
    '2' : 'two',
    '3' : 'three',
    '4' : 'four',
    '5' : 'five',
    '6' : 'six',
    '7' : 'seven',
    '8' : 'eight',
    '9' : 'nine',
    '10' : 'ten',
    '11' : 'eleven',
    '12' : 'twelve',
    '13' : 'thirteen',
    '14' : 'fourteen',
    '15' : 'fifteen',
    '16' : 'sixteen',
    '17' : 'seventeen',
    '18' : 'eighteen',
    '19' : 'nineteen',
    '20' : 'twenty',
    '30' : 'thirty',
    '40' : 'forty',
    '50' : 'fifty',
    '60' : 'sixty',
    '70' : 'seventy',
    '80' : 'eighty',
    '90' : 'ninety'
}
 
def get_num(num):
    '''Get token <= 90, return '' if not matched'''
    return _SMALL.get(num, '')
 
def triplets(l):
    '''Split list to triplets. Pad last one with '' if needed'''
    res = []
    for i in range(int(math.ceil(len(l) / 3.0))):
        sect = l[i * 3 : (i + 1) * 3]
        if len(sect) < 3: # Pad last section
            sect += [''] * (3 - len(sect))
        res.append(sect)
    return res
 
def norm_num(num):
    """Normelize number (remove 0's prefix). Return number and string"""
    n = int(num)
    return n, str(n)
 
def small2eng(num):
    '''English representation of a number <= 999'''
    n, num = norm_num(num)
    hundred = ''
    ten = ''
    if len(num) == 3: # Got hundreds
        hundred = get_num(num[0]) + ' hundred'
        num = num[1:]
        n, num = norm_num(num)
    if (n > 20) and (n != (n / 10 * 10)): # Got ones
        tens = get_num(num[0] + '0')
        ones = get_num(num[1])
        ten = tens + ' ' + ones
    else:
        ten = get_num(num)
    if hundred and ten:
        return hundred + ' ' + ten
        #return hundred + ' and ' + ten
    else: # One of the below is empty
        return hundred + ten
 
def num2eng(num):
    '''English representation of a number'''
    num = str(long(num)) # Convert to string, throw if bad number
    if (len(num) / 3 >= len(_PRONOUNCE)): # Sanity check
        raise ValueError('Number too big')
 
    if num == '0': # Zero is a special case
        return 'zero '
 
    # Create reversed list
    x = list(num)
    x.reverse()
    pron = [] # Result accumolator
    ct = len(_PRONOUNCE) - 1 # Current index
    for a, b, c in triplets(x): # Work on triplets
        p = small2eng(c + b + a)
        if p:
            pron.append(p + ' ' + _PRONOUNCE[ct])
        ct -= 1
    # Create result
    pron.reverse()
    return ', '.join(pron)
 
if __name__ == '__main__':
 
    numbers = [1.37, 0.07, 123456.00, 987654.33]
    for number in numbers:
        dollars, cents = [int(num) for num in str(number).split(".")]
 
        dollars = num2eng(dollars)
        if dollars.strip() == "one":
            dollars = dollars + "dollar and "
        else:
            dollars = dollars + "dollars and "
 
        cents = num2eng(cents) + "cents"
        print dollars + cents

Мы собираемся сосредоточиться только на последнем разделе, который тестирует программу. Здесь у нас есть список различных значений, которые мы запускаем через программу, и убедитесь, что она выдает то, что мы хотим. Обратите внимание, что у нас есть сумма меньше доллара. Это крайний случай, который я когда-либо использовал, потому что мой работодатель хочет проверить наш код на реальных суммах, но не хочет переводить огромные суммы денег. Вот немного другой способ вывода данных:

temp_amount = 10.34
if '.' in temp_amount:
    amount = temp_amount.split('.')
    dollars = amount[0]
    cents = amount[1]
else:
    dollars = temp_amount
    cents = '00'
 
amt = num2eng.num2eng(dollars)
total = amt + 'and %s/100 Dollars' % cents
print total

В этом случае мы не записываем часть центов в виде слов, а просто ставим число более ста. Да, я знаю, что это тонко, но эта статья для меня тоже мозги, так что в следующий раз, когда я сделаю это, у меня будет вся информация под рукой.

Пробуем PyNum2Word

 

После того, как я опубликовал мою оригинальную статью, кто-то пришел и рассказал мне о проекте PyNum2Word и о том, как я должен был это использовать. Проект PyNum2Word тогда еще не существовал, но я решил попробовать его на этот раз. К сожалению, у этого проекта нет документации, которую я мог бы найти. Даже не файл README! С другой стороны, он утверждает, что может сделать валюту для США, Германии, Великобритании, ЕС и Франции. Я думал, что Германия, Великобритания и Франция были в ЕС, поэтому я не уверен, какой смысл делать франки и тому подобное, когда они все сейчас используют евро.

В любом случае, в нашем случае мы будем использовать следующий файл, num2word_EN.py , из их пакета для наших тестов. На самом деле в нижней части файла есть тест, похожий на тот, который я создал. Я фактически основал свой тест на их. Давайте попробуем отредактировать файл и добавим число меньше единицы во второй список, например, 0,45 , чтобы посмотреть, работает ли это. Вот результат вывода второго списка (для краткости я пропускаю вывод первого списка):

0,45 — ноль целых четыре пять центов
0,45 — ноль целых четыре пять
1 1 — один цент
1 — один
120 — один доллар и двадцать центов
120 — сто двадцать тысяч
1000 — десять долларов
1000 — одна тысяча
1120 — одиннадцать долларов и двадцать центов —
1120 одиннадцатьсот двадцать
1800 — восемнадцать долларов
1800 — восемнадцатьсот
1976 — девятнадцать долларов и семьдесят шесть центов
1976 — девятнадцатьсот семьдесят шесть
2000 — двадцать долларов
2000 — две тысячи
2010 — двадцать долларов и десять центов
2010 — две тысячи десять
2099 — двадцать долларов девяносто девять центов
2099 — две тысячи девяносто девять
2171 — двадцать один доллар и семьдесят один цент.
2171 — двадцать один сто семьдесят один.

Это работает, но не так, как я ожидал. В США, когда мы говорим о валюте, мы бы назвали 0,45 «сорок пять центов», а не «ноль целых четыре пять центов». Когда я исследовал это, я узнал, что люди в некоторых других странах используют последнюю терминологию. Что мне интересно, так это то, что этот модуль берет что-то выше 100 и делит его на доллары и центы. Например, обратите внимание, что 120 переводится в «один доллар двадцать центов» вместо «сто двадцать долларов». Также обратите внимание, что там написано «двадцать центов», а не «указать два ноль центов». Я не знаю, как объяснить это противоречие. Это работает, если вы передадите ему просто целое число, которое меньше 100. Поэтому, если бы вы поместили пользователя в число с плавающей точкой, вы бы хотели разбить его так же, как я делал ранее:

if '.' in temp_amount:
    amount = temp_amount.split('.')
    dollars = amount[0]
    cents = amount[1]
else:
    dollars = temp_amount
    cents = '00'

Затем передайте каждую часть через сценарий, чтобы получить кусочки, а затем сложите их вместе.

Используя numbers.py

 

Другой мой читатель по имени Эрик Уолд связался со мной по поводу своего сценария numbers.py . Посмотрим, как это выдержит!

Посмотрев на код, вы быстро обнаружите, что он не может обрабатывать числа с плавающей точкой, поэтому нам придется разбить наши числа с плавающей запятой и раздать им доллары и центы отдельно. Я пробовал это с несколькими различными числами, и, кажется, преобразовать их правильно. Скрипт даже ставит запятые в тысячах. Это нигде не добавляет слова «и», но мне сейчас все равно.

Вывод

 

Все три из этих методов требуют какой-то оболочки, чтобы добавить к ним слова «доллары» и «центы» (или число / 100) и разбить поплавок на две части. Я думаю, что код Эрика очень прост и лучше всего документирован. Код проекта PyNum2Word также очень лаконичен и работает довольно хорошо, но документации нет. Решение, которое я нашел давным-давно, также работает, но я нахожу этот код очень уродливым и не очень легко читаемым. У меня нет рекомендаций, но я думаю, что Эрик мне нравится больше всего. Если вам нужна гибкость в работе с несколькими валютами, тогда стоит посмотреть проект PyNum2Word.