Как универсально преобразовать строку в число в 1С: решаем проблему нестандартных разделителей

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При обмене данными, загрузке из Excel или парсинге текстовых файлов программисты 1С часто сталкиваются с проблемой: число представлено в строке в «грязном» виде. Разделителем дробной части может быть как точка, так и запятая, разделителем разрядов — пробел, апостроф или точка, а отрицательные числа могут быть заключены в скобки или иметь минус в конце. Разберем, как создать универсальный механизм, который «переварит» большинство таких форматов.

Использование стандартных функций БСП

Если в вашей конфигурации подключена Библиотека стандартных подсистем (БСП), в первую очередь стоит обратиться к встроенным механизмам. Рассмотрим функцию СтрокаВЧисло из общего модуля СтроковыеФункцииКлиентСервер. Проанализируем, как она работает «под капотом»:


Функция СтрокаВЧисло(Знач Значение) Экспорт
    
    // Удаляем стандартные пробелы
    Значение = СтрЗаменить(Значение, " ", "");
    
    // Обработка бухгалтерского формата отрицательных чисел, например "(100)"
    Если СтрНачинаетсяС(Значение, "(") Тогда
        Значение = СтрЗаменить(Значение, "(", "-");
        Значение = СтрЗаменить(Значение, ")", "");
    КонецЕсли;
    
    // Проверка на пустую строку или строку, состоящую только из нулей
    СтрокаБезНулей = СтрЗаменить(Значение, "0", "");
    Если ПустаяСтрока(СтрокаБезНулей) Или СтрокаБезНулей = "-" Тогда
        Возврат 0;
    КонецЕсли;
    
    // Основное преобразование через ОписаниеТипов
    ТипЧисло = Новый ОписаниеТипов("Число");
    Результат = ТипЧисло.ПривестиЗначение(Значение);
    
    Возврат ?(Результат <> 0 И Не ПустаяСтрока(СтрокаБезНулей), Результат, Неопределено);
    
КонецФункции

Важно понимать, что метод ПривестиЗначение() объекта ОписаниеТипов работает более гибко, чем стандартная функция Число(). Он игнорирует многие нецифровые символы, однако его поведение сильно зависит от локальных настроек Windows. Если в системе разделителем является запятая, то точка в строке может привести к непредсказуемому результату.

Реализация собственного «всеядного» алгоритма

Когда стандартных средств недостаточно (например, когда приходят строки вида 123'456.78 или 1.234.567,89), нам необходимо реализовать более сложную логику. Рассмотрим пошагово алгоритм, который позволит корректно определить дробную часть:

  1. Очистка от визуального мусора. Удалим все символы, которые точно не являются цифрами или знаками: пробелы, неразрывные пробелы (символ с кодом 160), апострофы и валютные знаки.
  2. Определение десятичного разделителя. В строке может быть несколько точек или запятых. Нам нужно найти тот символ, который отделяет дробную часть. Как правило, это самый правый знак (точка или запятая).
  3. Нормализация строки. Удалим все разделители разрядов, а единственный найденный разделитель дробной части заменим на точку (стандарт для внутренних преобразований).
  4. Обработка знака. Проверим наличие минуса не только в начале, но и в конце строки (характерно для некоторых банковских выписок).

Посмотрим на пример реализации такой логики:


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

Проблема неопределенности (45,783)

Проанализируем ситуацию, упомянутую в обсуждении: как трактовать строку 45,783? Это сорок пять тысяч семьсот восемьдесят три или сорок пять целых семьсот восемьдесят три тысячных?

Вариант А: Если в строке только один разделитель и после него ровно три знака — это может быть как разделитель тысяч, так и десятичный знак. Без знания контекста (например, что это — количество кирпичей или вес золота) задачу решить невозможно.

Вариант Б: Если в строке несколько одинаковых разделителей (например, 1.234.567), то это однозначно разделители групп разрядов.

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

Особенности загрузки из Excel

Выясним причину, почему при загрузке из файлов Excel часто возникают ошибки преобразования. В формате .xlsx значение в ячейке может храниться как число, а отображаться с форматированием. Если вы используете COM-объект, лучше обращаться к свойству ячейки Value, которое возвращает числовой тип данных, а не Text.

Если же вы получаете данные в виде текста (например, через ADO или парсинг XML), помните про экспоненциальную форму записи (например, 1.2E+3). Стандартная функция 1С Число() умеет работать с такой записью, но только если в строке нет лишних пробелов. Поэтому предварительная очистка строки от невидимых символов — критически важный этап.

Резюме

Для создания надежного механизма преобразования следуйте этим правилам:

← На главную