Черновики
quittance.ru
документы
прочее
Авторские дневники
   / черновики
   / заметки о типографике
   / SEO FAQ для неспециалистов

Сумма прописью  еще одна реализация на php

В про­цес­се раз­ра­бот­ки он­лайн сер­ви­са за­пол­не­ния и пе­ча­ти пла­теж­но­го по­ру­че­ния есте­ствен­ным об­ра­зом по­тре­бо­ва­лось ре­шить клас­си­че­скую за­да­чу «сум­ма про­пи­сью».

Широ­ко из­вест­ная на тот мо­мент php-ре­а­ли­за­ция «сум­мы про­пи­сью» по­ка­за­лась неубе­ди­тель­ной, по­это­му за­да­ча бы­ла ре­ше­на в оче­ред­ной раз по-сво­е­му. Это ре­ше­ние здесь и пуб­ли­ку­ется.

На вход мож­но по­да­вать как чис­ло, так и стро­ку. В ка­че­стве раз­де­ли­те­ля руб­лей и ко­пе­ек мож­но ис­поль­зо­вать то­чу, за­пя­тую, ми­нус (де­фис) или знак ра­вен­ства.

Преду­смот­ре­но три фла­га, ко­то­рые мож­но за­дать в необя­за­тель­ном вто­ром па­ра­метре:

  • вы­во­дить ко­пей­ки циф­ра­ми (по умол­ча­нию  про­пи­сью);
  • обя­за­тель­но вы­во­дить нуле­вые ко­пей­ки (по умол­ча­нию не вы­во­дятся);
  • ис­поль­зо­вать со­кра­ще­ние «коп.» (по умол­ча­нию пол­но­стью: «ко­пей­ка», «ко­пей­ки», «ко­пеек»).

Общий ход ал­го­рит­ма: внеш­ний цикл про­хо­дит по груп­пам сле­ва на­пра­во от трил­ли­о­нов до руб­лей и ко­пе­ек. Внут­рен­ний цикл раз­би­ра­ет циф­ры внут­ри групп сле­ва на­пра­во от со­тен до еди­ниц.

Исклю­че­ния из об­ще­го хо­да ал­го­ритма:

  • чис­ла от …10 до …19 об­ра­ба­ты­ва­ют­ся особо;
  • груп­па ко­пе­ек со­сто­ит из двух цифр, осталь­ные  из трех;
  • нуле­вые груп­пы не име­ну­ют­ся, за ис­клю­че­ни­ем груп­пы руб­лей (а так­же и груп­пы ко­пе­ек, в слу­чае ко­гда спе­ци­аль­ным фла­гом за­да­но обя­за­тель­ное упо­ми­на­ние ко­пеек);
  • ко­пей­ки и ты­ся­чи жен­ско­го ро­да («од­на ко­пей­ка», «две ты­ся­чи»); осталь­ные груп­пы муж­ско­го ро­да («один рубль», «два мил­ли­она»).

<?php
 
// Convert digital Russian currency representation
// (Russian rubles and copecks) to the verbal one
// Copyright 2008 Sergey Kurakin
// Licensed under LGPL version 3 or later
 
define('M2S_KOPS_DIGITS', 0x01); // digital copecks
define('M2S_KOPS_MANDATORY', 0x02); // mandatory copecks
define('M2S_KOPS_SHORT', 0x04); // shorten copecks
 
function money2str_ru($money, $options = 0) {
 
$money = preg_replace('/[\,\-\=]/', '.', $money);
 
$numbers_m = array('', 'один', 'два', 'три', 'четыре', 'пять', 'шесть', 'семь',
'восемь', 'девять', 'десять', 'одиннадцать', 'двенадцать', 'тринадцать',
'четырнадцать', 'пятнадцать', 'шестнадцать', 'семнадцать', 'восемнадцать',
'девятнадцать', 'двадцать', 30 => 'тридцать', 40 => 'сорок', 50 => 'пятьдесят',
60 => 'шестьдесят', 70 => 'семьдесят', 80 => 'восемьдесят', 90 => 'девяносто',
100 => 'сто', 200 => 'двести', 300 => 'триста', 400 => 'четыреста',
500 => 'пятьсот', 600 => 'шестьсот', 700 => 'семьсот', 800 => 'восемьсот',
900 => 'девятьсот');
 
$numbers_f = array('', 'одна', 'две');
 
$units_ru = array(
(($options & M2S_KOPS_SHORT)
? array('коп.', 'коп.', 'коп.')
: array('копейка', 'копейки', 'копеек')),
array('рубль', 'рубля', 'рублей'),
array('тысяча', 'тысячи', 'тысяч'),
array('миллион', 'миллиона', 'миллионов'),
array('миллиард', 'миллиарда', 'миллиардов'),
array('триллион', 'триллиона', 'триллионов'),
);
 
$ret = '';
 
// enumerating digit groups from left to right, from trillions to copecks
// $i == 0 means we deal with copecks, $i == 1 for roubles,
// $i == 2 for thousands etc.
for ($i = sizeof($units_ru) - 1; $i >= 0; $i--) {
 
// each group contais 3 digits, except copecks, containing of 2 digits
$grp = ($i != 0) ? dec_digits_group($money, $i - 1, 3) :
dec_digits_group($money, -1, 2);
 
// process the group if not empty
if ($grp != 0) {
 
// digital copecks
if ($i == 0 && ($options & M2S_KOPS_DIGITS)) {
$ret .= sprintf('%02d', $grp). ' ';
$dig = $grp;
 
// the main case
} else for ($j = 2; $j >= 0; $j--) {
$dig = dec_digits_group($grp, $j);
if ($dig != 0) {
 
// 10 to 19 is a special case
if ($j == 1 && $dig == 1) {
$dig = dec_digits_group($grp, 0, 2);
$ret .= $numbers_m[$dig]. ' ';
break;
}
 
// thousands and copecks are Feminine gender in Russian
elseif (($i == 2 || $i == 0) && $j == 0 && ($dig == 1 || $dig == 2))
$ret .= $numbers_f[$dig]. ' ';
 
// the main case
else $ret .= $numbers_m[(int) ($dig * pow(10, $j))]. ' ';
}
}
$ret .= $units_ru[$i][sk_plural_form($dig)]. ' ';
}
 
// roubles should be named in case of empty roubles group too
elseif ($i == 1 && $ret != '')
$ret .= $units_ru[1][2]. ' ';
 
// mandatory copecks
elseif ($i == 0 && ($options & M2S_KOPS_MANDATORY))
$ret .= (($options & M2S_KOPS_DIGITS) ? '00' : 'ноль').
' '. $units_ru[0][2];
}
 
return trim($ret);
}
 
// service function to select the group of digits
function dec_digits_group($number, $power, $digits = 1) {
return (int) bcmod(bcdiv($number, bcpow(10, $power * $digits, 8)),
bcpow(10, $digits, 8));
}
 
// service function to get plural form for the number
function sk_plural_form($d) {
$d = $d % 100;
if ($d > 20) $d = $d % 10;
if ($d == 1) return 0;
elseif ($d > 0 && $d < 5) return 1;
else return 2;
}
 
?>

N.B. Для раз­бо­ра сум­мы на груп­пы и групп на циф­ры ис­поль­зу­ет­ся рас­ши­ре­ние php BCMath, ре­а­ли­зу­ю­щее ариф­ме­ти­ку про­из­воль­ной точ­но­сти. Встро­ен­ной ариф­ме­ти­ки php ока­за­лось недо­ста­точ­но для вы­пол­не­ния по­став­лен­ной за­да­чи (оши­ба­ет­ся в ко­пей­ках). Рас­ши­ре­ние BCMath объ­яв­ле­но стан­дарт­ным с вер­сии php 4.0.4, по­это­му про­блем с его до­ступ­но­стью дав­но не воз­ни­кает.

P.S. Недав­но встре­ти­лась еще од­на ре­а­ли­за­ция «по­лу­че­ния сум­мы про­пи­сью», немно­го схо­жая с мо­ею. В ней вы­де­ле­ние групп и цифр про­из­во­дит­ся не ма­те­ма­ти­че­ски, а пу­тём раз­бо­ра сим­воль­ной стро­ки с пред­ва­ри­тель­ной её нор­ма­ли­за­ци­ей. Впро­чем, это де­ло вкуса…

комментировать 12/10/2010
Copyright 2009–2010 Sergey Kurakin