Где в расчёте роялти теряются копейки — и наш код, которым это чинится
Почему сумма роялти в таблице и в акте расходится на копейки, чем опасна шкала без уточнения режима и как читать "1.234". Плюс наш код под MIT.
Расхождение на копейку выглядит несерьёзно ровно до того момента, когда его находит франчайзи. Дальше это уже не копейка, а вопрос “а остальные цифры вы как считали”, и отвечать на него приходится по всем двенадцати месяцам.
Мы разбираем три места, где сумма роялти уезжает, и выкладываем код, которым мы эти места закрыли у себя.
Копейка, которой нет в компьютере
Число 0,1 в компьютере хранится не как 0,1. Оно хранится как ближайшая
двоичная дробь, и она чуть больше. Поэтому почти в любом языке
программирования 0,1 + 0,2 даёт не 0,3, а 0,30000000000000004.
Табличные редакторы этот хвост прячут: они округляют результат при выводе, и на экране вы видите ровное число. Но хранится там по-прежнему неровное, и в дальнейших вычислениях участвует именно оно.
На одном начислении этого не видно: результат округляется до копеек, и хвост пропадает. Видно становится на сумме за год, когда округлённые значения складываются, а рядом лежит другой отчёт, где сначала сложили, а потом округлили. Две правильные цифры, разница в несколько рублей, и объяснить её нельзя.
Лечится это тем, что деньги вообще не хранятся дробным числом. 1 480 000 рублей 00 копеек — это целое число 148 000 000 копеек. Ставка 5,5 % — это целое число 550 сотых процента. Умножение целых даёт целое, и единственное деление происходит один раз, в самом конце.
Отсюда практическое правило, которое стоит проверить в своём учёте: округлять нужно один раз, на итоговой сумме начисления, а не на каждом промежуточном шаге. Если в вашей таблице промежуточные колонки округлены, итог будет отличаться от честного расчёта, и отличаться в разные стороны от месяца к месяцу.
Отдельно стоит зафиксировать в договоре, как округляется половина копейки. Вариантов три: вверх (в пользу управляющей компании), вниз (в пользу франчайзи) и к чётному (меньше накапливает перекос на больших объёмах). Пока это не записано, каждая сторона считает по-своему и обе правы.
Прогрессивная шкала, которая значит две разные вещи
В договоре написано: “6 % с выручки до миллиона, свыше миллиона — 4 %”.
Это можно прочитать двумя способами.
Первый. Первый миллион считается по 6 %, всё что сверх — по 4 %. Как в подоходном налоге. Выручка 2 000 000 даёт 60 000 + 40 000 = 100 000 ₽.
Второй. Выручка попала в верхнюю ступень, значит вся она считается по 4 %. Те же 2 000 000 дают 80 000 ₽.
Разница — 20 000 ₽ на одной точке за один месяц. А на выручке ровно в миллион первый способ даёт 60 000 ₽, второй — 40 000 ₽: полтора раза.
Обе формулировки встречаются в реальных договорах, и обе законны. Проблема не в том, какая правильная, а в том, что в тексте договора это чаще всего не уточнено вовсе — и выясняется в первый же месяц, когда сеть и партнёр присылают друг другу разные цифры.
Проверьте свой договор прямо сейчас. Если там нет слов “к части выручки, превышающей”, формулировку стоит дописать.
Цифра, которую нельзя прочитать однозначно
Выручка приезжает в отчёте не числом. Она приезжает строкой, и вот что в ней бывает:
1 234 567,89— из 1С, причём пробелы там неразрывные, и обычное “убрать пробелы” их не убирает;1 234 567.89— из другой выгрузки, точкой;(1 234,00)— из бухгалтерской выгрузки, где скобки означают минус;1 234 567,89 ₽,…руб.,…RUB— руками.
Всё это читается. А вот 1.234 прочитать однозначно нельзя: это может быть
тысяча двести тридцать четыре, а может быть одна целая двести тридцать четыре
тысячных. Если ваш импорт угадывает такие случаи молча, однажды он угадает
неправильно, и узнаете вы об этом из акта.
Правило то же, что и с договором: лучше отвергнуть строку и попросить исправить, чем угадать. Отчёт сдаётся раз в месяц, лишний вопрос стоит дешевле неверного начисления.
Что мы выложили
Эти три места мы у себя закрыли и вынесли код в отдельные библиотеки под лицензией MIT. Их можно взять и пользоваться, не имея к нам никакого отношения:
- royalty-calc — расчёт роялти. Деньги целыми копейками, ставки целыми сотыми процента, четыре правила округления на выбор, произвольные формулы на точных дробях. На выходе не только сумма, но и построчное объяснение, как она получилась.
- revenue-report — разбор отчёта о выручке: суммы во всех видах выше, период словом (“сентябрь 2026”) и диапазоном, CSV-выгрузка из русского Excel. Проверка возвращает сразу все ошибки отчёта, а не первую попавшуюся.
Обе на TypeScript, без внешних зависимостей, с тестами — 243 теста на двоих.
Если GitHub у вас не открывается, тот же код лежит на российских площадках — GitFlic и GitVerse, вплоть до хеша коммита.
Схемы из договоров и виды расчёта
В разборе восьми схем роялти мы перечисляли то, что встречается в договорах. Вот как оно ложится на то, что умеет библиотека:
| Как написано в договоре | Что считает библиотека |
|---|---|
| Процент от выручки | процент от выручки |
| Фиксированная сумма | фиксированная сумма |
| Ступени по выручке | прогрессивная шкала, режим задаётся явно |
| Минимальная гарантия | процент не ниже фиксированного минимума |
| Роялти с точки | фиксированная сумма на точку |
| Роялти с площади | ставка за единицу объёма (за м²) |
| Процент с оборота закупок | формула по переменным отчёта |
| Комбинированная | фиксированная часть плюс процент |
Девятый вариант — “роялти не платится” — в библиотеке тоже есть отдельной схемой. Это не мелочь: в сетях бывают точки на каникулах, точки сотрудников и пилотные площадки, и обрабатывать их отсутствием схемы неудобнее, чем явным “ноль”.
Почему мы это отдали
Расчёт роялти сам по себе не преимущество. Преимущество в том, что вокруг него: кабинет, куда франчайзи сдаёт отчёт, документы, сроки, проверки. А на арифметике ошибаются все, и библиотека, которую читают посторонние, ошибается реже закрытой.
Пример из нашей же разработки. В библиотеке есть возможность задать
произвольную формулу — что-то вроде (выручка - возвраты) * 0.05. Формулу
пишет пользователь, поэтому разбирает её наш собственный код, а список
разрешённых функций закрыт. Тест, написанный до реализации, проверил формулу
constructor(1) — и она прошла проверку, потому что список лежал в обычном
объекте, а у любого объекта есть унаследованное свойство constructor.
Ошибка нашлась за минуту, потому что её искали. В закрытом коде её искал бы
только автор.
Если вы найдёте схему расчёта, которой у нас нет, или формат выгрузки, который библиотека не поняла, — напишите об этом в issue репозитория или на почту. Живой случай из чужой практики полезнее звезды.
Посчитать свою схему, ничего не устанавливая, можно в калькуляторе: те же восемь схем в браузере, без регистрации. Что проверить в самом договоре — в чек-листе.