Почему метод ПривестиЗначение() в 1С округляет числа до двух знаков и как это исправить?

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

При разработке на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с неочевидным поведением стандартных функций. Одной из таких проблем является неожиданное округление чисел до двух десятичных знаков при использовании метода ПривестиЗначение() объекта ОписаниеТипов. Ситуация усугубляется тем, что на локальном компьютере разработчика (например, под управлением Windows 10) код может работать идеально, а после переноса на рабочий сервер (например, под управлением Windows Server 2016) начинает возвращать некорректный результат.

В этой статье мы подробно проанализируем причины такого поведения системы, разберем ошибки в программном коде и рассмотрим эффективные способы решения этой проблемы.

Анализ исходной ситуации и программного кода

Рассмотрим процедуру, которая призвана преобразовывать строковое представление числа в числовой тип данных с применением заданных квалификаторов:


Процедура СтрокаВЧисло(ЧислоРезультат, ЧислоСтрокой, ЗначениеПоУмолчанию = 0, ОписаниеТиповЧисло = Неопределено) Экспорт 
    Если ПустаяСтрока(ЧислоСтрокой) 
       И (ОписаниеТиповЧисло = Неопределено) 
       И (ОписаниеТиповЧисло.Типы.Найти(Тип("Число")) = Неопределено) Тогда 
        ЧислоРезультат = ЗначениеПоУмолчанию; 
        Возврат; 
    КонецЕсли; 
    
    ЧислоРезультат = ОписаниеТиповЧисло.ПривестиЗначение(ЧислоСтрокой); 
КонецПроцедуры

При тестировании этой процедуры на сервере под управлением Windows Server 2016 метод ОписаниеТиповЧисло.ПривестиЗначение(ЧислоСтрокой) возвращает 0 для строки "0,00035" (хотя в квалификаторах указана точность 5 знаков: КвалификаторыЧисла(15, 5)). Если передать число больше 0.005, значение округляется до 0.01. Таким образом, платформа упорно округляет данные до двух десятичных знаков.

Разбор ошибки в логике условия (Важное замечание)

Прежде чем перейти к основной проблеме с округлением, проанализируем условие Если в исходном коде. В нем содержится серьезная логическая ошибка, которая может привести к аварийному завершению работы программы (Runtime Error):


Если ПустаяСтрока(ЧислоСтрокой) И (ОписаниеТиповЧисло = Неопределено) И (ОписаниеТиповЧисло.Типы.Найти(Тип("Число")) = Неопределено) Тогда

Обратим внимание на сочетание проверок. Если переменная ОписаниеТиповЧисло действительно равна Неопределено, то вторая часть условия (ОписаниеТиповЧисло = Неопределено) вернет Истина. Однако сразу после этого платформа попытается вычислить третью часть условия: обратиться к свойству Типы у значения Неопределено. Это неминуемо вызовет ошибку обращения к методу/свойству отсутствующего объекта.

Несмотря на то, что в 1С применяется оптимизированное (короткое) вычисление логических выражений (если первый операнд в цепочке И равен Ложь, дальнейшие проверки не выполняются), при выполнении всех первых условий система все равно упадет на последнем шаге. Нам необходимо переписать это условие более безопасно.

Выясняем причину: Влияние региональных настроек ОС

Главная причина некорректного округления кроется в том, как платформа 1С интерпретирует текстовую строку при передаче ее в метод ПривестиЗначение(). Процесс разбора строки напрямую зависит от региональных стандартов (Locale) операционной системы.

Когда вы запускаете файловую базу на локальной машине (например, Windows 10), сеанс работает под вашей учетной записью. В системе установлены русские региональные стандарты, где разделителем дробной части является запятая. Строка "0,00035" успешно парсится платформой в число высокой точности.

На операционных системах Windows Server код серверных модулей выполняется в контексте специальной системной учетной записи (например, USR1CV8 или NetworkService). Очень часто при установке англоязычного дистрибутива Windows Server для системных учетных записей по умолчанию устанавливается стандарт "English (United States)", где разделителем дробной части является точка, а запятая используется в качестве разделителя групп разрядов (тысяч).

В результате на сервере происходит следующее:

  1. Метод ПривестиЗначение() получает на вход строку с запятой: "0,00035".
  2. Платформа пытается преобразовать эту строку в число, ориентируясь на системные настройки сервера (где разделитель — точка).
  3. Из-за несоответствия разделителя парсинг строки дает сбой. Система либо отбрасывает дробную часть, либо преобразует её некорректно, откатываясь к стандартному представлению с точностью по умолчанию (2 знака после запятой).

Способ решения №1: Предварительное явное преобразование (Рекомендуемый)

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

Функция Число() гораздо более гибко реагирует на различные разделители (запятые и точки) и корректно преобразует строковое представление в полноценный числовой тип 1С. После этого метод ПривестиЗначение() применит квалификаторы (точность 5 знаков) уже к готовому числу без промежуточных ошибок парсинга.

Рассмотрим исправленный и безопасный вариант функции:


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

Способ решения №2: Настройка системной учетной записи Windows Server

Если вам необходимо исправить поведение системы глобально (чтобы все аналогичные методы платформы работали корректно без переписывания кода), нужно синхронизировать региональные настройки системного пользователя, под которым запущена служба 1C:Enterprise 8.3 Server Agent.

Выполним следующие шаги:

  1. Зайдите на сервер под учетной записью Администратора.
  2. Откройте классическую Панель управления и перейдите в раздел Региональные стандарты (Region).
  3. Убедитесь, что на вкладке "Форматы" выбран правильный формат (например, Русский).
  4. Перейдите на вкладку Дополнительно (Administrative).
  5. Нажмите кнопку Копировать параметры (Copy settings).
  6. В открывшемся окне внизу установите флажки:
    • "Экран приветствия и системные учетные записи" (Welcome screen and system accounts)
    • "Новые учетные записи пользователей" (New user accounts)
  7. Нажмите ОК и согласитесь на перезагрузку сервера.

После перезагрузки сервера служба 1С получит те же региональные стандарты (включая разделитель-запятую), что и обычные пользователи, и метод ПривестиЗначение() начнет работать предсказуемо.

Важное примечание: После изменения региональных настроек в операционной системе обязательно перезапустите службу сервера 1С (1C:Enterprise 8.3 Server Agent), так как платформа кэширует настройки локали при старте кластера.

← На главную