Округлення чисел: є більше ніж одна правильна відповідь

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

Розглянемо цей код на C#:

Math.Round(1.25, 1) // Повертає 1.2  
Math.Round(1.35, 1) // Повертає 1.4

Це поведінка може вивести з себе розробників, бо вона суперечить тому, чого нас навчали в школі. Ми всі були навчені, що 5 завжди округляється в більшу сторону. То чому ж C# округляє 1.25 вниз до 1.2?

Відповідь криється в стандарті IEEE 754, який говорить, що числа, що закінчуються на .5, повинні округлятися до найближчої парної цифри. Це також називається округленням за правилами банків (banker’s rounding). Тому 1.25 округляється до 1.2 (2 — парне число), а 1.35 — до 1.4 (4 — парне число).

Ось таблиця з більш детальними правилами:

+--------------------------------------------------+-------+-------------+  
| Цифра після позиції округлення | Округлення | Приклад |  
|--------------------------------------------------|-------|-------------|  
| Від `0` до `4` | ВНИЗ | 1.24 → 1.2 |  
| `5` є останньою цифрою, а попередня цифра парна | ВНИЗ | 1.25 → 1.2 |  
| `5` є останньою цифрою, а попередня цифра непарна | ВГОРУ | 1.35 → 1.4 |  
| `5` *не* є *останньою цифрою* | ВГОРУ | 1.251 → 1.3 |  
| Від `6` до `9` | ВГОРУ | 1.26 → 1.3 |  
+--------------------------------------------------+-------+-------------+

Хоча це і здається академічною деталлю, насправді це може спричиняти проблеми у виробничих системах. Особливо вразливими є кросплатформні додатки — JavaScript за замовчуванням округляє 5 в більшу сторону (це називається «округлення в бік нуля»), а C# округляє до парного. Навіть Excel і Google Sheets мають ті самі правила округлення за замовчуванням. Ця маленька різниця може спричинити систематичні помилки, які важко виявити.

Підхід IEEE має обґрунтовані математичні причини. Традиційне «округлення в більшу сторону при 5» створює позитивний ухил, оскільки кожен 5 округляється в більшу сторону. Округлення до парного числа усуває цей ухил, округляючи то в більшу, то в меншу сторону. Для масштабних систем, що обробляють мільйони значень, це статистичне справедливе округлення має велике значення.

Але математична елегантність не допомагає, коли ваші фінансові звіти не збігаються між системами. Саме тому C# надає нам «шлях втечі»:

// Традиційне округлення, яке дійсно відповідає тому, що очікують люди  
Math.Round(1.25, 1, MidpointRounding.AwayFromZero) // Повертає 1.3

Справжня проблема не в тому, яке округлення є «правильним» — всі вони мають свої валідні випадки використання. Банківські системи потребують статистичної справедливості. Інтерфейси користувачів можуть потребувати округлення відповідно до людських очікувань. Наукові обчислення іноді потребують відсічення замість округлення. Є більше ніж два методи.

Проблема в тому, що більшість розробників не роблять свідомого вибору. Вони використовують стандартну поведінку мови, не усвідомлюючи, що це може не відповідати їхнім вимогам. І коли вони це помічають, зазвичай це вже після того, як щось пішло не так у виробничому середовищі.

Тому зробіть вибір явним. Якщо ви розробляєте фінансове програмне забезпечення, визначте заздалегідь, як будете обробляти округлення. Документуйте це. Тестуйте крайні випадки. І що б ви не робили, не припускайте, що Math.Round() працює так, як вас навчили в школі.

Оригінал опубліковано на https://www.npiontko.pro

Перекладено з: Rounding Numbers: There’s More Than One Right Answer

Leave a Reply

Your email address will not be published. Required fields are marked *