БлогNot. PHP: число прописью по-русски

PHP: число прописью по-русски

Классическая задача, которую программисты всё решают и решают (1234 -> "одна тысяча двести тридцать четыре" и т.п.). Правда, для русского языка нужно делать всё несколько нетривиально:

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

Функция numberToRussian, показанная ниже, пытается всё это учесть.

Целое число на входе функции может быть как положительным, так и отрицательным. Названия выводятся по принятой в России короткой шкале именования чисел. Чтобы не зависеть от максимально допустимого в вашей системе целого значения PHP_INT_MAX, лучше передавать в функцию строковую запись числа, тогда можно получить прописи чисел вплоть до 10 в 33 степени. Числа ещё больше обозначены только что изобретённой мною константой "дофигальон" :) Ну или возьмите нужные для массива $degrees названия степеней "десятки" по последней ссылке, там до 10 в 99 степени.

Оболочку-сервис для функции писать лень, подобных кодов в блоге уже много.

Все тесты видны внизу листинга, а вот и он сам:

<!DOCTYPE html>
<html dir="ltr" lang="ru-RU">
<head>
 <meta charset="windows-1251">
 <title>Число прописью по-русски</title>
</head>
<body>

<?php
function numberToRussian ($sourceNumber){ 
 //Целое значение $sourceNumber вывести прописью по-русски
 //Максимальное значение для аругмента-числа PHP_INT_MAX
 //Максимальное значение для аругмента-строки минус/плюс 999999999999999999999999999999999999
 $smallNumbers=array( //Числа 0..999
  array('ноль'),
  array('','один','два','три','четыре','пять','шесть','семь','восемь','девять'),
  array('десять','одиннадцать','двенадцать','тринадцать','четырнадцать',
        'пятнадцать','шестнадцать','семнадцать','восемнадцать','девятнадцать'),
  array('','','двадцать','тридцать','сорок','пятьдесят','шестьдесят','семьдесят','восемьдесят','девяносто'),
  array('','сто','двести','триста','четыреста','пятьсот','шестьсот','семьсот','восемьсот','девятьсот'),
  array('','одна','две')
 );
 $degrees=array(
  array('дофигальон','','а','ов'), //обозначение для степеней больше, чем в списке
  array('тысяч','а','и',''), //10^3
  array('миллион','','а','ов'), //10^6
  array('миллиард','','а','ов'), //10^9
  array('триллион','','а','ов'), //10^12
  array('квадриллион','','а','ов'), //10^15
  array('квинтиллион','','а','ов'), //10^18
  array('секстиллион','','а','ов'), //10^21
  array('септиллион','','а','ов'), //10^24
  array('октиллион','','а','ов'), //10^27
  array('нониллион','','а','ов'), //10^30
  array('дециллион','','а','ов') //10^33
  //досюда написано в Вики по нашей короткой шкале: https://ru.wikipedia.org/wiki/Именные_названия_степеней_тысячи
 );
 
 if ($sourceNumber==0) return $smallNumbers[0][0]; //Вернуть ноль
 $sign = '';
 if ($sourceNumber<0) {
  $sign = 'минус '; //Запомнить знак, если минус
  $sourceNumber = substr ($sourceNumber,1);
 }
 $result=array(); //Массив с результатом

 //Разложение строки на тройки цифр
 $digitGroups = array_reverse(str_split(str_pad($sourceNumber,ceil(strlen($sourceNumber)/3)*3,'0',STR_PAD_LEFT),3));
 foreach($digitGroups as $key=>$value){
  $result[$key]=array();
  //Преобразование трёхзначного числа прописью по-русски
  foreach ($digit=str_split($value) as $key3=>$value3) {
   if (!$value3) continue;
   else {
    switch ($key3) {
     case 0: 
      $result[$key][] = $smallNumbers[4][$value3]; 
      break;
     case 1: 
      if ($value3==1) {
       $result[$key][]=$smallNumbers[2][$digit[2]];
       break 2;
      }
      else $result[$key][]=$smallNumbers[3][$value3];
     break;
     case 2:
      if (($key==1)&&($value3<=2)) $result[$key][]=$smallNumbers[5][$value3];
      else $result[$key][]=$smallNumbers[1][$value3];
     break;
    }
   }
  }
  $value*=1;
  if (!$degrees[$key]) $degrees[$key]=reset($degrees);
  
  //Учесть окончание слов для русского языка
  if ($value && $key) {
   $index = 3;
   if (preg_match("/^[1]$|^\\d*[0,2-9][1]$/",$value)) $index = 1; //*1, но не *11
   else if (preg_match("/^[2-4]$|\\d*[0,2-9][2-4]$/",$value)) $index = 2; //*2-*4, но не *12-*14
   $result[$key][]=$degrees[$key][0].$degrees[$key][$index];
  }
  $result[$key]=implode(' ',$result[$key]);
 }
 
 return $sign.implode(' ',array_reverse($result));
}

$testValues = array ( //Тестовые значения
 -11111112,                                //ясно, что работает
 PHP_INT_MAX,                              //ещё сработает как С ЧИСЛОМ
 9223372036854775807,                      //как с ЧИСЛОМ уже неверно - 
                                           //см. http://ru2.php.net/manual/ru/language.types.integer.php
 '9223372036854775807',                    //а со СТРОКОЙ всё правильно
 "-999999999999999999999999999999999999",  //ещё корректно
 "1000000000000000000000000000000000000"   //уже дофигальон
);
foreach ($testValues as $value) {
 echo '<p>Значение=<b>'.$value.
      '</b>, результат преобразования=<b>'.numberToRussian($value).'</b></p>';
}
?>

</body>
</html>

Проверено на локалхосте "Денвер" в 64-битной системе со старым добрым PHP 5.3.13, на выдаче вот что (с точностью до разметки):


Значение=-11111112, результат преобразования=минус одиннадцать миллионов сто одиннадцать тысяч сто двенадцать
Значение=2147483647, результат преобразования=два миллиарда сто сорок семь миллионов четыреста восемьдесят три тысячи шестьсот сорок семь
Значение=9.2233720368548E+18, результат преобразования=девять квинтиллионов двадцать два квадриллиона триста тридцать семь триллионов двести три миллиарда шестьсот восемьдесят пять миллионов четыреста восемьдесят тысяч восемнадцать
Значение=9223372036854775807, результат преобразования=девять квинтиллионов двести двадцать три квадриллиона триста семьдесят два триллиона тридцать шесть миллиардов восемьсот пятьдесят четыре миллиона семьсот семьдесят пять тысяч восемьсот семь
Значение=-999999999999999999999999999999999999, результат преобразования=минус девятьсот девяносто девять дециллионов девятьсот девяносто девять нониллионов девятьсот девяносто девять октиллионов девятьсот девяносто девять септиллионов девятьсот девяносто девять секстиллионов девятьсот девяносто девять квинтиллионов девятьсот девяносто девять квадриллионов девятьсот девяносто девять триллионов девятьсот девяносто девять миллиардов девятьсот девяносто девять миллионов девятьсот девяносто девять тысяч девятьсот девяносто девять
Значение=1000000000000000000000000000000000000, результат преобразования=один дофигальон

Обратите внимание на комментарии в листинге, почему "не вышло" с числом 9223372036854775807.

Допустимыми числами на входе функции являются, вообще говоря, строки шаблона

^\-?\d{1,36}$

то есть, необязательный знак "-" в первой позиции плюс не более 36 десятичных цифр.

14.10.2016, 15:42 [11899 просмотров]


теги: php числа алгоритм

К этой статье пока нет комментариев, Ваш будет первым