Возможно, было бы лучше назвать эту статью «Как преобразовать числа в слова», но поскольку я говорю о валюте, я подумал, что использование десятичной дроби было более точным. Во всяком случае, пару лет назад я писал о том, как конвертировать числа в 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 <tebeka@cs.bgu.ac.il>'
__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.