On-Line Библиотека www.XServer.ru - учебники, книги, статьи, документация, нормативная литература.
       Главная         В избранное         Контакты        Карта сайта   
    Навигация XServer.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 (точка входа в приложение):

Здесь же мы привели объявления некоторых переменных, которые потребуются нам в этом приложении. Из приведенного кода видно, что сначала мы пытаемся подсоединиться к серверу 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 ():

Этот метод довольно прост и прямолинеен. Сначала создаются строковые переменные для создания базы данных (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 ();}
  }
  
Мы оставляем этот метод без комментариев, поскольку он очень прост. 

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

Единственный заслуживающий пристального внимания момент, заключается в том, что после нажатия пользоватлем кнопки "Преобразовать" мы должны обратиться к таблице валют и выбрать из нее в массив 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;
    }
  

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

Вот, собственно, и все. Нам осталось только провести небольшое

Испытание

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

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

А вот теперь, действительно, все. Если у читателей возникнут вопросы, пишите на указанные в начале статьи адреса. Автор постарается ответить на них в самые сжатые сроки.
Кроме того, автор может предоставить совершенно бесплатно (т.е. безвозмездно) всем желающим исходные тексты приложения, которое было описано в этой статье. Для этого в поле Subject просто напишите: "jAmount (source files)".

Что дальше ?

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



Языки программирования: разное