Сумма прописью: по-русски и на Java

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

Да, русский язык, все-таки, плохо подлежит алгоритмизации. Такие вещи, как «одна тысяЧА», но «две тысяЧИ», «пять тысяЧ»; или, скажем «триСТА», но «пятьСОТ»; или «двадЦАТЬ», но «сорок(!)» и «девяноСТО»; или «двЕ тысячи», но «двА миллиона» — со всем этим пришлось изрядно повозиться.

И еще: как, все-таки, хорошо, что в современных версиях Java можно использовать национальные алфавиты без дополнительных телодвижений.


package net.sf.lab3f.util;

import java.util.Stack;

public class RussianMoney {

    private static enum Ranges {
        UNITS, DECADES, HUNDREDS, THOUSANDS, MILLIONS, BILLIONS
    };

    private static Stack<ThreeChar> threeChars;

    private static class ThreeChar {

        char h, d, u;
        Ranges range;
    }

    public static String digits2text(String s) {
        try {
            return digits2text(new Double(s));
        } catch (NumberFormatException ex) {
            System.out.println(ex);
            return null;
        }
    }

    public static String digits2text(Double d) {
        // TODO: Научиться парсить числа > 1000000
        String s = d.toString();
        int n = s.length() - s.lastIndexOf('.');
        if (d == null || d < 0.0 || d > 1000000.0 || n > 3) {
            System.out.println("Пока гарантированно преобразуются лишь числа от 0.0 до 1000000.0");
            return null;
        }
        if (n == 2) {
            s += "0";
        }
        String[] sa = s.split("\\.");
        threeChars = new Stack<>();
        threeChars.push(new ThreeChar());
        threeChars.peek().range = Ranges.UNITS;
        StringBuilder sb = new StringBuilder(sa[0]).reverse();
        for (int i = 0; i < sb.length(); i++) {
            if (i > 0 && i % 3 == 0) {
                threeChars.push(new ThreeChar());
            }
            ThreeChar threeChar = threeChars.peek();
            switch (i) {
                case 0:
                    threeChar.u = sb.charAt(i);
                    break;
                case 3:
                    threeChar.range = Ranges.THOUSANDS;
                    threeChar.u = sb.charAt(i);
                    break;
                case 6:
                    threeChar.range = Ranges.MILLIONS;
                    threeChar.u = sb.charAt(i);
                    break;
                case 9:
                    threeChar.range = Ranges.BILLIONS;
                    threeChar.u = sb.charAt(i);
                    break;
                case 2:
                case 5:
                case 8:
                    threeChar.h = sb.charAt(i);
                    break;
                default:
                    threeChar.d = sb.charAt(i);
            }
        }
        StringBuilder result = new StringBuilder();
        while (!threeChars.isEmpty()) {
            ThreeChar thch = threeChars.pop();
            if(thch.h == '0' && thch.d == '0' && thch.u == '0' && !threeChars.isEmpty()) continue;
            if (thch.h > 0) {
                result.append(getHundreds(thch.h));
                result.append(' ');
            }
            if (thch.d > '0') {
                if (thch.d > '1' || (thch.d == '1' && thch.u == '0')) {
                    result.append(getDecades(thch.d));
                } else if (thch.d > '0') {
                    result.append(getTeens(thch.d));
                }
                result.append(' ');
            }
            if (thch.u > '0' && thch.d != '1') {
                result.append(getUnits(thch.u, thch.range == Ranges.THOUSANDS));
                result.append(' ');
            }
            switch (thch.range) {
                case BILLIONS:
                    if (thch.d == '1' || thch.u == '0') {
                        result.append("миллиардов");
                    } else if (thch.u > '4') {
                        result.append("миллиардов");
                    } else if (thch.u > '1') {
                        result.append("миллиарда");
                    } else {
                        result.append("миллиард");
                    }
                    break;
                case MILLIONS:
                    if (thch.d == '1' || thch.u == '0') {
                        result.append("миллионов");
                    } else if (thch.u > '4') {
                        result.append("миллионов");
                    } else if (thch.u > '1') {
                        result.append("миллиона");
                    } else {
                        result.append("миллион");
                    }
                    break;
                case THOUSANDS:
                    if (thch.d == '1' || thch.u == '0') {
                        result.append("тысяч");
                    } else if (thch.u > '4') {
                        result.append("тысяч");
                    } else if (thch.u > '1') {
                        result.append("тысячи");
                    } else {
                        result.append("тысяча");
                    }
                    break;
                default:
                    if (thch.d == '1' || thch.u == '0' || thch.u > '4') {
                        result.append("рублей");
                    } else if (thch.u > '1') {
                        result.append("рубля");
                    } else {
                        result.append("рубль");
                    }
            }
            result.append(' ');
        }
        result.append(sa[1] + ' ');
        switch (sa[1].charAt(1)) {
            case '1':
                result.append(sa[1].charAt(0) != '1' ? "копейка" : "копеек");
                break;
            case '2':
            case '3':
            case '4':
                result.append(sa[1].charAt(0) != '1' ? "копейки" : "копеек");
                break;
            default:
                result.append("копеек");
        }
        char first = Character.toUpperCase(result.charAt(0));
        result.setCharAt(0, first);
        return result.toString().replaceAll("null", "");
    }

    private static String getHundreds(char dig) {
        switch (dig) {
            case '1':
                return "сто";
            case '2':
                return "двести";
            case '3':
                return "триста";
            case '4':
                return "четыреста";
            case '5':
                return "пятьсот";
            case '6':
                return "шестсот";
            case '7':
                return "семсот";
            case '8':
                return "восемсот";
            case '9':
                return "девятьсот";
            default:
                return null;
        }
    }

    private static String getDecades(char dig) {
        switch (dig) {
            case '1':
                return "десять";
            case '2':
                return "двадцать";
            case '3':
                return "тридцать";
            case '4':
                return "сорок";
            case '5':
                return "пятьдесят";
            case '6':
                return "шестьдесят";
            case '7':
                return "семьдесят";
            case '8':
                return "восемьдесят";
            case '9':
                return "девяносто";
            default:
                return null;
        }
    }

    private static String getUnits(char dig, boolean female) {
        switch (dig) {
            case '1':
                return female ? "одна" : "один";
            case '2':
                return female ? "две" : "два";
            case '3':
                return "три";
            case '4':
                return "четыре";
            case '5':
                return "пять";
            case '6':
                return "шесть";
            case '7':
                return "семь";
            case '8':
                return "восемь";
            case '9':
                return "девять";
            default:
                return null;
        }
    }

    private static String getTeens(char dig) {
        String s = "";
        switch (dig) {
            case '1':
                s = "один";
                break;
            case '2':
                s = "две";
                break;
            case '3':
                s = "три";
                break;
            case '4':
                s = "четыр";
                break;
            case '5':
                s = "пят";
                break;
            case '6':
                s = "шест";
                break;
            case '7':
                s = "сем";
                break;
            case '8':
                s = "восем";
                break;
            case '9':
                s = "девят";
                break;
        }
        return s + "надцать";
    }

    public static void main(String[] args) {
        System.out.println(RussianMoney.digits2text(new Double(args[0])));
    }
}



Есть, конечно, вероятность рано или поздно встретить эту программу на известном сайте, но вроде не самый ужасный код, кому-то может и пригодится.

Комментарии (1)

RSS свернуть / развернуть
+
0
Вполне неплохой код. Все понятно и просто. За что ему на говнокод попадать? ))
avatar

Sergei_T

  • 04 апреля 2012, 21:02

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.