Извлечение числа из текста средствами встроенного языка 1С

· 9 мин

Содержание

Достать число из текстовой строки в 1С можно четырьмя путями: преобразовать строку целиком функцией Число(), вырезать нужный фрагмент по позиции через Сред() и СтрНайти(), перебрать строку посимвольно и собрать цифры вручную или применить регулярное выражение. Выбор зависит от того, насколько предсказуем формат: для чистого числа хватит одной функции, для «1 250,50 руб.» или артикула внутри фразы нужен разбор.

Ниже — рабочий код под каждый случай, разбор того, что ломает выделение числа (минус, дробь, разделитель разрядов), и способ честно измерить скорость, а не полагаться на ощущения.

Какой способ подобрать под вашу задачу

Прежде чем писать код, определите формат входной строки. От него зависит метод.

СпособКогда подходитОграничения
Число()Строка целиком — это число ("1250", "-12.5")Падает с ошибкой на любом мусоре, нужен перехват
Сред() + СтрНайти()Число стоит на известной позиции или после метки (№, от)Жёстко завязано на формат строки
Посимвольный переборЧисло «вкраплено» в произвольный текстКод длиннее, сам следишь за знаком и дробью
Регулярное выражениеФормат заранее неизвестен, чисел может быть несколькоНужна платформа новее или COM, есть накладные расходы

Не усложняйте без нужды. Если на вход всегда приходит аккуратная строка из своего же кода, хватит Число() в блоке Попытка. Регулярные выражения берегите для разбора внешних данных: писем, выгрузок, наименований.

Какой способ выделения числа выбрать?

Прямое преобразование: функция Число() и перехват ошибок

Отдельной функции «выдели число» во встроенном языке 1С нет. Для приведения значения к числу служит Число(). Она берёт строку и возвращает числовой тип:

Результат = Число("1250");   // 1250
Результат = Число("-12.5");  // -12.5

Число() ожидает точку как десятичный разделитель и не принимает пробелы-разделители разрядов. Строки "12,5" и "1 250" приведут к ошибке. Поэтому перед вызовом строку нормализуют, а сам вызов оборачивают в Попытка, иначе любой нечисловой символ обрушит выполнение:

Функция СтрокаВЧисло(Исходная) Экспорт
	Подготовленная = СокрЛП(Исходная);
	Подготовленная = СтрЗаменить(Подготовленная, ",", ".");
	Попытка
		Возврат Число(Подготовленная);
	Исключение
		Возврат 0;
	КонецПопытки;
КонецФункции

СокрЛП() убирает пробелы слева и справа, СтрЗаменить() меняет запятую на точку. Если после этого в строке остались буквы или лишние символы, Число() выбросит исключение и функция вернёт 0. При желании вместо нуля можно возвращать Неопределено — так вызывающий код отличит «ноль» от «не число».

Этот способ хорош, когда вы сами контролируете источник: поле ввода, колонку выгрузки, результат другого расчёта. Для свободного текста он не годится — там число надо сначала вычленить.

Вырезаем число по позиции: Лев(), Прав(), Сред() и СтрНайти()

Когда число стоит на предсказуемом месте, его проще вырезать, чем перебирать. Базовый набор строковых функций 1С:

  • 📍 Лев(Строка, Длина) — N символов слева.
  • 📍 Прав(Строка, Длина) — N символов справа.
  • 📍 Сред(Строка, Начало, Длина) — фрагмент с заданной позиции.
  • 📍 СтрНайти(Строка, Искомое) — позиция подстроки или 0.
  • 📍 СтрДлина(Строка) — длина строки.

Типичная задача — достать номер после метки. Пусть на входе "Заказ № 1234 от 05.10". Находим №, отступаем от него и вырезаем хвост до пробела:

Текст = "Заказ № 1234 от 05.10";
ПозицияМетки = СтрНайти(Текст, "№");
Если ПозицияМетки > 0 Тогда
	Хвост = СокрЛП(Сред(Текст, ПозицияМетки + 1));  // "1234 от 05.10"
	ПозицияПробела = СтрНайти(Хвост, " ");
	Если ПозицияПробела > 0 Тогда
		НомерСтрокой = Лев(Хвост, ПозицияПробела - 1); // "1234"
	Иначе
		НомерСтрокой = Хвост;
	КонецЕсли;
	Номер = Число(НомерСтрокой);  // 1234
КонецЕсли;

Когда число стоит в самом конце строки, удобнее Прав(). Например, из "Счёт-фактура-1250" достаём хвост, пока это цифры:

Текст = "Счёт-фактура-1250";
Поз = СтрДлина(Текст);
Длина = 0;
Пока Поз >= 1 Цикл
	Симв = Сред(Текст, Поз, 1);
	Если СтрНайти("0123456789", Симв) > 0 Тогда
		Длина = Длина + 1;
		Поз = Поз - 1;
	Иначе
		Прервать;
	КонецЕсли;
КонецЦикла;
НомерСтрокой = Прав(Текст, Длина);  // "1250"

Цикл Пока идёт от конца строки к началу (в Для шаг назад не задаётся) и считает, сколько цифр подряд стоит справа, а Прав() вырезает ровно этот хвост.

Такой разбор мгновенный и прозрачный, но хрупкий: поменяется формат строки — и Сред() вырежет не то. Применяйте его, когда формат гарантирован (ваши же печатные формы, коды документов, строки обмена с фиксированной структурой). Для плавающего формата переходите к перебору.

СтрНайти() возвращает позицию первого вхождения. Если меток несколько ("№1 и №2"), вырежется только первая. Для всех вхождений используйте расширенную форму СтрНайти() с указанием начальной позиции или регулярное выражение.

Посимвольный перебор: универсальный разбор без зависимостей

Самый надёжный способ достать число из произвольного текста — пройти строку по одному символу и собрать только цифры. Он работает на любой платформе и в любой конфигурации, не требует COM и не зависит от версии.

Простейший вариант — оставить одни цифры:

Функция ТолькоЦифры(Исходная) Экспорт
	Цифры = "0123456789";
	Результат = "";
	Для Поз = 1 По СтрДлина(Исходная) Цикл
		Симв = Сред(Исходная, Поз, 1);
		Если СтрНайти(Цифры, Симв) > 0 Тогда
			Результат = Результат + Симв;
		КонецЕсли;
	КонецЦикла;
	Возврат Результат;
КонецФункции

Проверку «символ — цифра?» делаем через СтрНайти() по строке-эталону "0123456789": так не зависим от кодировки и регистра. Для "арт. A-1500/B" функция вернёт "1500".

Минус подхода виден сразу: для "1 250,50" склеятся разряды и пропадёт дробь — выйдет "125050". Поэтому для денег и дробей нужен разбор поумнее, с учётом знака и десятичного разделителя:

Функция ПервоеЧисло(Текст) Экспорт
	Цифры = "0123456789";
	Собрано = "";
	Знак = "";
	ТочкаЕсть = Ложь;
	Для Поз = 1 По СтрДлина(Текст) Цикл
		Симв = Сред(Текст, Поз, 1);
		Если СтрНайти(Цифры, Симв) > 0 Тогда
			Собрано = Собрано + Симв;
		ИначеЕсли (Симв = "." ИЛИ Симв = ",") И ЗначениеЗаполнено(Собрано) И НЕ ТочкаЕсть Тогда
			Собрано = Собрано + ".";
			ТочкаЕсть = Истина;
		ИначеЕсли Симв = "-" И НЕ ЗначениеЗаполнено(Собрано) Тогда
			Знак = "-";
		ИначеЕсли ЗначениеЗаполнено(Собрано) Тогда
			Прервать;  // число кончилось, выходим
		КонецЕсли;
	КонецЦикла;
	Если НЕ ЗначениеЗаполнено(Собрано) Тогда
		Возврат 0;
	КонецЕсли;
	Возврат Число(Знак + Собрано);
КонецФункции

Функция возвращает первое число в строке. Логика такая: цифры копим в Собрано; точку или запятую принимаем один раз и только после первой цифры; минус — только в самом начале; как встретили разделитель уже после накопленного числа, выходим по Прервать. Запятую сразу превращаем в точку, чтобы итоговую строку принял Число().

Остаётся одна тонкость — разделитель разрядов. В "1 250,50" пробел прервёт сбор на "1". Если суммы могут приходить с пробелами, уберите их заранее, учтя и неразрывный пробел:

Текст = СтрЗаменить(Текст, " ", "");          // обычный пробел
Текст = СтрЗаменить(Текст, Символ(160), "");  // неразрывный пробел

После очистки ПервоеЧисло("Итого: 1 250,50 руб.") вернёт 1250.5.

Регулярные выражения: когда формат заранее неизвестен

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

В платформе не ниже 8.3.23 доступен встроенный объект РегулярноеВыражение. Шаблон -?\d+(\.\d+)? описывает «необязательный минус, цифры, необязательную дробную часть»:

РегВыр = Новый РегулярноеВыражение("-?\d+(\.\d+)?");
Совпадения = РегВыр.НайтиСовпадения("цена 1250.50, скидка -300");
Для Каждого Совпадение Из Совпадения Цикл
	Сообщить(Совпадение.Значение);  // 1250.50, затем -300
КонецЦикла;

Метод НайтиСовпадения() возвращает коллекцию найденного, у каждого элемента есть свойство Значение. Точные имена методов и свойств сверьте в синтакс-помощнике своей версии платформы — объект развивается от релиза к релизу.

На платформе старше 8.3.23 нативного объекта нет. На Windows задачу решает COM-объект VBScript.RegExp:

РегВыр = Новый COMОбъект("VBScript.RegExp");
РегВыр.Pattern = "-?\d+([\.,]\d+)?";
РегВыр.Global = Истина;
Совпадения = РегВыр.Execute("цена 1250,50 и 300 шт");
Для Каждого Совпадение Из Совпадения Цикл
	Сообщить(Совпадение.Value);
КонецЦикла;

COM-объект доступен только под Windows и требует, чтобы компонент был зарегистрирован в системе. На сервере 1С под Linux он не заработает — там выбирайте нативный объект или перебор.

Коротко про сам шаблон: \d — любая цифра, + — одна или больше, ? — ноль или один раз, -? — необязательный минус в начале, [\.,] — точка или запятая. Этого набора хватает, чтобы выловить и целые, и дробные значения.

Знак, дробь и разделитель разрядов: что ломает разбор

Большинство ошибок при выделении числа из строки — это крайние случаи, которые забыли учесть. Разберём их отдельно.

  • ➖ Отрицательные числа. Минус живёт только в начале числа. В перемешанном тексте легко принять дефис из артикула ("A-1500") за знак. В примере выше минус засчитывается, лишь если цифр ещё не было, — дефис внутри кода не портит результат.
  • 🔢 Дробная часть. Решите заранее, какой разделитель ждёте. Нормализуйте запятую в точку перед Число() и следите, чтобы точка встретилась один раз: "12.5.3" — это уже не число.
  • ␣ Разделитель разрядов. Пробел, неразрывный пробел или апостроф между разрядами надо вычистить до разбора, иначе число разорвётся. Удаляйте их через СтрЗаменить().
  • 📏 Длинные номера. Числовой тип 1С хранит до 38 знаков, но у реквизита в базе своя разрядность. Если тянете длинный штрихкод или ИНН, держите его строкой, а не числом, — иначе потеряете ведущие нули и упрётесь в точность.

Отдельно про ведущие нули: Число("007") даст 7, и "007" восстановить уже нельзя. Коды, артикулы и номера, где нули значимы, не переводите в число вообще — работайте со строкой.

Прежде чем писать разбор, соберите 5–10 реальных примеров входных строк и прогоните функцию на них. Крайние случаи всплывают именно на живых данных, а не в голове.

Как измерить скорость и что на неё влияет

Спорить, «что быстрее — перебор или регулярка», без замера бессмысленно: результат зависит от длины строк и их количества. Мерить просто — функцией ТекущаяУниверсальнаяДатаВМиллисекундах() до и после цикла:

Образец = "Заказ № 1250 на сумму 48 500,00 руб.";
Старт = ТекущаяУниверсальнаяДатаВМиллисекундах();
Для Счётчик = 1 По 100000 Цикл
	Результат = ПервоеЧисло(Образец);
КонецЦикла;
Длительность = ТекущаяУниверсальнаяДатаВМиллисекундах() - Старт;
Сообщить(СтрШаблон("Перебор: %1 мс на 100 000 вызовов", Длительность));

Обратите внимание на СтрШаблон() вместо склейки строки с числом через +: так число подставляется корректно и в любой локали.

Что реально влияет на скорость:

  • 🏃 На коротких строках посимвольный перебор обычно расходует меньше всего времени — он не компилирует шаблон и не поднимает COM.
  • 🐢 Регулярное выражение платит за компиляцию шаблона. Если разбираете строки в цикле, создавайте объект РегулярноеВыражение один раз до цикла, а не внутри него.
  • 🧱 COM-объект самый тяжёлый на старте: его создание и вызовы через COM дороже нативного кода. Для разовой операции это незаметно, для сотен тысяч строк — ощутимо.

Практический вывод: для единичных строк берите то, что читается проще. Для пакетной обработки больших выгрузок сначала замерьте оба варианта на своих данных и только потом выбирайте.

Частые вопросы

Чем `Число()` отличается от попытки сложить строку с числом?
Число() — явное приведение типа и единственный корректный способ. Оператор + со строкой выполнит конкатенацию, а не преобразование, и вы получите склеенный текст вместо числа.
Как достать сразу все числа из строки, а не первое?
Либо регулярным выражением с флагом поиска всех совпадений (Global у COM, НайтиСовпадения() у нативного объекта), либо перебором: не выходить по Прервать, а складывать найденные числа в массив.
Почему `Число("1 250,50")` выдаёт ошибку?
Число() не принимает пробелы-разделители разрядов и ждёт точку как десятичный знак. Уберите пробелы через СтрЗаменить() и замените запятую на точку — тогда преобразование пройдёт.
Как сохранить ведущие нули у номера или штрихкода?
Не переводите значение в число. Число("007") вернёт 7, нули потеряются безвозвратно. Храните и обрабатывайте такие коды строкой.
Работает ли COM-объект `VBScript.RegExp` на сервере под Linux?
Нет. COM доступен только под Windows. На Linux-сервере используйте встроенный объект РегулярноеВыражение (платформа не ниже 8.3.23) или посимвольный перебор.