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