В практике программирования на платформе 1С часто возникает задача нахождения суммы всех цифр числа. Это может потребоваться для формирования контрольных сумм, реализации алгоритмов валидации (например, алгоритма Луна для банковских карт) или в рамках различных математических расчетов. Особым случаем является нахождение цифрового корня — это результат рекурсивного сложения цифр до тех пор, пока не получится одноразрядное число (от 1 до 9).
В данной статье мы разберем несколько способов реализации этой задачи, проанализируем их производительность и выясним, почему один и тот же код может работать корректно или возвращать неожиданный результат.
Проанализируем самый распространенный и эффективный с точки зрения потребления ресурсов метод — использование арифметических операций. Суть метода заключается в последовательном отделении последней цифры числа с помощью операции получения остатка от деления на 10 % 10 и последующем уменьшении разрядности числа через целочисленное деление Цел(Число / 10).
Рассмотрим пример функции, которая вычисляет сумму цифр один раз:
Функция СуммаЦифрВЧисле(Знач Число)
Результат = 0;
// Работаем с модулем числа, чтобы избежать проблем с отрицательными значениями
Число = ?(Число < 0, -Число, Число);
Пока Число > 0 Цикл
Результат = Результат + (Число % 10);
Число = Цел(Число / 10);
КонецЦикла;
Возврат Результат;
КонецФункции
Этот метод хорош тем, что он работает исключительно с примитивным типом Число, не создавая лишних объектов в памяти и не вызывая преобразования типов, что крайне важно для высоконагруженных циклов.
Если наша задача — довести сумму до однозначного числа, мы можем использовать рекурсию. Однако здесь кроется типичная ловушка для начинающих разработчиков. Разберем ситуацию: почему при передаче числа 128 функция может вернуть 0?
Причина кроется в потере результата при передаче его «вверх» по стеку вызовов. Выясним причину на примере некорректного и корректного кода.
Ошибочный код:
Функция СуммаЧисел(Знач Число)
Сумма = 0;
Пока Число > 0 Цикл
Сумма = Сумма + Число % 10;
Число = Цел(Число / 10);
КонецЦикла;
Если Сумма > 9 Тогда
СуммаЧисел(Сумма); // ОШИБКА: Результат вызова игнорируется, Возврат не срабатывает
Иначе
Возврат Сумма;
КонецЕсли;
КонецФункции
В приведенном выше примере, если условие Сумма > 9 выполняется, функция вызывает саму себя, но не передает результат выполнения вызванной функции дальше. Правильное решение требует обязательного использования ключевого слова Возврат перед рекурсивным вызовом.
Исправленный код:
Функция СуммаЧисел(Знач Число)
Сумма = 0;
Пока Число > 0 Цикл
Сумма = Сумма + Число % 10;
Число = Цел(Число / 10);
КонецЦикла;
Если Сумма > 9 Тогда
Возврат СуммаЧисел(Сумма); // Правильно: возвращаем результат рекурсии
Иначе
Возврат Сумма;
КонецЕсли;
КонецФункции
Иногда разработчики предпочитают работать со строками, полагая, что это более наглядно. Рассмотрим алгоритм, в котором число преобразуется в строку, разбивается на символы, а затем складывается с помощью функции Вычислить().
Функция СуммаЧиселСтроковый(Число)
Сумма = Число;
СуммаСтрокой = Формат(Сумма, "ЧН=0; ЧГ=0");
Пока СтрДлина(СуммаСтрокой) > 1 Цикл
МассивВычислений = Новый Массив;
Для Инд = 1 По СтрДлина(СуммаСтрокой) Цикл
МассивВычислений.Добавить(Сред(СуммаСтрокой, Инд, 1));
КонецЦикла;
// Соединяем цифры знаком плюс и вычисляем строку
Сумма = Вычислить(СтрСоединить(МассивВычислений, "+"));
СуммаСтрокой = Формат(Сумма, "ЧН=0; ЧГ=0");
КонецЦикла;
Возврат Сумма;
КонецФункции
Проанализируем этот подход. Несмотря на его работоспособность, он является самым медленным. Использование функции Вычислить() требует запуска парсера выражений 1С, что крайне ресурсоемко. Кроме того, постоянное преобразование типов Число -> Строка -> Массив -> Строка -> Число создает огромную нагрузку на память при обработке больших массивов данных. Мы рекомендуем избегать этого метода в промышленной разработке.
Мало кто знает, что для нахождения цифрового корня (суммирования до одной цифры) не нужны ни циклы, ни рекурсии. В математике существует свойство, основанное на признаке делимости на 9. Цифровой корень числа совпадает с остатком от деления этого числа на 9 (за исключением случая, когда остаток 0, тогда корень равен 9).
Посмотрим на пример реализации этой формулы в одну строку:
Функция ЦифровойКореньБыстро(Знач Число)
Число = ?(Число < 0, -Число, Число);
Если Число = 0 Тогда
Возврат 0;
КонецЕсли;
Результат = 1 + (Число - 1) % 9;
Возврат Результат;
КонецФункции
Этот способ работает мгновенно. Мы избавляемся от итераций и рекурсивных вызовов. Если вам нужно получить финальную одиночную цифру, используйте именно эту формулу.
Подведем итоги нашего анализа и сравним рассмотренные методы по нескольким критериям:
Возврат.При реализации подобных функций всегда учитывайте следующие моменты:
Отрицательные числа: Операция % (остаток от деления) может вести себя специфически с отрицательными числами в разных версиях платформы. Всегда приводите входное значение к модулю с помощью функции Abs() или условия ?(Число < 0, -Число, Число).
Дробные числа: Если на вход подается число с десятичной точкой (например, 123.45), стандартный цикл Пока Число > 0 может уйти в бесконечный цикл или выдать неверный результат. В таких случаях предварительно преобразуйте число в целое, убрав разделитель, либо используйте Цел() на каждом этапе.
Тип данных: Если число слишком длинное (превышает точность типа Число в 1С), платформа может округлить его, и сумма цифр будет неверной. Для очень длинных последовательностей цифр (например, 50 знаков) следует передавать данные в функцию как тип Строка и обрабатывать их посимвольно.
Выяснив все способы, мы можем сделать вывод: для простых задач суммирования лучше использовать арифметический цикл, а для получения цифрового корня — математическую формулу.