Каждый разработчик 1С рано или поздно сталкивается с мистической на первый взгляд ситуацией: в цикле программы рассчитывается сумма числового показателя, параллельно значения выводятся в отчет или через отладочное сообщение, но при проверке этих же данных в Excel итоговые суммы расходятся на десятки и сотни тысяч. При этом ручной пересчет отдельных строк подтверждает правоту одной системы, а автоматические формулы Excel — другой. Разберем подробно, почему возникает эта проблема, как на нее влияют особенности платформы 1С и как правильно локализовать и устранить подобные расхождения.
В абсолютном большинстве случаев, когда 1С выдает правильную (большую) сумму, а Excel при автосуммировании выдает гораздо меньший результат, виновато визуальное представление чисел. Платформа 1С по умолчанию форматирует числовые значения при выводе на печать или в табличный документ, разделяя группы разрядов (тысячи, миллионы) специальным символом — неразрывным пробелом (символ с ASCII-кодом 160, или Символы.НПП).
Обычный пробел имеет код 32, но для предотвращения некрасивого переноса цифр на новую строку в интерфейсах используется именно код 160. Когда пользователь копирует полученную таблицу из 1С в Excel через буфер обмена, Excel видит число вида 150 000.00 с неразрывным пробелом внутри. Для математического движка Excel такая ячейка является строкой (текстом), а не числом.
В результате встроенная функция Excel СУММ или инструмент «Автосумма» просто игнорируют текстовые ячейки. Суммируются только те строки, в которых числа были меньше тысячи (и, соответственно, не имели разделителей групп разрядов), либо те, которые по какой-то причине скопировались без форматирования. Из-за этого итоговая сумма в Excel оказывается значительно меньше реальной.
Если данные уже выгружены и их необходимо быстро пересчитать без изменения кода 1С, выполним следующие шаги по очистке данных в Excel:
После этой операции Excel мгновенно преобразует строки в полноценные числа, и формулы автосуммы начнут отражать корректный результат.
Чтобы избавить пользователей от рутины и гарантировать корректный экспорт данных, разработчику необходимо управлять форматом вывода чисел. Рассмотрим основные способы решения этой задачи в коде.
Способ 1. Использование функции Формат() при выводе
Если мы выводим данные в текстовые сообщения или формируем строки для последующего копирования, необходимо принудительно отключать группировку разрядов с помощью параметра ЧГ=0 (Число: Группировка = 0). Проанализируем пример кода:
// Неправильный вывод — число будет содержать неразрывные пробелы
Сообщить("Цена товара: " + Выборка.Цена);
// Правильный вывод — число будет выведено сплошной строкой без разделителей
Сообщить("Цена товара для копирования: " + Формат(Выборка.Цена, "ЧГ=0"));
Способ 2. Очистка неразрывных пробелов через замену символов
Если нам необходимо программно подготовить строковое представление числа, очистив его от любых видов пробелов, воспользуемся функцией СтрЗаменить():
// Очищаем как обычные, так и неразрывные пробелы
СтрокаБезПробелов = СтрЗаменить(СтроковоеЗначениеЧисла, Символы.НПП, "");
СтрокаБезПробелов = СтрЗаменить(СтрокаБезПробелов, " ", "");
В исходном обсуждении упоминалось, что расчет производится в «рекурсивном цикле». Рекурсия накладывает жесткие требования к области видимости переменных. Рассмотрим две классические ошибки, которые могут приводить к искажению сумм в коде 1С при работе с рекурсивными вызовами процедур.
Ошибка 1: Накопление в переменной модуля (глобальной переменной)
Если переменная СуммаОбщаяТоваров объявлена на уровне модуля, то каждый новый запуск процедуры или повторный вызов функции в рамках одного сеанса работы будет прибавлять новые значения к уже накопленному итогу. Это приводит к колоссальному завышению результата.
Ошибка 2: Неправильная передача переменной-накопителя в параметры
Рассмотрим, как правильно организовать подсчет суммы по иерархической структуре (например, дереву номенклатуры) с передачей переменной по ссылке:
// Функция запускает расчет и возвращает итоговое значение
Функция РассчитатьСуммуДерева(ТекущаяСтрокаДерева) Экспорт
ПеременнаяСуммы = 0;
РекурсивныйПодсчет(ТекущаяСтрокаДерева, ПеременнаяСуммы);
Возврат ПеременнаяСуммы;
КонецФункции
// Вспомогательная процедура, выполняющая рекурсивный обход
Процедура РекурсивныйПодсчет(СтрокаРодитель, ОбщаяСумма)
// Обходим подчиненные элементы
Для Каждого СтрокаПодчиненная Из СтрокаРодитель.Строки Цикл
// Прибавляем значение к переменной, переданной по ссылке
ОбщаяСумма = ОбщаяСумма + СтрокаПодчиненная.Цена;
// Рекурсивный вызов для следующего уровня вложенности
РекурсивныйПодсчет(СтрокаПодчиненная, ОбщаяСумма);
КонецЦикла;
КонецПроцедуры
Обратите внимание, что переменная ОбщаяСумма не должна обнуляться внутри процедуры РекурсивныйПодсчет, иначе мы потеряем данные предыдущих уровней рекурсии.
Если проблема расхождения сумм кроется не в формате Excel, а непосредственно в самом алгоритме 1С, стоит тщательно проанализировать источник данных — запрос.
Проанализируем две архитектурные особенности работы с выборками запросов в 1С:
Выбрать() может обходить результаты по иерархии группировок. Если разработчик в одном цикле пытается просуммировать значения детальных записей и записей-итогов, конечная сумма увеличится кратно количеству группировок.Посмотрим на пример правильного обхода выборки с группировками, чтобы избежать задвоения сумм:
// Выборка только детальных записей, минуя группировочные итоги запроса
ВыборкаДетали = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.Прямой);
Пока ВыборкаДетали.Следующий() Цикл
СуммаОбщаяТоваров = СуммаОбщаяТоваров + ВыборкаДетали.Цена;
КонецЦикла;
Применение прямого обхода гарантирует, что мы просуммируем только конечные физические показатели строк, исключив влияние итоговых веток дерева запроса.