Две ромашки 5 лепестков

Две ромашки 5 лепестков Эта статья продолжает (или развивает) тему преобразования сухих арабских цифр в крепкие русские выражения. Тему затронул Игорь Орещенков в №4 (345) от 29.01.2002 г. Программа, предлагаемая вашему вниманию на этот раз, выполняет ту же самую операцию, что и программа Игоря: переводит числа в словесную форму, но имеет несколько дополнительных возможностей, что делает ее более универсальной. Кроме того, использованы несколько иные алгоритмы и подходы, что, надеюсь, будет интересно для людей, неравнодушных к программированию, коими я считаю читателей КГ.

Исходный текст программы приведен ниже. Номера строк, которые вы можете там наблюдать, к программе никакого отношения не имеют. Они поставлены для того, чтобы на строки легче было ссылаться по ходу статьи.
Прежде, чем разрабатывать, оценивать или выбирать ту или иную программу преобразования чисел в слова, в первую очередь следует определиться, с какими числами нам придется иметь дело. Если это суммы белорусских рублей — одно дело, нам достаточно оперировать только целыми числами. То есть белорусскую платежку можно великолепно оформить, используя программу Игоря Орещенкова. Нужно только сделать первую букву текстовой строки прописной. Для этого достаточно добавить одну строку в его программу (аналог стр. 28). Если читателю нужна функция для оформления документов, содержащих только целые числа (штуки, пачки и т.п.), он может далее не забивать себе голову моей писаниной, а перейти к более интересным материалам газеты. А тем, кто работает с тоннами, декалитрами, баррелями, унциями, миллиграммами или каратами, настоятельно рекомендую не расслабляться и найти в себе силы для дальнейшего чтения.
Валюты стран с умеренной инфляцией, как правило, имеют разменные единицы: рубль — копейки, доллар и евро — центы. Также и другие единицы измерения: тонны включают в себя килограммы, километры — метры, граммы — миллиграммы и т.п. При текстовой записи дробная часть числа, как правило, прописью не записывается. Величину 120,550 т мы должны записать так: Сто двадцать тонн 550 кг. Используя программу Игоря, нам придется вставить ячейку в Excel довольно громоздкую запись, что-то типа: =СЦЕПИТЬ(NumbToStr(Число; 2; "тонна"; "тонны"; "тонн"); " "; ЕСЛИ(ОСТАТ(Число; 1> =0,001; СЦЕПИТЬ(ТЕКСТ(ОСТАТ(Число; 1)*1000; "000"); " кг."); "")), где Число = 120,55. Достаточно длинная фраза, но не смертельная, если ее не нужно будет использовать как часть другой формулы. Если вы уже набрали и отладили программу, может, и нет смысла что-то переделывать, я не утверждаю, что моя программа лучше, она просто другая.
Теперь самое время рассмотреть функцию, текст которой помещен в конце статьи. Синтаксис вызова у нее следующий: ЧислоВТекст(Namber [,R1 [,R2 [,R5 [,ND [,RD]]]]]), где
Namber — единственный обязательный аргумент — число, которое требуется преобразовать в текст.
R1 — размерность единицы; например: <один> рубль, принято по умолчанию.
R2 — размерность двойки; например: <два> рубля; принято по умолчанию.
R5 — размерность пятерки; например: <пять> рублей; принято по умолчанию.
ND — число разрядов после запятой, от 0 до 3, по умолчанию принят 0.
RD — размерность дробной части; например: коп. по значениям не меняется, принято по умолчанию.
Функция возвращает строку, описывающую число, переданное ей в качестве аргумента. Необязательные аргументы помещены в квадратные скобки. При вызове функции эти скобки рисовать, естественно, не нужно. То, что какие-то аргументы передавать необязательно, указано в строке 01 программы, приведенной ниже, — перед ними стоит оператор Optional. Необязательные аргументы имеют значение, принятое по умолчанию. Оно и будет присвоено соответствующей переменной, если вы поленитесь это сделать сами. Вставив в ячейку функцию: =ЧислоВТекст(120), вы получите запись: Сто двадцать рублей. Функция =ЧислоВТекст(120;;;;2) вернет запись: Сто двадцать рублей 00 коп. Значения, принятые по умолчанию, можно изменить. Для этого в строке 01, вместо слова "рубль", следует написать "белорусский рубль" или "бакс" — кому что. Чтобы решить предыдущую суперзадачу с 120,55 т, в ячейку следует вставить функцию: =ЧислоВТекст(Число; "тонна"; "тонны"; "тонн"; 3; "кг."). Формула оказалась не такая уж супер, использована всего одна функция (в прошлом примере пришлось использовать пять функций, две из которых — дважды).
Настырный читатель, а только такой мог добраться до этого места и не уснуть, заподозрит подвох и спросит: А как функция определяет родовую принадлежность исчисляемых объектов? Кого считаем: девочек, мальчиков или что-то среднее? Если девочка, значит — ОДНА (или сорок одна), мальчик — ОДИН, а неведомая зверюшка — ОДНО. Отвечаю: по окончанию, вернее по последней букве единичной размерности (аргумент R1). В процессе изучения вопроса я обратил внимание, что не только мужчины (в смысле люди) стараются грести весь мир под себя, но и все объекты, относящиеся к мужскому роду, ведут себя так же нахально. Они заняли практически все окончания, оставив на женскую долю только "А" и "Я" (милЯ, вахтА и т.п.), а для среднего рода "О" и "Е" (наименованиЕ, местО и т.п.). Есть, конечно, исключения: печЬ, мышЬ, ночЬ и др., но эти слова не являются распространенными единицами измерения. Количество печей мы измеряем штуками, мышей — головами, ночей — оргиями :). Строки программы 58..66 распознают род единицы измерения.
Конечно, такой подход нельзя назвать универсальным. Представим ситуацию, что какой-то султан захочет посчитать своих дочерей, используя нашу программу. Но не штуками или головами, а именно дочерьми. Можно попытаться выкрутиться, сказав, что дочь великого султана должна именоваться не иначе как "принцесса", "свет очей" или, на худой конец, "доченька". Но султана не проведешь, и поскольку мы не хотим закончить жизнь, сидя на остром колу, запишем функцию следующим образом: =ЧислоВТекст(ЧислоДочерей; "дочь#а", "дочери", "дочерей"). Конструкция "xxx#А" указывает функции, что размерность женского рода (об этом говорит последняя буква), но в результирующую строку следует включать только текст, записанный до знака # (за это отвечают строки 67..91). Султан доволен, мы спасены.
Этот же прием используется, если размерность требуется записать в сокращенном виде (не "тонна", а "т#а"). Если размерность мужского рода, можно особенно не напрягаться. Слова, которые не оканчиваются на "а", "я", "о" или "е", программа причисляет к роду мужчин.
Убедившись, что программа может участвовать не только в оформлении платежных поручений белорусского производства, но и товарно-распорядительных документов, отправимся дальше "в лес". Далее мы коснемся особенностей реализации алгоритма, и если тонкости программирования вас не занимают, можете отложить газету.
То, что Игорь Орещенков разбивает число на триады, а затем обрабатывает каждую триаду отдельно, на мой взгляд, весьма разумно. Более удачного подхода я пока не встречал. Но выделять триады можно по-разному. Игорь сделал это путем расчетов, в моей программе число сначала было преобразовано в строку, а затем строка разделена на сегменты. Оба способа работают, но второй способ позволяет избежать дополнительных вычислений, а значит и возможных погрешностей при округлениях (стр. 14..20).
Далее (стр. 21..26) формируется текстовое выражение, используя функцию Триада. В строке 28 первая буква фразы переводится в разряд прописных. В конце программы (стр.32..34) перехватываются возможные ошибки, например некорректные данные, переданные функции. Функция Триада() работает достаточно очевидно, поэтому комментировать ее особенно незачем, обращу внимание на использование функции Choose() (стр. 96, 103, 106, 118, 120, 122, 125), которая позволяет не создавать дополнительных массивов и сделать текст программы более наглядным. Функцию Триада() можно использовать в Excel автономно, т.е. отдельно от функции ЧислоВТекст(), если такая нужда возникнет.
Отдельно хочу описать процесс установки программы. Я расскажу про самый простой способ. Открываете файл Excel 2000 (или XP), в котором находится шаблон вашей платежки или накладной, далее меню Сервис/Макрос/Редактор Visual Basic (или просто нажмите Alt-F11) — и попадаете в среду VBA. Создаете программный модуль Insert/Module и в открытое окно перепишите исходный текст программы, приведенной ниже (номера строк не переписываете), сохраняем все и возвращаемся в среду Excel. После этого функции ЧислоВТекст() и Триада() можно использовать в любой ячейке таблицы этого файла так, как будто они всегда присутствовали в системе. Существуют и другие способы присоединения собственных функций к системе.
Таким образом, мы несколько расширили возможности Excel, приспособив ее для наших нужд. Создавая собственные функции, можно расширять вычислительные возможности Excel практически до бесконечности. Достаточно просто можно даже построить полноценную программу бухгалтерского учета или какую-либо другую систему.

01) Function ЧислоВТекст(Namber, Optional R1 = "рубль", Optional R2 = "рубля", Optional R5 = "рублей", Optional ND = 0, Optional RD = "коп.") As String
02) On Error GoTo Err_ЧислоВТекст
03) Dim МассивТриад(1 To 4) As Long
04) Dim I As Integer
05) Dim Буфер1 As String
06) Dim Буфер2 As String
07) Dim СтроковоеЗначение As String
08) Dim ДробнаяЧасть As String
09) Dim МассивРазрядов
10) If Namber = 0 Then
11) ЧислоВТекст = ""
12) Exit Function
13) End If
14) СтроковоеЗначение = Format(CStr(Namber), "000000000000.000")
15) ДробнаяЧасть = Mid(СтроковоеЗначение, 14, ND)
16) СтроковоеЗначение = Left(СтроковоеЗначение, 12)
17) ' выделение триад
18) For I = 4 To 1 Step -1
19) МассивТриад(I) = CInt(Mid(СтроковоеЗначение, (4 — I) * 3 + 1, 3))
20) Next

21) Буфер1 = Триада(МассивТриад(4), "миллиард", "миллиарда", "миллиардов") & Триада(МассивТриад(3), "миллион", "миллиона", "миллионов") & Триада(МассивТриад(2), "тысяча", "тысячи", "тысяч")
22) Буфер2 = Триада(МассивТриад(1), R1, R2, R5)
23) Буфер1 = Буфер1 & IIf(Буфер2 = "", " " & R5, Буфер2)
24) If ND <> 0 Then
25) Буфер1 = Буфер1 & " " & ДробнаяЧасть & " " & RD
26) End If

'Прописная буква вначале
27) Буфер1 = Trim(Буфер1)
28) Mid(Буфер1, 1) = UCase(Left(Буфер1, 1))
29) ЧислоВТекст = Буфер1
30) Exit_ЧислоВТекст:
31) Exit Function
32) Err_ЧислоВТекст:
33) MsgBox "ЧислоВТекст: ошибка!" & vbCrLf & Err.Number & " : " & Err.Description, vbCritical
34) Resume Exit_ЧислоВТекст
35) End Function

36) Function Триада(Namber, Optional R1 = "", Optional R2 = "", Optional R5 = "") As String
37) On Error GoTo Err_Триада
38) Const SP As String = " "
39) Dim ТриадаТекст As String 'триада цифр в текстовой форме
40) Dim СимволРода As String 'символ распознавания рода
41) Dim НомерРода As Byte
42) Dim Буфер1 As String
43) Dim Буфер3
44) Dim RP1 As String, RP2 As String, RP5 As String
45) Dim Позиция As Long
46) If Namber = 0 Then
47) Триада = ""
48) Exit Function
49) End If
50) 'приведем триаду к стандартному виду
51) ТриадаТекст = Right(CStr(Int(Namber)), 3)
52) ТриадаТекст = Format(ТриадаТекст, "000")
53) 'Если значение нулевое, возвращаем пустую строку
54) If CInt(ТриадаТекст) = 0 Then
55) Триада = ""
56) Exit Function
57) End If
58) 'Определим род размерности по последней букве
59) СимволРода = Right(StrConv(Trim(R1), vbUpperCase), 1)
60) If (СимволРода = "А") Or (СимволРода = "Я") Then
61) НомерРода = 2
62) ElseIf (СимволРода = "О") Or (СимволРода = "Е") Then
63) НомерРода = 3
64) Else
65) НомерРода = 1
66) End If

67) 'Выделяем размерность до знака #
68) If R1 <> "" Then
69) Позиция = InStr(R1, "#")
70) If Позиция <> 0 Then
71) RP1 = Left(R1, Позиция — 1)
72) Else
73) RP1 = R1
74) End If
75) End If
76) If R2 <> "" Then
77) Позиция = InStr(R2, "#")
78) If Позиция <> 0 Then
79) RP2 = Left(R2, Позиция — 1)
80) Else
81) RP2 = R2
82) End If
83) End If
84) If R5 <> "" Then
85) Позиция = InStr(R5, "#")
86) If Позиция <> 0 Then
87) RP5 = Left(R5, Позиция — 1)
88) Else
89) RP5 = R5
90) End If
91) End If

92) Буфер1 = "" 'задаем начальное значение строки
93) ' выделяем сотни
94) Буфер3 = CInt(ТриадаТекст) \ 100
95) If Буфер3 <> 0 Then
96) Буфер1 = Буфер1 & SP & Choose(Буфер3, "сто", "двести", "триста", "четыреста", "пятьсот", "шестьсот", "семьсот", "восемьсот", "девятьсот")
97) End If
98) 'десятки триады
99) Буфер3 = (CInt(ТриадаТекст) \ 10) Mod 10
100) If Буфер3 <> 0 Then
101) If Буфер3 = 1 Then 'специальная обработка чисел второго десятка
102) Буфер3 = CInt(ТриадаТекст) Mod 10 + 1
103) Триада = Буфер1 & SP & Choose(Буфер3, "десять", "одиннадцать", "двенадцать", "тринадцать", "четырнадцать", "пятнадцать", "шестнадцать", "семнадцать", "восемнадцать", "девятнадцать") & SP & R5
104) Exit Function
105) Else 'прочие десятки
106) Буфер1 = Буфер1 & SP & Choose(Буфер3 — 1, "двадцать", "тридцать", "сорок", "пятьдесят", "шестьдесят", "семьдесят", "восемьдесят", "девяносто")
107) If CInt(ТриадаТекст) Mod 10 = 0 Then
108) Триада = Буфер1 & SP & R5
109) Exit Function
110) End If
111) End If
112) End If
113) 'единицы триады
114) Буфер3 = CInt(ТриадаТекст) Mod 10
115) If Буфер3 <= 2 Then
116) Select Case НомерРода
117) Case 1
118) Буфер1 = Буфер1 & SP & Choose(Буфер3, "один", "два")
119) Case 2
120) Буфер1 = Буфер1 & SP & Choose(Буфер3, "одна", "две")
121) Case 3
122) Буфер1 = Буфер1 & SP & Choose(Буфер3, "одно", "два")
123) End Select
124) Else
125) Буфер1 = Буфер1 & SP & Choose(Буфер3 — 2, "три", "четыре", "пять", "шесть", "семь", "восемь", "девять")
126) End If
127) Select Case Буфер3
128) Case 1
129) Буфер1 = Буфер1 & SP & RP1
130) Case 2 To 4
131) Буфер1 = Буфер1 & SP & RP2
132) Case Else
133) Буфер1 = Буфер1 & SP & RP5
134) End Select

135) Триада = Буфер1
136) Exit_Триада:
137) Exit Function
138) Err_Триада:
139) MsgBox " ЧислоВТекст: ошибка!" & vbCrLf & Err.Number & " : " & Err.Description, vbCritical
140) Resume Exit_Триада
141) End Function

Программа написана 24.01.2001

Тимур Кошкаров, timur@bip.by



Компьютерная газета. Статья была опубликована в номере 08 за 2002 год в рубрике программирование :: разное

©1997-2024 Компьютерная газета