вторник, 2 декабря 2008 г.

Суммы прописью (не только денежные)

Когда-то (году, кажется, в 2001) возникла стандартная задача, которая в среде программистов называется "Сумма прописью". Задача не особо сложная, но кропотливая. Некоторое время спустя я столкнулся с аналогичной задачей - вывести прописью единицы измерения (то ли метры, то ли килограммы - не помню). Решил и эту задачу и даже выложил на своем сайте (fplab.h10.ru). Со временем статья размножилась, но из нее исчезли все упоминания об авторе. Не то, чтобы стало обидно, но показалось как-то несправедливым. Да и сайт куда-то пропал. Разбираться почему - не стал, т.к. сайт на бесплатном хостинге и качать права - глупо. Вот эта статья.

Преобразование сумм из цифрового представления в строковое
Постановка задачи

Предлагаемая вниманию уважаемых читателей статья посвящена одной сугубо практической задаче, которая сплошь и рядом встречается в обширных классах офисных и бизнес-программ. Прежде всего, позвольте задать вам такой вопрос: случалось ли вам видеть или более того, держать в руках какой-либо денежный документ (например, платежное поручение, кассовый чек или счет-фактуру)? И еще: обращали ли вы внимание на то, что на любой денежной купюре ее номинал указан дважды: в виде последовательности цифр (числовой номинал) и в виде той же суммы прописью? В практике разработки программного обеспечения достаточно часто возникает необходимость преобразования сумм или чисел из цифровой формы в строковое представление. Поясним это на нескольких примерах:

* в соответствии с правилами бухгалтерского учета (принятым, не только в нашей стране, но и в международной практике), все суммы, указываемые в денежных документах должны быть отражены дважды: один раз цифрами, второй раз - прописью;
* при оформлении операций покупки/продажи иностранной валюты в обменных пунктах коммерческих банков, их клиентам на руки выдается справка для предъявления в органах таможни, в которой все необходимые суммы в рублях или иностранной валюте указываются дважды: цифрами и прописью;
* при заключении договоров и контрактов требования законодательства и существующая юридическая практика требуют, чтобы все встречающиеся в их тексте цифры (суммы, проценты, количества и проч.) были указаны дважды: цифрами и прописью;
* любая складская программа фигурирует такими понятиями как метры, килограммы, упаковки, ящики, штуки. При документальном оформлении этих товаров или их инвентаризации, мы опять сталкиваемся с необходимостью указания "двойственного" представления всех этих категорий: и цифрами, и прописью.

Читатель может с легкостью продолжить этот список. Приведенные выше примеры свидетельствуют, что задача преобразования сумм цифрами в соответствующие строковые представления, достаточно широко распространена и вполне естественно желание эту задачу автоматизировать.
В Internet можно найти немало примеров такого рода программ (их часто называют "конверторами"). Большая часть из них написана на VB или VBA и предназначена для выполнения соответствующих преобразований в приложениях MS Office. Немало существует конверторов для FoxPro. Наша задача, учитывая тематику этого сайта, заключается в написании Java-приложения, осуществляющего конвертацию сумм из цифрового представления, в строковое. Для определенности, мы напишем приложение, которое будет конвертировать денежные суммы, как наиболее часто встречающийся случай. Внимательный читатель с легкостью сумеет распространить "сферу влияния" этого приложения и на другие категории чисел которые нужно конвертировать в строки.

Прежде чем мы начнем говорить о самом приложении, нам придется на некоторое время "углубиться" в тонкости грамматики. Необходимость этого станет понятна, если мы рассмотрим небольшой пример:

810 121.21 Сто двадцать один рубль 21 копейка
810 121.23 Сто двадцать один рубль 23 копейки
810 121.25 Сто двадцать один рубль 25 копеек

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

810 121.21 Сто двадцать один рубль 21 копейка
276 121.21 Сто двадцать одна марка 21 пфенниг
840 121.21 Сто двадцать один доллар 21 цент

А здесь уже что-то новенькое: оказывается, нам необходимо учитывать не только падежи и числа (единственное или множественное), но и род той или иной денежной единицы. В данном случае марка (код 276) - валюта женского рода, а рубль (810) и доллар (840) - мужского.
Таким образом, как в рекламе зубной щетки: "в общем, вы поняли ...". Потратьте несколько минут на преобразования различных сумм в разных валютах в строки и вы согласитесь, что при всей простоте постановки задачи, ее решение, отнюдь не очевидно.
Однако, поставленная задача актуальна и ее необходимо как то решить, т.е. написать программу на выбранном нами языке программирования Java. Поэтому мы, как тому учат классики программирования (Ч.Хоар, Н.Вирт, Э.Дейкстра и Д.Кнут), займемся выработкой некой абстракции, которая позволит нам учесть все возможные варианты написания сумм в строки.

Строим абстракцию

Ограниченные рамки on-line публикации не позволят нам, к сожалению, изложить детальное описание процесса выработки необходимой нам абстракции и, поэтому, мы приводим только итоговую таблицу для одной валюты (код 810, т.е. рубли):

Последняя цифра Целая часть (слева) Дробная часть (справа) Род
1 рубль копейка Мужской
2, 3 ,4 рубля копейки Мужской
0, 5 рублей копеек Мужской

Внимательно рассмотрите эту таблицу. Попробуйте с ее помощью конвертировать какую либу сумму (в рублях, разумеется) в соответствующее строковое представление. Забавно, не правда ли ? Задержитесь на несколько минут и постройте подобные таблицы для марок (код 276), долларов (код 840) или любой другой валюты.
Не кажется ли вам, что теперь перед нами забрезжил выход из этой весьма запутанной ситуации с падежами, родами и прочими грамматическими "премудростями" ? Теперь становится понятно, как можно "справиться" с имеющейся сложностью: достаточно где-то (лучше всего в какой-либо базе данных или в конфигурационном файле) хранить приведенную таблицу для каждой нужной валюты и написать некий "обработчик" этой информации.
Конечно, этот обработчик будет, по видимости включать в себя большое количество инструкций выбора (т.е. конструкций if и case) и трудно рассчитывать на то, что он будет компактным, однако принципиально, он (обработчик) не сложен: берем число, разбиваем его на группы и подбираем для каждой группы соответствующее строковое представление из базы данных или файла конфигурации. Поэтому, приступим к программированию, но перед этим сначала решим ...

Где хранить настроечную информацию ?

Сразу договоримся, что вышеприведенную таблицу мы будем в дальнейшем называть "настроечной информацией". Мы уже говорили, что настроечную информацию по валютам можно хранить либо в базе данных, либо в конфигурационном файле. Мы остановились на первом способ, поскольку, как правило, бухгалтерские приложения уже включают в себя справочник валют в виде одной или нескольких таблиц базы данных. В качестве базы данных мы выбираем широко распространненный сервер MySQL. Кроме, собственно, сервера нам потребуется еще jdbc-драйвер. Если вы никогда не работали с MySQL или с этим драйвером, ничего страшного: оба они довольно просто инсталлируются и снабжены удобной и понятной документацией.
Более того, принятый нами подход отличается замечательной особенностью: его можно использовать практически с любой базой данных; главное условие - наличие соответствующего jdbc-драйвера. Тем не менее, приведенные примеры кода ориентированы именно на MySQL и, возможно, вам придется их немного изменить. Впрочем, как показывает практика, это не составляет большого труда.
Наше приложение будет состоять из двух основных классов: fplAmount и jAmount. Первый класс носит вспомогательный характер и предназначен для создания тестовой базы данных, таблицы валют и заполнения этой таблицы необходимой настроечной информацией. Кроме того, этот класс предоставлет пользователю простую графическую оболочку для выбора валюты и ввода суммы цифрами. Второй класс осуществляет преобразование введенной суммы из цифрового представления в строковое. Рассмотрим сначала класс fplAmount.
Начнем, как водится, с метода main (точка входа в приложение):

// Импорт необходимых пакетов JDK 1.3
import java.awt.*;
import java.awt.event.*;
import java.sql.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;

public class fplAmount extends JFrame {

static JFrame frame; // Основное окно приложения
static JComboBox currBox; // Выпадающий список валют
static JTextField summaField; // Поле для ввода суммы
static Connection conn = null; // Объект-соединение
static Vector cv = new Vector (); // Вектор кодов валют

// В этой строке будет находиться результат преобразования
// суммы цифрами в сумму прописью
static String res = "";
...........................
// Точка входа ...
public static void main (String [] args) throws Exception {
connect (); // Подсоединиться к серверу MySQL
checkAndFill (); // Проверить данные
selectCurrency (); // Сформировать список валют
UIManager.setLookAndFeel ("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
frame = new fplAmount ();
}
}


Здесь же мы привели объявления некоторых переменных, которые потребуются нам в этом приложении. Из приведенного кода видно, что сначала мы пытаемся подсоединиться к серверу MySQL, затем проверяем и заполняем (при необходимости) данными таблицу валют, формируем список валют и, наконец, создаем и выводим на экран основное окно приложения. Для тех, кто работает в среде, отличной от Windows, можно порекомендовать закомментировать строку, определяющую стиль пользовательского интерфейса (UIManager.setLookAndFeel ...). Разберем по порядку перечисленные методы.

// Подключиться к серверу MySQL
static void connect () {
try {
Class.forName ("org.gjt.mm.mysql.Driver");
conn = DriverManager.getConnection (
"jdbc:mysql://localhost:3306/mysql");
}
catch (SQLException sqle) {
System.out.println ("SQL exception:" + sqle.getMessage ());
System.out.println ("SQLState:" + sqle.getSQLState ());
System.out.println ("VendorError:" + sqle.getErrorCode ());
System.exit (-1);
}
catch (Exception sqle) {sqle.printStackTrace ();}
}


Сначала мы загружаем класс-драйвер, а затем пытаемся получить объект- соединение с сервером MySQL. Поскольку мы не знаем, существует ли на момент создания соединения с сервером MySQL необходимая нам база данных, мы "обманываем" сервер, подсоединяясь к наверняка существующей базе данных mysql (заметим, что для этого у нас должны быть соответствующие права доступа, поэтому, не исключено, что вам придется изменить параметр в методе DriverManager.getConnection и добавить в него имя и пароль). Если соединение установлено успешно, то объект-соединение Connection conn получает значение, отличное от null. Если же соединение установить не удалось (например потому, что сервер MySQL не запущен или в параметре метода DriverManager.getConnection что-то не так), то приложение аварийно завершит свою работу и выйдет в операционную среду.

Теперь рассмотрим метод checkAndFill ():

// Проверить существование таблицы валют и заполнить ее данными
static void checkAndFill () {
Statement stmt = null;
// SQL-запрос для создания тестовой базы данных
String sqlCreateDB = "create database jAmount";
String sqlUseDB = "use jAmount";
// SQL-запрос для создания тестовой таблицы валют
String sqlCreateTable = "create table currency (" +
"ID_Currency smallint not null," +
"ISO_Currency char (3)," +
"Scale int," +
"Description char (32)," +
"i1 varchar (32)," +
"i24 varchar (32)," +
"i5 varchar (32)," +
"r1 varchar (32)," +
"r24 varchar (32)," +
"r5 varchar (32)," +
"Sex char (1)," +
"primary key (ID_Currency))";
// SQL-запросы для заполнения таблицы валют тестовыми данными
String [] sqlText = {
"insert into currency values (810,'RUR',1,'Российские рубли','рубль','рубля','рублей','копейка','копейки','копеек','M')",
"insert into currency values (276,'DEM',1,'Немецкие марки','марка','марки','марок','пфенниг','пфеннига','пфеннигов','F')",
"insert into currency values (840,'USD',1,'Доллары США','доллар','доллара','долларов','цент','цента','центов','M')"
};

// Создать базу данных и выбрать ее по умолчанию
try {
stmt = conn.createStatement ();
boolean value = stmt.execute (sqlCreateDB);
}
catch (SQLException sqle) {}
catch (Exception sqle) {}
try {
stmt = conn.createStatement ();
boolean value = stmt.execute (sqlUseDB);
}
catch (SQLException sqle) {}
catch (Exception sqle) {}

// Cоздать таблицу со списком валют
try {
stmt = conn.createStatement ();
boolean value = stmt.execute (sqlCreateTable);
}
catch (SQLException sqle) {}
catch (Exception sqle) {}
// Заполнить тестовую таблицу валют данными
for (int i = 0; i <= sqlText.length; i++) {
try {
int value = stmt.executeUpdate (sqlText [i]);
}
catch (SQLException sqle) {}
catch (Exception sqle) {}
}
}


Этот метод довольно прост и прямолинеен. Сначала создаются строковые переменные для создания базы данных (sqlCreateDB), выбора базы данных по умолчанию (sqlUseDB), создания таблицы валют (sqlCreateTable) и, наконец, массив строк для заполнения таблицы валют данными (sqlText). Далее последовательно выполняются необходимые SQL-инструкции.
Остановимся несколько подробней на переменной sqlCreateTable. Как видно из приведенного кода структура таблицы валют проста и (за исключением нескольких полей) полностью дублирует структуру таблицы из раздела "Строим абстракцию". Поскольку структура этой таблицы будет играть ключевую роль в дальнейшем изложении, мы еще раз рассмотрим ее:

Поле Тип Назначение
ID_Currency smallint числовой идентификатор валюты (810, 276, 840 и т.д.)
ISO_Currency char (3) так называемый ISO-код (буквенное обозначение валюты: (рубли - RUR, доллары - USD, марки - 276 и т.д.)
Scale int масштаб (т.е. за какое количество данной валюты устанавливаются котрировки Банка России
Description char (32) наименование валюты
i1 varchar (32) строковое представление последней цифры целой части
i24 varchar (32) строковое представление последней цифры целой части
i5 varchar (32) строковое представление последней цифры целой части
r1 varchar (32) строковое представление последней цифры дробной части
r24 varchar (32) строковое представление последней цифры дробной части
r5 varchar (32) строковое представление последней цифры дробной части
Sex char (1) род валюты (M - мужской, F - женский)


Наконец, в sqlText указаны данные для трех валют: рублей, марок и долларов. Т.о. мы создаем базу данных, выбираем ее по умолчанию, создаем таблицу валют и заполняем ее в цикле данными из массива sqlText.

Последний метод, который мы должны рассмотреть, это метод selectCurrency (). Его назначение понятно: выбрать числовые коды (ID_Currency) в вектор.

// Выбрать коды валют из таблицы CURRENCY в вектор
static void selectCurrency () {
try {
Statement stmt = conn.createStatement ();
ResultSet rset = stmt.executeQuery (
"select id_currency from currency");
while (rset.next ()) {
cv.addElement (rset.getObject (1));
}
}
catch (SQLException sqle) {
System.out.println ("SQL exception:" + sqle.getMessage ());
System.out.println ("SQLState:" + sqle.getSQLState ());
System.out.println ("VendorError:" + sqle.getErrorCode ());
System.exit (-1);
}
catch (Exception sqle) {sqle.printStackTrace ();}
}


Мы оставляем этот метод без комментариев, поскольку он очень прост.

Наконец, чтобы окончательно закончить подготовку к конвертации сумм в строковое представление, представим конструктор, который создает основное окно приложения и позволяет организовать диалог с пользователем:

// Создаем основное окно приложения
public fplAmount () {
setTitle ("Сумма прописью");
setFont (new Font ("Helvetica", Font.PLAIN, 12));
// Разместить компоненты в основном окне
JLabel currLabel = new JLabel (" Валюта ", JLabel.RIGHT),
summaLabel = new JLabel (" Сумма ", JLabel.RIGHT);
currBox = new JComboBox (cv);
summaField = new JTextField ("0.00", 12);
JButton convertButton = new JButton ("Преобразовать");
JPanel lPanel = new JPanel (new GridLayout (2, 1)),
rPanel = new JPanel (new GridLayout (2, 1)),
topPanel = new JPanel (new BorderLayout ()),
botPanel = new JPanel (new FlowLayout (FlowLayout.RIGHT));
lPanel.add (currLabel);
lPanel.add (summaLabel);
rPanel.add (currBox);
rPanel.add (summaField);
topPanel.add (lPanel, BorderLayout.WEST);
topPanel.add (rPanel, BorderLayout.CENTER);
botPanel.add (convertButton);
getContentPane ().add (topPanel, BorderLayout.CENTER);
getContentPane ().add (botPanel, BorderLayout.SOUTH);
pack ();
setSize (getPreferredSize ().width, getPreferredSize ().height);
setResizable (true);
// Вывести основное окно в центре экрана монитора
Dimension sSize = Toolkit.getDefaultToolkit ().getScreenSize ();
Dimension fSize = getSize ();
if (fSize.height > sSize.height) fSize.height = sSize.height;
if (fSize.width > sSize.width) fSize.width = sSize.width;
setLocation ((sSize.width - fSize.width)/2, (sSize.height - fSize.height)/2);
setVisible (true);
// Обработчик события нажатия на кнопку "Преобразовать"
convertButton.addActionListener (new ActionListener () {
public void actionPerformed (ActionEvent e) {
// Определить код выбранной валюты из выпадающего списка
String code = currBox.getSelectedItem ().toString ();
// Массив суффиксов - окончаний. Элементы массива:
// 0...5 - строки целой и дробной частей;
// 6 - род валюты (M - мужской, F - женский)
String [] suff = {"", "", "", "", "", "", ""};
// Выбрать из таблицы настроечную информацию,
// касающуюся данной валюты
try {
Statement stmt = conn.createStatement ();
ResultSet rset = stmt.executeQuery ("select i1,i24,i5,r1,r24,r5,Sex from currency where id_currency=" + code);
ResultSetMetaData meta = rset.getMetaData ();
int cols = meta.getColumnCount ();
// Занести настроечную информацию в массив
while (rset.next ()) {
for (int i = 0; i < cols; i++) suff [i] = rset.getString (i + 1);
}
}
catch (SQLException sqle) {
System.out.println ("SQL exception:" + sqle.getMessage ());
System.out.println ("SQLState:" + sqle.getSQLState ());
System.out.println ("VendorError:" + sqle.getErrorCode ());
System.exit (-1);
}
catch (Exception sqle) {sqle.printStackTrace ();}
// Перевести число в строку !!!
new jAmount (suff, summaField.getText ());
}
});

addWindowListener (new WindowAdapter () {
public void windowClosing (WindowEvent event) {
System.exit (0);
}
});
}


Единственный заслуживающий пристального внимания момент, заключается в том, что после нажатия пользоватлем кнопки "Преобразовать" мы должны обратиться к таблице валют и выбрать из нее в массив suff настроечную информацию для выбранной валюты. Этот массив (вместе со строкой, представляющей собой сумму) передаются классу jAmount для преобразования (конвертирования) суммы цифрами в строку.
Конвертируем

Вот наконец-то, мы добрались до цели нашего рассказа: преобразования сумм цифрами в строку. Класс jAmount достаточно объемен, однако, он принципиально не сложен, поскольку всю подготовительную работу мы выполнили ранее. Напомним, что нашей целью было получение т.н. настроечной информации по выбранной валюте. Эту настроечную информацию (в виде массива) мы и передаем конструктору класса jAmount. И еще: кроме собственно настроечной информации мы передаем строку, представляющую собой набор цифр, который класс jAmount, должен преобразовать (конвертировать) в строковое представление суммы. Мы не будем комментировать этот класс и его методы так же, как мы комментировали класс fplAmount. Все заслуживающие внимания моменты содержатся в комментариях к приведенному коду. Не пугайтесь, если сразу вам не удастся понять суть применяемого алгоритма. Скопируйте код на свой диск, вставьте в "подозрительные" или непонятные места отладочную печать на консоль или лучше в файл, откомпилируйте и изучайте !

import java.math.*;

public class jAmount {

private BigInteger summ;
private String [] suff;
private static final BigInteger zero = new BigInteger ("0");
private static final BigInteger hundred = new BigInteger ("100");
private static final BigInteger thousand = new BigInteger ("1000");

// Конструктор класса. Конструктор в качестве параметров получает:
// String suff [] массив наименований (настроечная информация) и
// String sum сумма для преобразования
public jAmount (String [] suff, String s) {
this.suff = suff;
try {
BigDecimal decimal = new BigDecimal (s);
// Преобразуем в копейки (центы, пфенниги и т.д.),
// одним словом - убираем дробную часть
decimal = decimal.multiply (new BigDecimal (100.00));
summ = decimal.toBigInteger ();
// Приступить к преобразованию
toString ();
// Метод для вывода результата преобразования. Можно просто
// выводить полученное строковое представление суммы на консоль:
// System.out.println (fplAmount.res);
jAmountResult jar = new jAmountResult (fplAmount.frame);
jar.setVisible (true);
}
catch (NumberFormatException e) {
// Ой !!!!! Что-то не так: скорее всего, в строке
// представляющей собой сумму цифрами, встретились символы
// отличные от цифр и точки. Можно просто выводить сообщение
// об ошибках на консоль:
// System.out.println (e);
jAmountError jae = new jAmountError (fplAmount.frame);
jae.setVisible (true);
}
}

// Получить правую (дробную) часть суммы
public String getRightPart () {
return alignSumm (summ.remainder (hundred).abs ().toString ());
}

// Если сумма меньше 10, то выровнять ее дописыванием "0"
String alignSumm (String s) {
switch (s.length ()) {
case 0: s = "00"; break;
case 1: s = "0" + s; break;
}
return s;
}


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

public String toString () {
StringBuffer result = new StringBuffer ();
BigInteger [] divrem = summ.divideAndRemainder (hundred);
if (divrem [0].signum () == 0) result.append ("Ноль ");
divrem = divrem [0].divideAndRemainder (thousand);
BigInteger quotient = divrem [0];
BigInteger remainder = divrem [1];
int group = 0;
do {
int value = remainder.intValue ();
result.insert (0, groupToString (value, group));
// Для нулевой группы добавим в конец соответствующую валюту
if (group == 0) {
int rank10 = (value % 100) / 10;
int rank = value % 10;
if (rank10 == 1) {
result = result.append (suff [2]);
}
else {
switch (rank) {
case 1: result = result.append (suff [0]); break;
case 2:
case 3:
case 4: result = result.append (suff [1]); break;
default: result = result.append (suff [2]); break;
}
}
}
divrem = quotient.divideAndRemainder (thousand);
quotient = divrem [0];
remainder = divrem [1];
group++;
}
while (!quotient.equals (zero) || !remainder.equals (zero));

// Дробная часть суммы
String s = getRightPart ();
result = result.append (" ").append (s);
result = result.append (" ");

if (s.charAt (0) == '1') {
result = result.append (suff [5]);
}
else {
switch (s.charAt(1)) {
case '1': result = result.append (suff [3]); break;
case '2':
case '3':
case '4': result = result.append (suff [4]); break;
default: result = result.append (suff [5]); break;
}
}
// По правилам бухгалтерского учета первая буква строкового
// представления должна быть в верхнем регистре
result.setCharAt (0, Character.toUpperCase (result.charAt (0)));

// Вот ради этой строки все и затевалось: результат получен !!!
fplAmount.res = result.toString();
return result.toString();
}

// Преобразование группы цифр в строку
String groupToString (int value, int group) {
if (value < 0 || value > 999) throw new IllegalArgumentException ("value must be between 0 an 999 inclusively");
if (group < 0) throw new IllegalArgumentException ("group must be more than 0");
StringBuffer result = new StringBuffer (32);
if (value == 0) {
return result.toString();
}
// Разбор числа по разрядам, начиная с сотен
int rank = value / 100;
switch (rank) {
default: break;
case 1: result = result.append ("сто "); break;
case 2: result = result.append ("двести "); break;
case 3: result = result.append ("триста "); break;
case 4: result = result.append ("четыреста "); break;
case 5: result = result.append ("пятьсот "); break;
case 6: result = result.append ("шестьсот "); break;
case 7: result = result.append ("семьсот "); break;
case 8: result = result.append ("восемьсот "); break;
case 9: result = result.append ("девятьсот "); break;
}
// Далее, десятки
rank = (value % 100) / 10;
switch (rank) {
default: break;
case 2: result = result.append ("двадцать "); break;
case 3: result = result.append ("тридцать "); break;
case 4: result = result.append ("сорок "); break;
case 5: result = result.append ("пятьдесят "); break;
case 6: result = result.append ("шестьдесят "); break;
case 7: result = result.append ("семьдесят "); break;
case 8: result = result.append ("восемьдесят "); break;
case 9: result = result.append ("девяносто "); break;
}
// Если десятки = 1, добавить соответствующее значение. Иначе - единицы
int rank10 = rank;
rank = value % 10;
if (rank10 == 1) {
switch (rank) {
case 0: result = result.append ("десять "); break;
case 1: result = result.append ("одиннадцать "); break;
case 2: result = result.append ("двенадцать "); break;
case 3: result = result.append ("тринадцать "); break;
case 4: result = result.append ("четырнадцать "); break;
case 5: result = result.append ("пятнадцать "); break;
case 6: result = result.append ("шестнадцать "); break;
case 7: result = result.append ("семнадцать "); break;
case 8: result = result.append ("восемнадцать "); break;
case 9: result = result.append ("девятнадцать "); break;
}
}
else {
switch (rank) {
default:
break;
case 1:
if (group == 1) // Тысячи
result = result.append ("одна ");
else
// Учесть род валюты (поле "Sex" настроечной информации)
if (suff [6].equals ("M")) result = result.append ("один ");
if (suff [6].equals ("F")) result = result.append ("одна ");
break;
case 2:
if (group == 1) // Тысячи
result = result.append ("две ");
else
// Учесть род валюты (поле "Sex" настроечной информации)
if (suff [6].equals ("M")) result = result.append ("два ");
if (suff [6].equals ("F")) result = result.append ("две ");
break;
case 3: result = result.append ("три "); break;
case 4: result = result.append ("четыре "); break;
case 5: result = result.append ("пять "); break;
case 6: result = result.append ("шесть "); break;
case 7: result = result.append ("семь "); break;
case 8: result = result.append ("восемь "); break;
case 9: result = result.append ("девять "); break;
}
}
// Значение группы: тысячи, миллионы и т.д.
switch (group) {
default:
break;
case 1:
if (rank10 == 1)
result = result.append ("тысяч ");
else {
switch (rank) {
default: result = result.append ("тысяч "); break;
case 1: result = result.append ("тысяча "); break;
case 2:
case 3:
case 4: result = result.append ("тысячи "); break;
}
}
break;
case 2:
if (rank10 == 1)
result = result.append ("миллионов ");
else {
switch (rank) {
default: result = result.append ("миллионов "); break;
case 1: result = result.append ("миллион "); break;
case 2:
case 3:
case 4: result = result.append ("миллиона "); break;
}
}
break;
case 3:
if (rank10 == 1)
result = result.append ("миллиардов ");
else {
switch (rank) {
default: result = result.append ("миллиардов "); break;
case 1: result = result.append ("миллиард "); break;
case 2:
case 3:
case 4: result = result.append ("миллиарда "); break;
}
}
break;
case 4:
if (rank10 == 1)
result = result.append ("триллионов ");
else {
switch (rank) {
default: result = result.append ("триллионов "); break;
case 1: result = result.append ("триллион "); break;
case 2:
case 3:
case 4: result = result.append ("триллиона "); break;
}
}
break;
}
return result.toString();
}
}


Вот, собственно, и все. Нам осталось только провести небольшое
Испытание
Испытать наше приложение достаточно просто:

* убедитесь, что сервер MySQL запущен;
* откомпилируйте приложение javac fplAmount.java;
* запустите приложение на исполнение java fplAmount

Обязательно поэкпериментируйте с самыми разными суммами для разных валют. Помните, что мы говорили об отладочной печати на консоль: если что-то осталось вам неясным или непонятным, выводите информацию на консоль и анализируйте, анализируйте, анализируйте...
В нашем изложении "за бортом" осталась реализация двух вспомогательных классов jAmountResult (для вывода результатов преобразования) и jAmountError (для вывода сообщений об ошибках). Это мы оставляем читателям в качестве несложного упражнения.

А вот теперь, действительно, все. Если у читателей возникнут вопросы, пишите на указанные в начале статьи адреса. Автор постарается ответить на них в самые сжатые сроки.
Что дальше ?

Помните, в начале статьи мы говорили о том, что не только денежные суммы должны преобразовываться в строки ? В строки необходимо переводить метры, килограммы, штуки, литры и другие единицы измерения. Рассмотренное нами приложение представляет собой отправную точку для разработки подобных задач.
Дерзайте и вам улыбнется удача !

пятница, 28 ноября 2008 г.

Информация для работодателей
Специализируюсь в области анализа и постановки задач, а также разработки профессионального ПО (преимущественно в области учета - бухгалтерского, финансового, управленческого, складского и т.п.)
Профессионально владею: Java, ObjectPascal
БД: Oracle, MSSQL, Sybase, Pervasive (разумеется, знаю SQL)
ОС: Windows (куда же без нее), Linux
Последние 16 лет работал в основном в банках. Опыт руководства коллективами разработчиков.
Проекты, выполненные в последние годы (конечно, только более-менее крупные и сложные):
1. проектирование, разработка и внедрение АБС (автоматизированной банковской системы на СУБД Informix под Unix)
2. разработка различного ПО для банков: кредитование юридически и физических лиц, потребительское кредитование, автокредитование, ипотечное кредитование. Модули: кассовых операций, валютно-обменных, удаленного обслуживания ("банк-клиент")
3. разработка аналитического ПО для оценки ликвидности, кредитоспособности, финансовой устойчивости и других показателей
4. разработка ПО автострахования (включая КАСКО)
5. множество более мелких проектов, коих сразу и не упомнить

Место жительства - Ростов-на-Дону

Подробности - по Вашему запросу.

Контакты:
icq: 338-488-305 (имя fplab)
mail: fplab@inbox.ru, fplab@mail.ru

четверг, 27 сентября 2007 г.

Хороший материал, найденный в Wiki

Да, хорошее все-таки изобретение эта самая энциклопедия Wiki. Искал на днях кое-что и поисковик вывел на страничку
"счетчиковых" машин (перевод, конечно, еще тот, но в целом правильно отражает идею; более употребительно название "регистровые" машины).
По-моему, изучение программирования надо начинать не со стандартного Pascal/C/Basic etc., а с таких вот "примитивных" ассемблеров. После этого гораздо сдержанней начинаешь относиться к бантикам и рюшечкам (кажется, это сказал Э.Дейкстра) современных языков программирования; появляется осознание того, без скольких совершенно не нужных вещей (конструкций, метафор, парадигм, ...) можно легко обойтись.

пятница, 7 сентября 2007 г.

Небольшой (надеюсь) timeout

К сожалению, в связи с некоторыми обстоятельствами, вынужден на время прервать ведение блога. Тем не менее, как только представится случай, я намерен вернуться к нему. Тем более, есть кое-какой материал, который правда довольно сыроват для публикации. Но лучше не торопиться: всему свое время.
Удачи всем !!!

P.S. Или я чего-то не понимаю, но нигде не видно моего электронного адреса для переписки. Сейчас разбираться некогда, поэтому сделаю проще: пишите сюда

суббота, 1 сентября 2007 г.

Между тем...

По ходу дела вспомнил об одной теме в форуме Wasm.RU, где уже с полгода как возник (с моей подачи, каюсь) вопрос о минимальном наборе команд какого-либо процессора. Ответы неожиданно порадовали, поскольку участники форума вспомнили о стрелке Пирса, штрихе Шеффера и о "загадочных" для меня машинах Шенфилда. Слово "загадочные" я взял в кавычки не случайно: о машине Тьюринга не знает только ленивый, чуть меньше знакома машина Поста (последней, кстати, посвящена увлекательная книга В.А.Успенского, которая так и называется; вот тут свободно лежит ее электронная копия, рекомендую); существует и ряд других подходов. Но что есть "машина Шенфилда", для меня остается загадкой.
Разумеется, я сначала подумал, что эта машина каким-либо образом связана с известной книгой Джозефа Шенфилда (или, вероятнее, правильнее - Шёнфилда) "Математическая логика", изданной еще в 1975 г. в серии "Математическая логика и основания математики". Книга весьма интересная, хотя и сложная для первоначального знакомства с предметом. Но ничего о каких-бы то ни было машинах я в этой книге не нашел. Более того, ни в одной из известных книг по математической логике, побывавших в моих руках (а список этот, поверьте, весьма обширный) о машинах Шенфилда не сказано ни слова.
Один из участников вышеупомянутого обсуждения прислал мне свой университетский конспект лекций по математической логике и теории алгоритмов. Там, действительно, речь идет о машинах Шенфилда. Оказалось, что "машины Шенфилда" - не что иное, как давным давно известные регистровые машины: см., например, М.Минский "Вычисления и автоматы" и Дж.Булос и Р.Джеффри "Вычислимость и логика". Правда, ни в первой, ни во второй книге о машинах Шенфилда - ни слова.
Таким образом, я никак не могу победить "лингвистическую" загадку: причем тут Шенфилд. Понимаю, что это пустяк, не более чем безделица, но интересно все-таки.

среда, 29 августа 2007 г.

"Ты помнишь, как все начиналось...?"

Итак, приступаю (и, по старой доброй национальной традиции, не прямо к делу, а несколько издалека)...
Почти всякий курс математической логики начинается стандартно: с исчисления высказываний. Вводятся логические операции (иначе, связки): конъюнкция, дизъюнкция, отрицание, импликация и эквивалентность; также постулируется наличие двух истинностных значения: истина (true) и ложь (false).
Далее, приводятся таблицы истинности логических связок и изучаются способы определения истинности логических формул путем построения таблиц. Учащемуся предлагается изучить два-три десятка логических формул (иногда и больше), которые при любых значениях истинности составляющих их частей, всегда имеют значение true и показывается, как эти формулы можно применять. Потом наступает пора исчисления предикатов и прочих технических вещей, которые не слишком сложны, но порой утомительны. Понятно, что порядок и способ изложения тех или иных понятий может, конечно, разниться, но общая тенденция примерно такова.
Когда учащийся окончательно притомится и начнет терять надежду выпутаться из бесконечных таблиц, логических формул, когда он почти разуверится, что все это может быть не только полезным, но и интересным, это самое интересное и наступает: теория множеств (причем, уровень изложения может быть от элементарного - на уровне понятий, до весьма продвинутого), формальная арифметика и теория доказательств. Обычно, тут излагаются действительно интересные и глубокие вещи: теоремы Геделя, обсуждаются парадоксы и т.д.
Если остается время и силы учащегося окончательно не иссякли, то иногда дается введение в теорию рекурсивных функций и теорию вычислимости.
Тема настоящего блога как раз последнее - вопросы вычислимости. Мы не будем глубоко и тщательно касаться теории (для этого есть много хороших книг, написанных выдающимися специалистами; упомянем имена Д.Гильберта, С.Клини, А.Тарского и других), а в основном займемся практикой. Разумеется, без базовых понятий в той же математической логике не обойтись. Тем, кто запамятовал, что это такое лучше обратиться к какому-нибудь стандартному учебнику, скажем, книгам Мендельсона, Колмогорова, Клини или Новикова (одним словом, подойдет любой университетский учебник логики для математических факультетов, какой читетель сможет найти).
В качестве более живого и увлекательного (но, отнюдь далеко не простого) чтения, можно порекомендовать великолепные книги американского логика и известного популяризатора Р.Смаллиана: поищите, не пожалеете.
Вот, пожалуй, и все, что я хотел сегодня поведать. До встречи и удачи всем !!!

понедельник, 27 августа 2007 г.

Вместо введения

Здравствуйте, уважаемый посетитель !
Честно говоря, это мой первый опыт блоговедения. Не обессудьте, если что не так - постараюсь быть внимательным к содержательным замечаниям и конструктивной критике и по-возможности быстро исправлять ляпы и устранять ошибки. Надеюсь, что блог окажется удачным, неутомительным, продуктивным и, не в последнюю очередь, занимательным. Да-да, еще и занимательным, поскольку вокруг тем, которые я хочу предложить Вашему вниманию, незаслуженно сложился ореол избыточной абстрактности, оторванности от жизни и, как следствие, едва ли не бесполезности.
Чтобы не возвращаться к этому, позвольте сразу представиться: меня зовут Алексеем. Область интересов - математическая логика, основания математики и программирование. Если чуть более конкретно (но без излишних и утомительных подробностей, неуместных в этом "введении") это вопросы вычислимости и всякие "штучки", вроде машин Тьюринга, Поста, рекурсивных функций и тому подобные вопросы.
Причем тут программирование ? Честно говоря, почти и не причем. Разве что, во-первых, это все-таки "близкородственные" направления (с той существенной разницей, что логика, основания математики и вычислимость направления больше теоретические, а программирование - преимущественно прикладное) и, во-вторых, надо же как-то зарабатывать на хлеб насущный и если уж приходится заниматься программированием, то, по крайней мере, не испытывая известного омерзения от занятий этим делом. Это, конечно, шутка!
Затевая этот блог мне меньше всего хочется, чтобы он превратился в "помойку", в какую рано или поздно превращаются почти все форумы сходного направления (особенно это касается чисто программистских форумов). Религиозные споры (Windows против Linux, C++ против Java и т.п.) надоели и опротивели. Прежде чем дать автору вопроса содержательный ответ, его (т.е. автора) почти непременно оскорбят, отошлют к RTFM, усомнятся в умственных способностях, сексуальной ориентации и профессиональной пригодности. И даже после всего этого отнюдь не факт, что ответ будет получен. Надеюсь, читатели и участники обсуждений в этом блоге (если он состоится и к чему я постараюсь приложить все силы) обладают достаточным культурным уровнем и не опустятся до описанной выше мерзости.
Засим, позвольте на время откланяться. Чтобы не оставлять в неопределенности потенциальных собеседников блога, я хочу обозначить следующую тему, которая, на мой взгляд, лежит на стыке теории и практики - минимизация конструкций языков программирования.
Спасибо, что прочли это несколько затянувшееся введение и до встречи !!! Удачи всем