webew
Войти » Регистрация
 
PHP

Число прописью средствами PHP

1 мая 2008, 21:35

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

В русском языке существительное, употребляемое вместе с числительным, имеет три формы. Рассмотрим пример:

  • один друг
  • два друга
  • три друга
  • четыре друга
  • пять друзей
  • ...
  • сто один друг.

Мы видим, что три различных формы существительного отвечают числительным 1,2 и 5. Кроме того, числительные для существительных женского рода другие, см. пример ниже:

  • одна подруга
  • две подруги
  • три подруги
  • четыре подруги
  • пять подруг
  • ...
  • сто одна подруга.

Женский род числительных требуется даже для описания существительных мужского рода, так как слово «тысяча» — слово женского рода.

Функция written_number(), приведенная ниже, имеет два аргумента. Первый аргумент — целое неотрицательное число (меньше миллиарда), второй — род (0 - мужской, 1 женский).

<?php
global $N0, $Ne0, $Ne1, $Ne2, $Ne3, $Ne6;

$N0 = 'ноль';

$Ne0 = array(
             0 => array('','один','два','три','четыре','пять','шесть',
                        'семь','восемь','девять','десять','одиннадцать',
                        'двенадцать','тринадцать','четырнадцать','пятнадцать',
                        'шестнадцать','семнадцать','восемнадцать','девятнадцать'),
             1 => array('','одна','две','три','четыре','пять','шесть',
                        'семь','восемь','девять','десять','одиннадцать',
                        'двенадцать','тринадцать','четырнадцать','пятнадцать',
                        'шестнадцать','семнадцать','восемнадцать','девятнадцать')
             );

$Ne1 = array('','десять','двадцать','тридцать','сорок','пятьдесят',
             'шестьдесят','семьдесят','восемьдесят','девяносто');

$Ne2 = array('','сто','двести','триста','четыреста','пятьсот',
             'шестьсот','семьсот','восемьсот','девятьсот');

$Ne3 = array(1 => 'тысяча', 2 => 'тысячи', 5 => 'тысяч');

$Ne6 = array(1 => 'миллион', 2 => 'миллиона', 5 => 'миллионов');

function written_number($i, $female=false) {
  global $N0;
  if ( ($i<0) || ($i>=1e9) || !is_int($i) ) {
    return false; // Аргумент должен быть неотрицательным целым числом, не превышающим 1 миллион
  }
  if($i==0) {
    return $N0;
  }
  else {
    return preg_replace( array('/s+/','/\s$/'),
                         array(' ',''),
                         num1e9($i, $female));
    return num1e9($i, $female);
  }
}

function num_125($n) {
  /* форма склонения слова, существительное с числительным склоняется
   одним из трех способов: 1 миллион, 2 миллиона, 5 миллионов */

  $n100 = $n % 100;
  $n10 = $n % 10;
  if( ($n100 > 10) && ($n100 < 20) ) {
    return 5;
  }
  elseif( $n10 == 1) {
    return 1;
  }
  elseif( ($n10 >= 2) && ($n10 <= 4) ) {
    return 2;
  }
  else {
    return 5;
  }
}

function num1e9($i, $female) {
  global $Ne6;
  if($i<1e6) {
    return num1e6($i, $female);
  }
  else {
    return num1000(intval($i/1e6), false) . ' ' .
      $Ne6[num_125(intval($i/1e6))] . ' ' . num1e6($i%1e6, $female);
  }
}

function num1e6($i, $female) {
  global $Ne3;
  if($i<1000) {
    return num1000($i, $female);
  }
  else {
    return num1000(intval($i/1000), true) . ' ' .
      $Ne3[num_125(intval($i/1000))] . ' ' . num1000($i%1000, $female);
  }
}

function num1000($i, $female) {
  global $Ne2;
  if( $i<100) {
    return num100($i, $female);
  }
  else {
    return $Ne2[intval($i/100)] . (($i%100)?(' '. num100($i%100, $female)):'');
  }
}

function num100($i, $female) {
  global $Ne0, $Ne1;
  $gender = $female?1:0;
  if ($i<20) {
    return $Ne0[$gender][$i];
  }
  else {
    return $Ne1[intval($i/10)] . (($i%10)?(' ' . $Ne0[$gender][$i%10]):'');
  }
}

?>

Рассмотрим примеры использования функции written_number():

$ruble = array(1 => 'рубль', 2 => 'рубля', 5 => 'рублей');
$sum = 21802;
echo 'Всего оказано услуг на сумму: '
    .  written_number($sum) . ' ' . $ruble[num_125($sum)] . ' 00 коп.';
$friendm = array(1 => 'друг', 2 => 'друга', 5 => 'друзей');
$friendf = array(1 => 'подруга', 2 => 'подруги', 5 => 'подруг');
$m_count = 11;
$f_count = 21;
echo 'У пользователя ' . written_number($m_count) . ' ' . $friendm[num_125($m_count)]
    . ' и ' . written_number($f_count, true) . ' ' . $friendf[num_125($f_count)] . '.';

Статья написана по материалам онлайн-курса «Программирование на PHP».


© Все права на данную статью принадлежат порталу webew.ru. Перепечатка в интернет-изданиях разрешается только с указанием автора и прямой ссылки на оригинальную статью. Перепечатка в печатных изданиях допускается только с разрешения редакции.
Добавить комментарий
Отображение комментариев: Древовидное | Плоское

bur

Наверное, ошибка:

$friendm = array(1 => 'друг', 2 => 'друга', 5 => 'друзей');
$friendf = array(1 => 'подруга', 2 => 'подруги', 5 => 'подруг');
$m_count = 11;
$f_count = 1000;
echo 'У пользователя ' . written_number($m_count) . ' ' . $friendm[num_125($m_count)]
    . ' и ' . written_number($f_count, true) . ' ' . $friendf[num_125($f_count)] . '.' . '<br>';



Выведет:
У пользователя одиннадцать друзей и одна тысяча ноль подруг
05.05.2008, 00:51
Ответить
NO USERPIC

rgbeast

Спасибо за багу, исправил.
05.05.2008, 01:04
Ответить

bur

А не лучше ли, в случае неверного типа входных данных, вместо die возвращать переданный аргумент? Мало ли, вдруг там число больше миллиона. Тогда правильней вернуть его, а не обрушивать весь скрипт. Ведь die довольно мощная функция. Она не только репортит ошибку, но и прекращает дальнейшую интерпритацию всего скрипта.
05.05.2008, 02:08
Ответить
NO USERPIC

rgbeast

die() в функции written_number() стояла скорее как маркер, чем как функциональная возможность, заменил die() на return false. Скрипт, вызывающий функцию, должен теперь сам обработать ошибку. Конкретная реализация обработки ошибки будет зависеть от логики приложения.
05.05.2008, 02:51
Ответить
NO USERPIC

remitmaster

В избранное... спасибо!
22.06.2008, 16:15
Ответить
NO USERPIC

doron

Огромное спс
02.01.2009, 13:33
Ответить
NO USERPIC

altsoph

В свое время решал эту задачу в более широком варианте.

Основные плюсы моего решения:
* реализация в виде отдельной библиотеки
* поддержка 6 русских падежей
* набор готовых перечислимых объектов (штуки, рубли, копейки, центы и т. п.) + возможность передать библиотеке произвольный перечислимый объект
* обработка и склонение десятичных дробей (до 6 знаков после запятой)

Более подробное описание, возможность поиграть с примерами и скачать код тут:
http://altsoph.ru/?p=522
24.02.2009, 13:43
Ответить
NO USERPIC

avs

Отличный скрипт. Вопрос, можно ли сделать так, чтобы первое слово начиналось с заглавной буквы? Например, число 123, написать так:"Сто двадцать три"
04.05.2012, 23:01
Ответить
NO USERPIC

rgbeast

Такое преобразование всегда можно выполнить над результатом, используя функцию PHP ucfirst()
05.05.2012, 13:10
Ответить
NO USERPIC

avs

Спасибо получилось, но столкнулся с очередной проблемой.
При округлении числа, скрипт не работает, например:
есть переменные
$a=200;
$b=3;
$c=round($a/$b);
при выводе переменной $c получим 67, но скрипт отказывается писать его прописью, хотя число целое.
06.05.2012, 20:37
Ответить
NO USERPIC

rgbeast

round() возвращает не целое число, а float с нулями после запятой (см. http://ru.php.net/round ). Используйте intval(round($a/$b));
06.05.2012, 21:13
Ответить

1234ru

С другой стороны, почему бы не модифицировать функцию так, чтобы она работала и с дробными числами?
Ведь говорят же, например "двадцать одна и девять десятых секунды" - т.е. надо брать intval(floor(число)).

Следует отметить, что существительное в случае дробного числа всегда будет в единственном числе и родительном падеже (т.к. десятых/сотых/тысячных/и т.п. чего? секунды).
То, что не убивает нас, делает нас инвалидами.
07.05.2012, 14:17
Ответить
NO USERPIC

rgbeast

Наверное, имеет смысл написать дополнительную фунцию written_float(), которая будет использовать written_number()
07.05.2012, 14:48
Ответить

1234ru

Существуют ли условия, в которых written_number() не должна работать с дробным числом?
То, что не убивает нас, делает нас инвалидами.
07.05.2012, 16:05
Ответить
NO USERPIC

rgbeast

written_number() пока что очень простая функция. И, в основном, она нужна для целых чисел - в бухгалтерии пишут "Двадцать два рубля 00 коп" и никогда не превращают доли в текст. Функция для дробных будет очень сложной и, возможно, неоднозначной в зависимости от применения. Как, например, написать 3.1415926, 2.5e-19, 4.88e22, 0.333333333333333 или 0.999999999999999?

Числа с плавающей точкой - отдельный мир, со своими правилами. Поэтому логично, что одни функцию для целых, другие - для float.
07.05.2012, 16:26
Ответить

1234ru

Я имел в виду превращение в текст только целой части.
Впрочем, это действительно неполное решение задачи (возможно, будет ожидаться превращение в текст и дробной части тоже), так что для ясности пусть действительно перед использованием функции собственноручно превращают число в целое.
То, что не убивает нас, делает нас инвалидами.
07.05.2012, 16:44
Ответить

1234ru

Альтернативный вариант:

function get_digit($number, $digit) {
    # Получение разряда числа
    $up = pow(10, $digit);
    $down = pow(10, $digit - 1);
   
    return ($number >= $down)
             ? floor( ($number % $up) / $down )
             : 0 ;
}

function number_written_alt($number, $words, $gender = 'female') {
    # Возвращает число прописью в именительном падеже
    # (используется для товарных чеков).
    # $gender: female|male|middle   
   
    if (!is_array($words))
        $words = explode(',', $words);
   
    $str = '';
   
    $names = array(
            1 => 'тысяча,тысячи,тысяч',
            'миллион,миллиона,миллионов',
            'миллиард,миллиарда,миллиардов',
            // сюда добавить по желанию
        );
   
    $F = __FUNCTION__;
   
    foreach (array_reverse($names, TRUE) as $i => $w) {
       
        $pow = pow(1000, $i);
       
        if ($number >= $pow) {
            $str .= $F(
                    floor($number/$pow),
                    $w,
                    ( ($i == 1) ? 'female' : 'male' )
                ) . ' ';
           
            $number = $number % $pow;
        }
    }
   
   
    # Сотни
   
        if ($number >= 100) {
            $hundreds = array(
                1 => 'сто',
                'двести',
                'триста',
                'четыреста',
                'пятьсот',
                'шестьсот',
                'семьсот',
                'восемьсот',
                'девятьсот'
            );
            $h = get_digit($number, 3);
            if (isset($hundreds[$h]))
                $str .= "$hundreds[$h] ";
        }
       
       
    # Десятки   
       
        $d = get_digit($number, 2);
       
        if ($d >= 2 OR $d == 0) {
            $decs = array(
                2 => 'двадцать',
                'тридцать',
                'сорок',
                'пятьдесят',
                'шестьдесят',
                'семьдесят',
                'восемьдесят',
                'девяносто'
            );
            if (isset($decs[$d]))
                $str .= "$decs[$d] ";
           
            # Единицы
           
            $u = get_digit($number, 1);
           
            if ($u > 2) {
                $units = array(
                        3 => 'три',
                        'четыре',
                        'пять',
                        'шесть',
                        'семь',
                        'восемь',
                        'девять'
                    );
                $str .= "$units[$u] "
                        . (
                              ($u > 4)
                              ? $words[2]
                              : $words[1]
                          ) ;
            }
           
            elseif ($u == 2) {
                $tmp = array(
                        'female' => 'две',
                        'male' => 'два',
                        'middle' => 'два'
                    );
                $str .= "$tmp[$gender] $words[1]";
            }
            elseif ($u == 1) {
                $tmp = array(
                        'female' => 'одна',
                        'male' => 'один',
                        'middle' => 'одно'
                    );
                $str .= "$tmp[$gender] $words[0]";
            }
            else
                $str .= $words[2]; // ноль
           
        }
        else {
           
            $sub_d = $number % 100;
           
            $tmp = array(
                    10 => 'десять',
                    'одиннадцать',
                    'двенадцать',
                    'тринадцать',
                    'четырнадцать',
                    'пятнадцать',
                    'шестнадцать',
                    'семнадцать',
                    'восемьнадцать',
                    'девятнадцать'
                );
            $str .= "$tmp[$sub_d] $words[2]";
            unset($tmp);
        }
       

   
    return $str;
}
То, что не убивает нас, делает нас инвалидами.
24.12.2012, 17:33
Ответить
NO USERPIC

jo0506

Еще похожая реализация есть тут: http://www.propisu.ru. по написанию числа прописью.
04.05.2013, 20:51
Ответить
NO USERPIC

rgbeast

У вас число прописью предоставляется через онлайн-сервис, а код библиотеки не выложен.
05.05.2013, 02:57
Ответить
Добавить комментарий
Отображение комментариев: Древовидное | Плоское
© 2008—2017 webew.ru, связаться: x собака webew.ru
Сайт использует Flede и соответствует стандартам WAI-WCAG 1.0 на уровне A.
Rambler's Top100