Расчёт процентов по вкладу

Разбираем одну из самых частых COBOL-задач в банке: начислить проценты на остаток вклада.

Начисление процентов — это когда банк раз в месяц (или в год) добавляет к вашему вкладу небольшую сумму, рассчитанную от остатка на счёте и процентной ставки.

Зачем эта задача вообще существует

Представь банк, в котором миллион вкладчиков. Каждую ночь где-то в дата-центре запускается программа, которая проходит по всем счетам и досчитывает проценты — кому-то за месяц, кому-то за квартал. Эта программа не должна зависать, не должна ошибаться в копейках и должна отработать за отведённое окно времени, обычно несколько часов. Именно для таких задач и создавался COBOL ещё в 1959 году, и именно поэтому банки до сих пор доверяют ему миллиарды долларов расчётов.

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

Как хранят деньги в COBOL

Первое, что удивляет новичков: в COBOL для денег не используют обычные числа с плавающей точкой (как float в других языках). Дробные числа с плавающей точкой имеют свойство накапливать крошечные ошибки округления — и если так считать проценты миллиону вкладчиков, банк за год потеряет (или найдёт лишними) реальные деньги. Поэтому в COBOL деньги описывают через специальную конструкцию PIC (сокращение от picture — «картинка», то есть шаблон числа) с фиксированной точкой.

01  WS-VKLAD.
    05  WS-OSTATOK       PIC 9(7)V99.
    05  WS-STAVKA         PIC 9(2)V9(4).
    05  WS-PROCENTY       PIC 9(7)V99.
    05  WS-NOVYY-OSTATOK  PIC 9(7)V99.

Что здесь происходит. 01 — это уровень записи, в COBOL данные организуют в виде дерева, и группа WS-VKLAD объединяет все поля вклада. Уровень 05 — вложенные поля этой группы. PIC 9(7)V99 означает: 7 цифр до запятой (9(7) — семь девяток подряд, каждая девятка — это одна десятичная цифра), буква V — это подразумеваемая десятичная точка (она не хранится физически, компьютер просто «помнит», где она должна быть), и ещё 2 цифры после неё. Итого поле WS-OSTATOK может хранить сумму от 0 до 9 999 999,99 — вполне достаточно для остатка на вкладе. А WS-STAVKA PIC 9(2)V9(4) хранит процентную ставку вроде 7,5000% с четырьмя знаками после запятой — так точнее, чем в обычных деньгах.

Сам расчёт: оператор COMPUTE

Теперь, когда поля описаны, посчитаем проценты. В COBOL для арифметики есть отдельный оператор COMPUTE — он позволяет писать формулы почти как в математике, вместо того чтобы городить отдельные ADD, MULTIPLY и DIVIDE команды одна за другой.

MOVE 15000.00 TO WS-OSTATOK.
MOVE 7.5000   TO WS-STAVKA.

COMPUTE WS-PROCENTY = WS-OSTATOK * WS-STAVKA / 100
    ON SIZE ERROR
        DISPLAY "ОШИБКА: переполнение при расчёте процентов"
END-COMPUTE.

COMPUTE WS-NOVYY-OSTATOK = WS-OSTATOK + WS-PROCENTY.

DISPLAY "Остаток: "    WS-OSTATOK.
DISPLAY "Проценты: "   WS-PROCENTY.
DISPLAY "Новый остаток: " WS-NOVYY-OSTATOK.

Разберём построчно. MOVE 15000.00 TO WS-OSTATOK кладёт остаток 15 000 рублей в поле — MOVE в COBOL это как присваивание = в других языках, только читается «переместить значение туда-то». Дальше COMPUTE WS-PROCENTY = WS-OSTATOK * WS-STAVKA / 100 — это и есть формула процентов: остаток умножаем на ставку и делим на 100 (потому что ставка задана в процентах, а не в долях). Обрати внимание на конструкцию ON SIZE ERROR — это встроенная защита COBOL: если результат вычисления не поместится в отведённое под него поле (например, если бы у нас было слишком много цифр), программа не упадёт молча и не обрежет число — она выполнит блок ON SIZE ERROR и сообщит об ошибке. Такая защита от переполнения — не необязательная роскошь, а стандартная практика в банковском коде, потому что «тихо потерянные» деньги в отчёте недопустимы. Заканчивается блок словом END-COMPUTE — в COBOL многие операторы имеют такие явные «закрывающие» слова, чтобы не было путаницы, где заканчивается один блок кода и начинается следующий код программы.

Вывод:

Остаток: 0015000.00
Проценты: 0001125.00
Новый остаток: 0016125.00

Обрати внимание: числа выводятся с ведущими нулями (0015000.00 вместо 15000.00) — это прямое следствие того, как мы описали поле через PIC 9(7)V99: оно всегда занимает ровно 7 цифр до точки, независимо от того, сколько цифр реально «значащие». Для банковских отчётов это даже удобно — все суммы в колонке выровнены по ширине.

Практический пример: расчёт для нескольких вкладов

А теперь усложним: пусть у нас не один вклад, а несколько, с разными ставками в зависимости от типа вклада. Это уже ближе к реальности — банк не начисляет всем одинаковый процент.

01  WS-TIP-VKLADA     PIC X(10).
01  WS-STAVKA-ITOG    PIC 9(2)V9(4).

EVALUATE WS-TIP-VKLADA
    WHEN "STANDARD"
        MOVE 5.5000 TO WS-STAVKA-ITOG
    WHEN "PREMIUM"
        MOVE 8.2000 TO WS-STAVKA-ITOG
    WHEN "NAKOPIT"
        MOVE 9.7500 TO WS-STAVKA-ITOG
    WHEN OTHER
        MOVE 0.0000 TO WS-STAVKA-ITOG
        DISPLAY "Неизвестный тип вклада: " WS-TIP-VKLADA
END-EVALUATE.

EVALUATE — это аналог switch или конструкции «если-иначе-если» из других языков, только по-COBOLовски многословный и очень читаемый. Он проверяет значение поля WS-TIP-VKLADA (тип X(10) означает текстовую строку из 10 символов, буква X — «любой символ», в отличие от 9, которая означает только цифры) и в зависимости от него подставляет нужную ставку. Ветка WHEN OTHER обязательно нужна на случай, если в данные попало что-то непредвиденное — опечатка в справочнике или битые данные из другой системы. Без такой защитной ветки программа могла бы просто пропустить расчёт молча, а на банковском мейнфрейме молчаливо пропущенный расчёт — это прямая финансовая ошибка, которую потом неделями ищут аудиторы.

Частые ошибки

Первая и самая обидная ошибка — забыть про ON SIZE ERROR. Без него при переполнении COBOL по умолчанию просто обрежет старшие разряды числа, и в отчёте окажется неверная сумма без единого предупреждения. Вторая ошибка — перепутать V (подразумеваемая точка) с реальной десятичной точкой в описании PIC: если написать PIC 9(7).99 вместо PIC 9(7)V99, компилятор выдаст ошибку, потому что точка в PIC-описании имеет другой смысл. Третья частая ошибка новичков — забыть, что деление в целочисленной арифметике COBOL может терять точность, если не задать достаточно знаков после запятой у промежуточных полей: если посчитать проценты с точностью до 2 знаков, а потом умножить результат на что-то ещё, накопленная погрешность может быть заметна на больших суммах. Поэтому в банковских программах промежуточные поля обычно делают точнее, чем финальный результат, и округляют только на последнем шаге.

Итоги

  • Деньги в COBOL хранят в полях с фиксированной точкой через PIC 9(n)V9(m) — это защищает от ошибок округления, которые копятся при работе с миллионами счетов.
  • Оператор COMPUTE считает формулы, а ON SIZE ERROR — обязательная защита от переполнения в финансовых расчётах.
  • EVALUATE — удобный способ выбрать ставку или логику в зависимости от типа вклада, с обязательной веткой WHEN OTHER для непредвиденных данных.
  • В банковском коде важнее не скорость написания, а предсказуемость и отсутствие тихих ошибок — отсюда вся эта «многословность» COBOL.
Проверьте себя
1. Зачем в COBOL для хранения денег используют PIC 9(7)V99, а не обычное дробное число?
AТак короче писать код
BЭто защищает от накопления ошибок округления при массовых расчётах
CИначе COBOL не умеет хранить числа
DЭто требование российского законодательства
2. Что произойдёт при COMPUTE без ON SIZE ERROR, если результат не помещается в поле?
AПрограмма обязательно упадёт с остановкой
BРезультат обрежется без предупреждения, и это может остаться незамеченным
CCOBOL автоматически увеличит размер поля
DНичего, COBOL сам расширяет любые числа