Банківська округлення виявляється, є й таке

Пару місяців назад потрібно було написати на php невеликий скрипт, який повинен був підраховувати велику кількість фінансових даних - брати від сум банківських переказів відсоток і підраховувати суму. При обчисленні відсотків з'являлися суми з десяти- і стотисячним частками значень, які при подальшому підсумовуванні і округленні давали досить цікаві ефекти. Наприклад, скрипт генерував звіт, в якому суми вихідні і розраховані розрізнялися на 10-20 копійок. Почав розбиратися з цим, і відкрив для себе новий тип окрулегія - банківське округлення.

Банківська округлення виявляється, є й таке

Спочатку я пішов неправильним шляхом - почав проганяти розраховані відсотки через функцію round. Але це не допомогло. Все одно були розбіжності на копійки. Довелося засісти за читання теорії, і всього через 10 хвилин з'ясувалося, що для боротьби з такими ефектами застосовується метод «бухгалтерського округлення», або за кордоном - «bank rounding».

Спосіб простий - якщо тисячні частки рубля рівні 5, то виглядає цифра перед нею (одиниці копійок). Якщо вона парна, то тисячні просто відкидаються, а якщо непарна - то копійки округлюються вгору. Тобто в порівнянні з математичним округленням різниця така (це тестовий масив чисел, підрахованих руками).

4.565335, // bank: 4.56, math: 4.57
34.565783, // bank: 34.56, math: 34.57
56.355532, // bank: 56.36, math: 56.36
9.4557642, // bank: 9.45, math: 9.45
10.345643, // bank: 10.34, math: 10.35
7.235345, // bank: 7.24, math: 7.24
9.285456, // bank: 9.28, math: 9.29
3.225, // bank: 3.22, math: 3.23
10.25527, // bank: 10.26, math: 10.26
11.41525, // bank: 11.42, math: 11.42
0.105, // bank: 0.10, math: 0.11
0.115, // bank: 0.12, math 0.12

Насправді, такий підхід не знімає проблему округлення повністю, а тільки значно скорочує вірогідність накопичення помилки за рахунок того, що кількість «парних» і «непарних» сум статистично приблизно однаково. Але на практиці такий точності цілком достатньо. Після використання цієї функції, по крайней мере видимі розбіжності в скрипті припинилися.

Залишилося тільки знайти реалізацію цієї функції в Інтернеті, і справу зроблено. Звичайно, алгоритм найпростіший, але завжди краще покладатися на чийсь успішний досвід. А ось тут і чекав мене мега облом. Я не знайшов реалізацію цієї функції для PHP! Вірніше, знайшов одну на StackOverflow, але вона працювала через перетворення в рядок і виділення функцією substr десятих часток суми. Для друіх мов знаходив реалізації через логарифми.

Мене це не влаштувало, тому написав за хвилину свою функцію. Пояснювати її сенсу немає, вона «прозора».

function bank_round ($ val)
$ Tmp = intval (abs ($ val) * 100);
if ($ tmp% 2! = 0) $ tmp + = 1;
if ($ val <0) $tmp = 0 — $tmp;
return $ tmp / 100;
>

Результат її виконання:

Банківська округлення виявляється, є й таке

Трохи поясню цей результат.

  • Перший стовпець - це сирі дані з великим числом цифр після коми. У перший рядок «разом» (під «------») записана їх арифметична сума, а у другому рядку нижче - результат математичного округлення.
  • Другий стовпець, це сума чисел, кожне з яких було округлено за математичними правилами. Як видно, такий підхід дає розбіжність в 0,4.
  • І, нарешті, третій стовпець - це сума чисел, кожне з яких було округлено за правилами «банківського округлення». Результат, як говориться, у наявності.

Загалом - функція примітивна, але раптом кому досвід в нагоді?