Найти в Дзене
Геннадий Шушпанов

История одной ошибки

"Поиск не затягивает дело, а ошибки -- затягивают." (Им покоряется небо) Однажды перестали автоматически обрабатываться заказы. При ручной обработке было выявлено, что сумма заказа рассчитанная системой заказов отличалась от суммы заказа, рассчитанной в учетной системе на несколько копеек. Это касалось лишь тех заказов, число позиций в которых было больше одной. Причиной расхождения была ошибка в расчете величины новой комиссии. До этого, комиссионные сборы рассчитывались на каждую позицию в отдельности. Новая же комиссия относилась к заказу в целом. Тем не менее этот факт проигнорировали, возможно полагая, что если поделить, а потом сложить, то сумма останется той же. Ведь так быстрее, не надо создавать отдельную ветку для обработки этой комиссии. Но это в математике с ее абсолютной точностью, а в мире рублей и копеек это не так. Первый раунд борьбы выиграла ошибка. Ведь было так очевидно, что дело в округлении. И хотелось быстрей. Но оказалось, что если убрать округление, то ничего н
"Поиск не затягивает дело, а ошибки -- затягивают." (Им покоряется небо)

Считаем копейки

Однажды перестали автоматически обрабатываться заказы. При ручной обработке было выявлено, что сумма заказа рассчитанная системой заказов отличалась от суммы заказа, рассчитанной в учетной системе на несколько копеек. Это касалось лишь тех заказов, число позиций в которых было больше одной.

Причиной расхождения была ошибка в расчете величины новой комиссии. До этого, комиссионные сборы рассчитывались на каждую позицию в отдельности. Новая же комиссия относилась к заказу в целом. Тем не менее этот факт проигнорировали, возможно полагая, что если поделить, а потом сложить, то сумма останется той же. Ведь так быстрее, не надо создавать отдельную ветку для обработки этой комиссии. Но это в математике с ее абсолютной точностью, а в мире рублей и копеек это не так.

Первый раунд борьбы выиграла ошибка. Ведь было так очевидно, что дело в округлении. И хотелось быстрей. Но оказалось, что если убрать округление, то ничего не меняется. Пришлось копнуть глубже. Выяснилось, что до того момента, когда части суммы складывались, их значения записывались в базу данных, в поля с типом Numeric(10,2)... и округлялись до копеек. Казалось победа близка -- сделаем формат 10.3 и дело пойдет. Ведь надо быстрее. Но в результате новая неудача -- теперь расхождение проявлялось независимо от числа позиций. Поиск привел к двум "открытиям".

Последовательное округление. Разработчики для финансовых расчетов создали класс Money, основанный на BigDecimal установив точность расчетов в 4 десятичных знака в дробной части. Второй раз округление до трех знаков теперь происходило при записи в базу данных. И наконец при формировании страницы вызывалась toFixed(2), проводя округление до двух знаков. При такой последовательности числа вида X.324[5-9] получали в дробной части 33 вместо положенных 32.

Странности функции JS toFixed(). По определению эта функция преобразует число с плавающей точкой в строку содержащую десятичное представление числа с указанным числом десятичных знаков и округлением при необходимости. Однако порой она выдает неожиданный результат:

console.log(0.325.toFixed(2));
console.log(10.325.toFixed(2));
console.log(100.325.toFixed(2));
console.log(500.325.toFixed(2));
0.33
10.32
100.33
500.32

Причина кроется в том, что на самом деле используется не десятичное, а двоичное округление, размер мантиссы ограничен, а 0.325 в двоичной системе счисления бесконечная дробь. Поэтому двоичное 0.325 слегка больше десятичного, 10.325 -- слегка меньше. Всё это не имело значение, пока окончательное округление не выпало на долю toFixed.

"Каждый опыт несет свой урок" (Дюна).

Округление для финансовых расчетов критичная операция. Особенно, когда эти расчеты охватывают несколько систем. Следует обратить внимание на число хранимых разрядов и правила округления и согласовать эти параметры для всех систем.

Другой урок: "хотите быстрее достичь результата -- не спешите". Эпопея с копейками растянулась почти на год, потребовав несколько итераций. Вряд ли разработка дополнительного кода для обработки комиссии на заказ заняла бы более 2-4 недель.

Сгенерировано с помощью Шедеврум
Сгенерировано с помощью Шедеврум