1C-Code → Основы 1С → Числа: арифметика, округление и деньги
Тип, с которого начинается любая учётная задача. Подробно про округление — там теряют копейки.
Глава 5 из 36 · чтение примерно 24 минут
Число — самый частый тип в 1С. Суммы, количества, проценты, номера, дни просрочки — всё это числа, и почти каждая задача сводится к тому, чтобы посчитать их правильно.
Правильно — значит до копейки. Именно поэтому половина главы про округление: там теряют деньги, и теряют незаметно.
В 1С нет отдельно «целого» и «дробного» числа. Тип один — Число, и в него одинаково хорошо кладётся 5 и 5,25.
В коде дробная часть отделяется точкой, а не запятой: Цена = 10.5;. На экране 1С покажет запятую, как принято в русском языке, но пишут в коде точку.
Число в 1С — десятичное, и это редкость
Почти во всех языках дробные числа хранятся приближённо, в двоичном виде. Знаменитый пример: 0.1 + 0.2 там не равно 0.3, а даёт 0,30000000000000004.
В 1С этой проблемы нет. Число здесь десятичное с фиксированной точностью, и 0,1 + 0,2 равно ровно 0,3. Для учётной системы это принципиально: копейки не должны появляться и исчезать сами.
Если вы читали про «ошибки плавающей точки» в других языках — здесь этот приём не нужен. А вот про округление — нужен, и очень.
Сумма = 250 + 100; // 350
Остаток = 250 - 100; // 150
Итого = 250 * 4; // 1000
Средняя = 1000 / 4; // 250
| Часть | Что означает |
|---|---|
+ − * / | как в математике, порядок действий тоже обычный |
скобки | меняют порядок: (2 + 3) * 4 = 20, а 2 + 3 * 4 = 14 |
деление | всегда даёт дробное: 7 / 2 = 3,5, а не 3 |
деление на ноль | не даёт бесконечность — программа прекращает работу с ошибкой |
Упражнение 1. Сумма с наценкой
Напишите функцию СЦеной(Закупочная, Наценка), которая возвращает цену продажи: закупочная плюс наценка в процентах. Для 100 и 30 должно получиться 130.
Решается в тренажёре: код запускается и проверяется сразу.
Две команды, которые путают чаще всего.
Цел(Значение) — отбрасывает дробную часть. Не округляет, а именно отрезает. Цел(7.9) даёт 7, а не 8.
Окр(Значение, Разрядов) — округляет по правилам. Окр(7.9) даёт 8.
| Выражение | Результат | Что произошло |
|---|---|---|
| Цел(7.9) | 7 | дробная часть отброшена |
| Окр(7.9) | 8 | округлено до ближайшего |
| Цел(-7.9) | −7 | отброшена дробь — значение приблизилось к нулю |
| Окр(-7.9) | −8 | округлено до ближайшего — значение ушло от нуля |
| Окр(1.2345, 2) | 1,23 | два знака после запятой |
| Окр(1250, -2) | 1300 | отрицательный разряд округляет до сотен |
Отрицательные числа: Цел коварнее, чем кажется
Цел(-7.9) — это −7, а не −8. Отбрасывание дробной части всегда двигает число к нулю, в какую бы сторону вы ни думали.
На положительных числах это совпадает с «округлением вниз», и потому ошибку не замечают — пока в данных не появится минус. А он появляется: возвраты, корректировки, отрицательные остатки.
Упражнение 2. Сколько целых коробок
В коробку помещается 12 штук. Напишите функцию ЦелыхКоробок(Штук), которая возвращает, сколько коробок заполнится полностью. Для 30 штук это 2 коробки (шесть штук останутся).
Решается в тренажёре: код запускается и проверяется сразу.
Знак % даёт остаток: 17 % 5 — это 2, потому что 17 = 5 × 3 + 2.
В учёте он нужен постоянно: сколько штук не влезло в целые коробки, чётный ли номер, делится ли сумма нацело.
Упражнение 3. Что не влезло
Дополните: функция Остаток(Штук) возвращает, сколько штук останется сверх полных коробок по 12. Для 30 штук — 6.
Решается в тренажёре: код запускается и проверяется сразу.
Правило, которое надо запомнить дословно: округлять надо каждую строку, а не итог. Потому что Окр(а) + Окр(б) и Окр(а + б) — разные числа.
✓ Каждую строку
Окр(10.005 * 3, 2) = 30.02
Окр(10.005 * 3, 2) = 30.02
итого 60.04Так печатается накладная, и так складывает бухгалтер.
✕ Итог целиком
10.005 * 3 = 30.015
10.005 * 3 = 30.015
Окр(60.03, 2) = 60.03Копейка разницы — и документ не сходится со своей же печатной формой.
Почему копейка расходится
Расхождение на копейку — самая частая причина жалобы «документ не проводится, а всё правильно». Ошибка не в данных и не в проверке, а в порядке действий: где именно поставили округление.
Найти это трудно, потому что на большинстве документов сумма сходится: разница появляется только когда цена содержит больше двух знаков — после пересчёта из валюты, после расчёта скидки, после деления на количество.
Упражнение 4. Сумма строки как в накладной
Напишите функцию СуммаСтроки(Цена, Количество): произведение, округлённое до копейки. Для цены 10.005 и количества 3 должно получиться 30.02.
Решается в тренажёре: код запускается и проверяется сразу.
Что делать с ровно половиной — 2,5? Есть два правила, и 1С умеет оба.
Окр(2.5) без третьего параметра даёт 3: половина уходит от нуля. Это привычное «школьное» округление, и в учёте используется оно.
Окр(2.5, 0, 0) даёт 2: половина уходит к чётному. Такое округление называют банковским — при большом числе операций оно не накапливает перекос в одну сторону.
Какое выбирать
По умолчанию — обычное, оно же принято в бухгалтерии и в печатных формах. Банковское применяют, когда об этом прямо сказано в требованиях: например, в расчётах с некоторыми банками или в статистике.
Главное — не смешивать в одной системе. Разные правила в разных местах дают расхождения, которые потом сходятся вручную ночами.
Деление на ноль в 1С — не бесконечность и не ноль, а прекращение работы с ошибкой. Обработчик, упавший на одном менеджере из тридцати, не покажет отчёт вообще.
Поэтому знаменатель проверяют до деления, а не после. После — поздно: до проверки дело не дойдёт.
Упражнение 5. Процент выполнения плана
Напишите функцию Процент(Факт, План). Если план не больше нуля — вернуть 0, не деля. Иначе вернуть процент выполнения, округлённый до одного знака.
Решается в тренажёре: код запускается и проверяется сразу.
Задача, которая встречается в учётной работе постоянно: разложить сумму документа по строкам, скидку — по товарам, доставку — по местам.
Сделанная в лоб — делим и округляем каждую долю — она даёт расхождение. Сто рублей на троих: 33,33 плюс 33,33 плюс 33,33 равно 99,99. Копейка исчезла, документ не сходится, и бухгалтер её найдёт.
Приём против этого один и применяется везде: последняя доля не считается, а берётся остатком. Тогда сумма долей равна исходной всегда — по построению, а не по счастливому совпадению.
✓ Так — копейка теряется
Для Каждого Строка Из Строки Цикл
Строка.Доля = Окр(Сумма / Строки.Количество(), 2);
КонецЦикла;
// 33,33 + 33,33 + 33,33 = 99,99Каждая доля округлена сама по себе. Ошибки округления складываются, и расхождение растёт со строками.
✕ Так — сходится всегда
Остаток = Сумма;
Для Сч = 1 По Всего Цикл
Если Сч = Всего Тогда
Доля = Остаток; // последняя — всё, что осталось
Иначе
Доля = Окр(Сумма / Всего, 2);
Остаток = Остаток - Доля;
КонецЕсли;
КонецЦикла;
// 33,33 + 33,33 + 33,34 = 100,00Лишняя копейка честно достаётся последней строке. Сумма долей равна исходной при любом числе строк.
Почему именно последней
Кому достанется копейка — вопрос не математики, а договорённости. В типовых конфигурациях её отдают строке с наибольшей суммой: там она менее заметна в процентах.
Но принцип от этого не меняется: одна из долей считается остатком, а не формулой. Кто именно — решают уже по задаче.
И ещё: распределять надо пропорционально, если строки разного веса. Делить поровну можно только когда они действительно равны — например, места в доставке.
Упражнение 7. Разложить сумму по частям
Напишите Распределить(Сумма, Частей): верните доли через / , каждая с двумя знаками. Последняя доля — остаток, чтобы сумма сошлась. Для 100 на 3 ожидается 33,33 / 33,33 / 33,34.
Решается в тренажёре: код запускается и проверяется сразу.
Две операции, которые путают, и обе встречаются в первой же учётной задаче.
Начислить НДС сверху: Сумма * СтавкаНДС / 100. Было 1000 без НДС, стало 1200 с НДС при ставке 20%.
Выделить НДС из суммы, в которой он уже сидит: Сумма * СтавкаНДС / (100 + СтавкаНДС). Из 1000 с НДС выделяется 166,67, а не 200.
Формула «20/120» — это она и есть, записанная числами. Именно её чаще всего и вспоминают неправильно.
Округлять один раз, и не раньше времени
НДС считают от итога строки, а не от цены, и округляют один раз — в конце.
Округление цены, потом умножение на количество даёт расхождение, которое растёт с количеством: три копейки на единице превращаются в три рубля на сотне. Поэтому порядок всегда один: посчитать точно, округлить в самом конце.
Это общее правило, не только про НДС. Любая цепочка вычислений округляется один раз — там, где результат показывают или записывают.
Упражнение 8. Выделить НДС
Напишите НДСИзСуммы(Сумма, Ставка): выделить НДС из суммы, в которой он уже есть, и округлить до копеек. Для 1000 и ставки 20 ожидается 166.67.
Решается в тренажёре: код запускается и проверяется сразу.
Почему 0,1 + 0,2 в 1С даёт ровно 0,3
Если вы программировали на других языках, то знаете неприятность: там 0.1 + 0.2 не равно 0.3, потому что дробные числа хранятся двоичными и десятичную дробь точно не представляют.
В 1С этой проблемы нет. Число здесь десятичное с фиксированной точностью — сделано специально ради денег. Сравнивать суммы обычным = можно, и это правильно.
Знать об этом стоит по двум причинам: чтобы не тащить из других языков лишние ухищрения со сравнением через малую погрешность, и чтобы ответить на собеседовании, чем число в 1С отличается от привычного.
У числового реквизита в конфигураторе задают длину (всего цифр) и точность (из них после запятой). Для сумм обычно 15 и 2.
Важно, что это не подсказка, а ограничение. Число, не помещающееся в разрядность, обрезается при записи: посчитали 1234,567 при точности 2 — в базу легло 1234,56, и дальше все отчёты будут считать по нему.
Второе: длина считается ВСЕГО, вместе с дробной частью. Длина 5 и точность 2 — это максимум 999,99, а не 99999,99. На этом спотыкаются, заводя реквизит для процентов.
| Команда | Что делает | Пример |
|---|---|---|
| Макс(а, б) | большее из двух | Макс(Остаток, 0) — «не меньше нуля» |
| Мин(а, б) | меньшее из двух | Мин(Пеня, Долг) — «не больше долга» |
| Цел(x) | отбросить дробную часть | Цел(30 / 12) = 2 |
| Окр(x, n) | округлить до n знаков | Окр(1.2345, 2) = 1,23 |
| Число("250") | превратить строку в число | при загрузке из файла |
| Строка(250) | превратить число в строку | чтобы приклеить к тексту |
| x % y | остаток от деления | 17 % 5 = 2 |
Макс с нулём вместо Если
Приём, который стоит завести в привычку: Макс(Значение, 0) означает «значение, но не меньше нуля».
Это короче четырёх строк с Если и закрывает целый класс мест, где отрицательное число выглядит как поломка: остаток, нехватка, дни просрочки, размер скидки.
Упражнение 6. Не меньше нуля
Напишите функцию НеХватает(Сумма, Минимум): сколько добрать до минимальной суммы заказа. Если сумма уже достаточна — вернуть 0, а не отрицательное число. Обойдитесь без Если.
Решается в тренажёре: код запускается и проверяется сразу.
Числа освоены. Следующая глава — строки: как их склеивать, резать, искать в них и превращать в числа и обратно. Там своя группа ловушек, в основном связанных с тем, что строку легко спутать с числом.
Читать про код и писать код — разные умения. В тренажёре к этой главе идут упражнения: вы пишете решение, оно запускается на настоящем интерпретаторе 1С и проверяется сразу, без установки платформы.
← Где живёт код: модули, клиент и сервер
→ Строки: склеить, разрезать, найти, проверить