PHP — получение суммы прописью

    Здравствуйте!

    Знаю что эта тема уже поднималась, но предложенный вариант, также как и функция которой я пользовался до этого, оставляли чувство что задачу можно решить проще и элегантнее. Поэтому я сел, проанализировал задачу и написал еще один велосипед вариант. В результате у меня получилось две функции. Текстовое представление суммы подходит для использования в различных накладных, платежках, счетах фактурах и других платежных документах.

    Пример использования:
    num2str(878867.15); // восемьсот семьдесят восемь тысяч восемьсот шестьдесят семь рублей 15 копеек
    

    Далее сам код…

    /**
     * Возвращает сумму прописью
     * @author runcore
     * @uses morph(...)
     */
    function num2str($num) {
    	$nul='ноль';
    	$ten=array(
    		array('','один','два','три','четыре','пять','шесть','семь', 'восемь','девять'),
    		array('','одна','две','три','четыре','пять','шесть','семь', 'восемь','девять'),
    	);
    	$a20=array('десять','одиннадцать','двенадцать','тринадцать','четырнадцать' ,'пятнадцать','шестнадцать','семнадцать','восемнадцать','девятнадцать');
    	$tens=array(2=>'двадцать','тридцать','сорок','пятьдесят','шестьдесят','семьдесят' ,'восемьдесят','девяносто');
    	$hundred=array('','сто','двести','триста','четыреста','пятьсот','шестьсот', 'семьсот','восемьсот','девятьсот');
    	$unit=array( // Units
    		array('копейка' ,'копейки' ,'копеек',	 1),
    		array('рубль'   ,'рубля'   ,'рублей'    ,0),
    		array('тысяча'  ,'тысячи'  ,'тысяч'     ,1),
    		array('миллион' ,'миллиона','миллионов' ,0),
    		array('миллиард','милиарда','миллиардов',0),
    	);
    	//
    	list($rub,$kop) = explode('.',sprintf("%015.2f", floatval($num)));
    	$out = array();
    	if (intval($rub)>0) {
    		foreach(str_split($rub,3) as $uk=>$v) { // by 3 symbols
    			if (!intval($v)) continue;
    			$uk = sizeof($unit)-$uk-1; // unit key
    			$gender = $unit[$uk][3];
    			list($i1,$i2,$i3) = array_map('intval',str_split($v,1));
    			// mega-logic
    			$out[] = $hundred[$i1]; # 1xx-9xx
    			if ($i2>1) $out[]= $tens[$i2].' '.$ten[$gender][$i3]; # 20-99
    			else $out[]= $i2>0 ? $a20[$i3] : $ten[$gender][$i3]; # 10-19 | 1-9
    			// units without rub & kop
    			if ($uk>1) $out[]= morph($v,$unit[$uk][0],$unit[$uk][1],$unit[$uk][2]);
    		} //foreach
    	}
    	else $out[] = $nul;
    	$out[] = morph(intval($rub), $unit[1][0],$unit[1][1],$unit[1][2]); // rub
    	$out[] = $kop.' '.morph($kop,$unit[0][0],$unit[0][1],$unit[0][2]); // kop
    	return trim(preg_replace('/ {2,}/', ' ', join(' ',$out)));
    }
    
    /**
     * Склоняем словоформу
     * @ author runcore
     */
    function morph($n, $f1, $f2, $f5) {
    	$n = abs(intval($n)) % 100;
    	if ($n>10 && $n<20) return $f5;
    	$n = $n % 10;
    	if ($n>1 && $n<5) return $f2;
    	if ($n==1) return $f1;
    	return $f5;
    }
    


    Upd1: исправил оформление + небольшой рефакторинг функции
    Upd2: глубокий рефакторинг, позволивший сильно укоротить/упростить алгоритм
    Метки:
    Поделиться публикацией
    Комментарии 27
    • –3
      У Лебедева в его интернет-магазине такой надо поставить :)
      Интернет-магазинус® с уникальной фичей: Кассус®
      • +3
        Сложно все выглядит, без комментариев тем более.
        >$s[$b[$kk][3]][$tmp[$i]]
        убиваем мозг
        • 0
          понял. добавлю комментарии
          • +1
            И пожалуйста подсветку синтаксиса, спасибо
        • +1
          Биллион? По-моему мы в России живём.
          • +1
            я делал для Американской системы) щас исправлю
            • –1
              Рухнула эта система! Нет её больше!
              • НЛО прилетело и опубликовало эту надпись здесь
            • +2
              Критика: за такие названия переменных (всех, кроме $offset и $n) вам в средние века поотрывали бы руки.
              • +1
                будем считать это обфускацией )
                но адекватных названий я придумать не смог — для временных переменных с индексами и флагами
                • 0
                  А зачем обфускать алгоритм которым мнээ гордимся :)
                • 0
                  ЗЫ. Тут дело даже не в умении обзывать всё своими именами, а в неумении программировать метафорически. Не комменты здесь нужны, а такой алгоритм, где не используются переменные, которые нельзя охарактеризовать (за исключением $i). Это если говорить о красивом коде, а если о функциональном, то браво, наверное это работает, если ничего не трогать.
                  • 0
                    вы правы конечно.
                    просто в процессе написания алгоритма, с короткими переменными было проще, так как постоянно его менял и рефакторил) а в итоговом не додумался исправить
                    обновил код. теперь более длинные и осмысленные названия )
                • –2
                  Для ценников это точно неудобно, особенно с ценами больше тысячи рублей. Цифры воспринимаются проще.
                  • +1
                    функция конечно не для ценников, а для использования при печати финансовых документов, платежек и т.д.
                    там как раз требуется суммы писать прописью
                  • 0
                    мой велосипед на питоне (написанный 1.5 года назад)
                    python.su/forum/viewtopic.php?pid=6177#p6160
                    • 0
                      да. на питоне красивше получается)
                    • –1
                      >«восемьсот семьдесят восемь тысяч восемьсот шестьдесят семь рублей пятнадцать копеек»
                      с запятыми не лучше будет?
                      • 0
                        в финансовых документах — недопускается
                        • 0
                          да я его видел. но та реализация не очень понравилась.
                          это и натолкнуло на создание еще одного велосипеда )
                        • +1
                          Нужно у себя где-нибудь тоже сделать переменную $sex
                          • 0
                            да — код становится чуть веселее )
                          • 0
                            $out в начале используется, а потом пропадает. Как я понимаю в место нее потом используется $o. Наверное это последствия неудачного рефакторинга.
                            В конце функции в регулярном выражении само выражение заключено в двойные кавычки.
                            \s — забекслешить можно или поставить одинарные кавычки, чтобы не вводить никого в заблуждение.

                            • 0
                              Искал такую функцию на JavaScript, ничего путёвого не нашлось. Решил портировать на CoffeeScript :)
                              Старался чтобы получилось 1 к 1, единственное чего не понял как сделать это sprintf, поэтому пришлось руками дописывать нули, в остальном же легко можно видеть как соотносятся строчки в php и cs.
                              файл на гитхабе: bit.ly/Nz3wKm
                              • 0
                                я тоже делал порт на JS. но у вас компактнее получилось )
                              • 0
                                runcore Большое спасибо!
                                +1 вам к карме

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