Склонение числительных в 1С — это выбор формы слова после числа: 1 год, 2 года, 5 лет, 11 лет, 21 год. Строго говоря, склоняется не числительное, а согласованное с ним существительное, но в поиске задачу называют так. Ниже правило выбора формы, общий модуль для любого слова, штатные ЧислоПрописью и СтрокаСЧислом, функции БСП и ошибки, из-за которых в документах появляются «11 год» и «двадцать один штука».
Код рассчитан на платформу 8.3. Общий модуль из статьи работает в любом режиме совместимости, а метод СтрокаСЧислом требует платформы 8.3.10 или новее.
Самый частый баг в таком коде — форму выбирают только по последней цифре, и в договор попадает «11 год».
У слова после целого числа три формы: как после единицы (год), как после двойки (года) и как после пятёрки (лет). Форму определяют две последние цифры числа, и проверять их нужно в таком порядке:
После дробного числа слово стоит в родительном падеже единственного числа: 0,5 года, 1,5 года, 7,5 года. На письме эта форма совпадает с формой «два», поэтому четвёртый вариант передавать не нужно.
| Число | Две последние цифры | Последняя цифра | Форма | Результат |
|---|---|---|---|---|
| 0 | 00 | 0 | пять | 0 лет |
| 1 | 01 | 1 | один | 1 год |
| 3 | 03 | 3 | два | 3 года |
| 11 | 11 | 1 | пять (исключение) | 11 лет |
| 12 | 12 | 2 | пять (исключение) | 12 лет |
| 21 | 21 | 1 | один | 21 год |
| 24 | 24 | 4 | два | 24 года |
| 111 | 11 | 1 | пять (исключение) | 111 лет |
| 122 | 22 | 2 | два | 122 года |
| 1,5 | — | — | два (дробное число) | 1,5 года |
Функции удобно держать в клиент-серверном общем модуле, чтобы вызывать их из формы, из печати и из фонового задания. Создайте общий модуль СклонениеПоЧислуКлиентСервер, включите флажки «Клиент (управляемое приложение)», «Сервер» и «Внешнее соединение» и вставьте код. Формы слова передаются одной строкой через запятую в порядке «для 1, для 2, для 5»: "год,года,лет", "день,дня,дней", "штука,штуки,штук".
// Общий модуль СклонениеПоЧислуКлиентСервер
// Флажки: Клиент (управляемое приложение), Сервер, Внешнее соединение;
// в конфигурации на обычных формах добавьте Клиент (обычное приложение).
// Работает в любом режиме совместимости: СтрРазделить и СтрШаблон не используются.
// Номер формы слова для числа:
// 1 - как после 1 (год), 2 - как после 2 (года), 3 - как после 5 (лет).
Функция НомерФормыСлова(Знач Число) Экспорт
Если Число < 0 Тогда
Число = -Число;
КонецЕсли;
// Дробные числа: 0,5 года, 1,5 года, 7,5 года
Если Число <> Цел(Число) Тогда
Возврат 2;
КонецЕсли;
// 11-14, 111-114, 211-214 - всегда "лет", какой бы ни была последняя цифра
ДвеПоследние = Число % 100;
Если ДвеПоследние >= 11 И ДвеПоследние <= 14 Тогда
Возврат 3;
КонецЕсли;
Последняя = Число % 10;
Если Последняя = 1 Тогда
Возврат 1;
ИначеЕсли Последняя >= 2 И Последняя <= 4 Тогда
Возврат 2;
КонецЕсли;
Возврат 3;
КонецФункции
// Слово в форме, согласованной с числом.
// Формы - строка "год,года,лет": формы для 1, для 2 и для 5 через запятую.
Функция ФормаСлова(Знач Число, Знач Формы) Экспорт
Варианты = ТриФормы(Формы);
Возврат Варианты[НомерФормыСлова(Число) - 1];
КонецФункции
// Число цифрами и слово: "0 дней", "21 день", "1 024 штуки".
Функция ЧислоСоСловом(Знач Число, Знач Формы) Экспорт
// Без ЧН=0 функция Формат для нуля возвращает пустую строку
Возврат Формат(Число, "ЧН=0") + " " + ФормаСлова(Число, Формы);
КонецФункции
// Число прописью и слово: "одиннадцать лет", "двадцать одна штука".
// Род - "м", "ж" или "с": один год, одна штука, одно место.
Функция ЧислоПрописьюСоСловом(Знач Число, Знач Формы, Знач Род = "м") Экспорт
Текст = ЧислоПрописью(Цел(Число), "Л=ru_RU", ПараметрыПредмета(Формы, Род));
Возврат СтрочнаяПерваяБуква(СокрЛП(Текст));
КонецФункции
// Для договоров и справок: "3 (три) года",
// с Род = "ж" - "21 (двадцать одна) штука".
Функция ЧислоЦифрамиИПрописью(Знач Число, Знач Формы, Знач Род = "м") Экспорт
Число = Цел(Число);
// НП=Ложь - только числительное, без предмета исчисления
Прописью = ЧислоПрописью(Число, "Л=ru_RU; НП=Ложь", ПараметрыПредмета(Формы, Род));
Возврат Формат(Число, "ЧН=0") + " (" + СтрочнаяПерваяБуква(СокрЛП(Прописью)) + ") "
+ ФормаСлова(Число, Формы);
КонецФункции
// Служебные функции
// Разбирает "год, года, лет" в массив из трёх обрезанных строк.
Функция ТриФормы(Знач Формы)
Текст = СтрЗаменить(Формы, ",", Символы.ПС);
Если СтрЧислоСтрок(Текст) <> 3 Тогда
ВызватьИсключение "Нужны три формы слова через запятую, например ""год,года,лет"". Передано: "
+ Формы;
КонецЕсли;
Результат = Новый Массив;
Для НомерСтроки = 1 По 3 Цикл
Результат.Добавить(СокрЛП(СтрПолучитьСтроку(Текст, НомерСтроки)));
КонецЦикла;
Возврат Результат;
КонецФункции
// Параметры предмета исчисления для ЧислоПрописью, дробная часть не выводится.
Функция ПараметрыПредмета(Знач Формы, Знач Род)
Варианты = ТриФормы(Формы);
Возврат Варианты[0] + ", " + Варианты[1] + ", " + Варианты[2] + ", " + Род + ", , , , , 0";
КонецФункции
Функция СтрочнаяПерваяБуква(Знач Текст)
Возврат НРег(Лев(Текст, 1)) + Сред(Текст, 2);
КонецФункции
Проверьте модуль на граничных числах: цикл от 0 до 25 проходит через исключения 11–14 и через 21–24, где правило снова работает.
&НаКлиенте
Процедура ПроверитьСклонение(Команда)
Для Количество = 0 По 25 Цикл
Сообщить(СклонениеПоЧислуКлиентСервер.ЧислоСоСловом(Количество, "год,года,лет"));
КонецЦикла;
// 0 лет, 1 год, 2 года, 5 лет, 11 лет, 14 лет, 21 год, 22 года, 25 лет
Сообщить(СклонениеПоЧислуКлиентСервер.ЧислоСоСловом(111, "год,года,лет")); // 111 лет
Сообщить(СклонениеПоЧислуКлиентСервер.ЧислоСоСловом(1.5, "год,года,лет")); // 1,5 года
Сообщить(СклонениеПоЧислуКлиентСервер.ЧислоПрописьюСоСловом(21, "штука,штуки,штук", "ж"));
// двадцать одна штука
Сообщить(СклонениеПоЧислуКлиентСервер.ЧислоЦифрамиИПрописью(3, "год,года,лет"));
// 3 (три) года
КонецПроцедуры
-21 % 10 равно −1. Без этой проверки вышло бы «−21 дней».Число <> Цел(Число) функция сразу возвращает вторую форму.СтрПолучитьСтроку: эти функции есть в любой версии платформы, в отличие от СтрРазделить. Пробелы обрезаются, так что "год, года, лет" тоже подходит, а результат — обычный массив. Если форм не три, функция вызывает исключение: лучше упасть при отладке, чем напечатать клиенту «5 года».Цел отбрасывает дробь без округления, поэтому округляйте до вызова.С числом согласуется и глагол: «Загружен 1 документ», «Загружено 5 документов», «Осталась 1 минута», «Осталось 2 минуты». Три формы глагола передаются так же, как формы существительного. В печатной форме готовая строка уходит в параметр макета вместе с остальными параметрами области.
// Модуль формы обработки загрузки
&НаКлиенте
Процедура СообщитьИтогЗагрузки(Загружено)
// Глагол тоже согласуется с числом
Текст = СклонениеПоЧислуКлиентСервер.ФормаСлова(Загружено, "Загружен,Загружено,Загружено")
+ " " + СклонениеПоЧислуКлиентСервер.ЧислоСоСловом(Загружено, "документ,документа,документов");
ПоказатьОповещениеПользователя("Загрузка завершена", , Текст);
// Загружен 1 документ, Загружено 3 документа, Загружено 11 документов
КонецПроцедуры
// Серверный код печати: модуль менеджера документа или общий модуль печати
Процедура ЗаполнитьСрокДоговора(ОбластьМакета, СрокЛет)
ОбластьМакета.Параметры.СрокДоговора =
СклонениеПоЧислуКлиентСервер.ЧислоЦифрамиИПрописью(СрокЛет, "год,года,лет");
// 3 (три) года, 11 (одиннадцать) лет, 21 (двадцать один) год
КонецПроцедуры
Платформенная функция ЧислоПрописью(Число, ФорматнаяСтрока, ПараметрыПредметаИсчисления) знает правила склонения сама: ею печатают сумму прописью в счетах и актах. Третий параметр, предмет исчисления, задаёт слово после числа. Для русского языка это строка из девяти элементов через запятую:
| Позиция | Что задаёт | Рубли | Годы |
|---|---|---|---|
| 1 | Форма целой части для 1 | рубль | год |
| 2 | Форма для 2 | рубля | года |
| 3 | Форма для 5 | рублей | лет |
| 4 | Род целой части: м, ж или с | м | м |
| 5–7 | Три формы дробной части | копейка, копейки, копеек | пусто |
| 8 | Род дробной части | ж | пусто |
| 9 | Число знаков дробной части | 2 | 0 |
// Количество лет прописью
Текст = ЧислоПрописью(21, "Л=ru_RU", "год, года, лет, м, , , , , 0");
// Двадцать один год
// Женский род меняет само числительное
Текст = ЧислоПрописью(22, "Л=ru_RU", "штука, штуки, штук, ж, , , , , 0");
// Двадцать две штуки
// Сумма с копейками, как в счёте
Текст = ЧислоПрописью(251.30, "Л=ru_RU",
"рубль, рубля, рублей, м, копейка, копейки, копеек, ж, 2");
// Двести пятьдесят один рубль 30 копеек
// Копейки тоже прописью
Текст = ЧислоПрописью(251.30, "Л=ru_RU; ДП=Истина",
"рубль, рубля, рублей, м, копейка, копейки, копеек, ж, 2");
// Двести пятьдесят один рубль тридцать копеек
// Только числительное, без слова
Текст = ЧислоПрописью(3, "Л=ru_RU; НП=Ложь", "год, года, лет, м, , , , , 0");
// Три
В форматной строке Л=ru_RU задаёт язык прописи, НП=Ложь отключает вывод предмета исчисления, НД=Ложь — названия дробной части, а ДП=Истина пишет дробную часть прописью. Результат начинается с заглавной буквы. Для суммы в счёте так и надо, а в середине фразы первую букву придётся сделать строчной, как в ЧислоПрописьюСоСловом из модуля.
В платформе 8.3.10 появился метод СтрокаСЧислом(Шаблон, Число, Вид, ФорматнаяСтрока). Он выбирает из шаблона фрагмент по правилам языка и подставляет в него число вместо %1. Шаблон состоит из шести фрагментов через точку с запятой, по одному на категорию числа из стандарта Unicode CLDR: zero, one, two, few, many, other. В русском языке используются четыре: one (1, 21, 101), few (2–4, 22–24), many (0, 5–20, 25–30) и other (дробные числа). Фрагменты zero и two оставляют пустыми.
// Платформа 8.3.10 и выше, режим совместимости не ниже 8.3.10
// Порядок фрагментов: zero; one; two; few; many; other
Шаблон = ";%1 год;;%1 года;%1 лет;%1 года";
Текст = СтрокаСЧислом(Шаблон, 21, ВидЧисловогоЗначения.Количественное, "Л=ru_RU"); // 21 год
Текст = СтрокаСЧислом(Шаблон, 11, ВидЧисловогоЗначения.Количественное, "Л=ru_RU"); // 11 лет
Текст = СтрокаСЧислом(Шаблон, 1.5, ВидЧисловогоЗначения.Количественное, "Л=ru_RU"); // 1,5 года
// В шаблон можно вынести всю фразу вместе с глаголом
Текст = СтрокаСЧислом(";Остался %1 день;;Осталось %1 дня;Осталось %1 дней;Осталось %1 дня",
3, ВидЧисловогоЗначения.Количественное, "Л=ru_RU");
// Осталось 3 дня
Своя функция не нужна, а в мультиязычной конфигурации достаточно перевести шаблон: правила каждого языка платформа знает сама. Минусы — непривычный синтаксис шаблона и требование к версии: на платформе или в режиме совместимости ниже 8.3.10 вызов не скомпилируется.
В модуле СтроковыеФункцииКлиентСервер библиотеки стандартных подсистем давно есть функция ЧислоЦифрамиПредметИсчисленияПрописью(Число, ПараметрыПредметаИсчисления): для 23 и "минуту,минуты,минут" она вернёт «23 минуты». С БСП 2.4.3 эта функция помечена устаревшей и оставлена для совместимости. Взамен разработчики библиотеки предлагают СтрокаСЧисломДляЛюбогоЯзыка из того же модуля — обёртку над СтрокаСЧислом, которая не вызывает исключение, если в шаблоне нет строки для текущего языка. Шаблон в неё передают через НСтр.
// БСП 2.4.3 и новее: шаблон в НСтр, правила берутся по текущему языку
Текст = СтроковыеФункцииКлиентСервер.СтрокаСЧисломДляЛюбогоЯзыка(
НСтр("ru = ';%1 день;;%1 дня;%1 дней;%1 дня'"), 21);
// 21 день
// Устаревший вариант, встречается в старом коде
Текст = СтроковыеФункцииКлиентСервер.ЧислоЦифрамиПредметИсчисленияПрописью(23, "минуту,минуты,минут");
// 23 минуты
Параметры функций БСП меняются от версии к версии, так что сверьтесь с описанием в модуле своей конфигурации.
| Способ | Результат | Требования | Когда брать |
|---|---|---|---|
Своя функция ЧислоСоСловом | 21 день | Любая версия и конфигурация | Сообщения, надписи и отчёты в русскоязычной конфигурации |
ЧислоПрописью | Двадцать один день | Любая версия | Суммы и количества прописью в печатных формах |
СтрокаСЧислом | Остался 21 день | Платформа и режим совместимости 8.3.10+ | Мультиязычные конфигурации, фразы целиком |
БСП СтрокаСЧисломДляЛюбогоЯзыка | 21 день | БСП 2.4.3+ | Конфигурация на БСП, новый код |
Если Число % 10 = 1 И Число <> 11 чинит 11, но оставляет «111 год» и «211 год». Проверяйте остаток от деления на 100.ЧислоПрописью ждут порядок «для 1, для 2, для 5»: год, года, лет.м. Для «штука», «неделя» нужен ж, для «место», «наименование» — с.Формат(0) без ЧН=0 возвращает пустую строку.Формат(1.3, "ЧДЦ=0") даёт «1», а форма выбрана по исходному 1,3. Округлите число один раз и передавайте одно значение и в Формат, и в ФормаСлова.Число < 0. Приведите значение функцией Число() до вызова, а для ненадёжного источника сначала проверьте тип значения.Посчитайте полные годы между датами и передайте их в ЧислоСоСловом или ЧислоПрописьюСоСловом. Простая разность годов даёт лишний год, если годовщина в текущем году ещё не наступила.
// Полных лет между датами: стаж, возраст, срок владения
Функция ПолныхЛет(Знач ДатаНачала, Знач ДатаОкончания) Экспорт
ДатаНачала = НачалоДня(ДатаНачала);
ДатаОкончания = НачалоДня(ДатаОкончания);
Лет = Год(ДатаОкончания) - Год(ДатаНачала);
Если ДобавитьМесяц(ДатаНачала, Лет * 12) > ДатаОкончания Тогда
Лет = Лет - 1; // годовщина в этом году ещё не наступила
КонецЕсли;
Возврат Макс(Лет, 0);
КонецФункции
// Вызов: "Стаж: 11 лет"
// Текст = "Стаж: " + СклонениеПоЧислуКлиентСервер.ЧислоСоСловом(
// ПолныхЛет(ДатаПриема, ТекущаяДатаСеанса()), "год,года,лет");
Передавайте формы нужного слова: "день,дня,дней", "месяц,месяца,месяцев", "позиция,позиции,позиций". Если слово нужно во многих местах, заведите в общем модуле функцию вроде ФормыДень(), которая возвращает эту строку.
В языке запросов подходящих функций нет, а собирать форму через ВЫБОР громоздко. В отчёте на СКД добавьте вычисляемое поле с выражением вида СклонениеПоЧислуКлиентСервер.ЧислоСоСловом(Просрочка, "день,дня,дней"): в выражениях схемы можно вызывать экспортные функции неглобальных общих модулей с флажком «Сервер». В обычном коде склоняйте после выполнения запроса, при выводе результата.
В английском две формы: 1 year, 2 years. Здесь удобнее СтрокаСЧислом с шаблоном ";%1 year;;;;%1 years" и кодом языка Л=en. В многоязычной конфигурации храните шаблоны для всех языков в НСтр и не собирайте фразы из кусков.
Если в договоре, акте или справке нужно поправить склонения, сумму прописью или добавить новые поля, это типовая доработка печатных форм 1С. Опишите, что должно получиться, и разместите задачу на площадке: оценка бесплатная, ответ — в течение рабочего дня.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.