Загадки округлення, Різне, Програмування, статті

Автор: Олексій Михайличенко, Королівство Delphi


Ця стаття замислювалася як коротка відповідь на питання N 40789, де обговорювалися помилки у функціях округлення. Як сказав автор, “мені все одно що” бухгалтерське “, що” арифметичне “- головне щоб вважало також як в Excel “е”. Іншими словами, виникла необхідність прояснити, чим відрізняється бухгалтерське округлення від арифметичного, яке з них реалізовано в Excel “e – це” генії чистої краси “, і чому деякі Delphi-функції дивним чином працюють інакше.


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



  1. Дуже коротко згадаємо, чому обчислення на комп’ютері можуть давати несподівані результати. Наведемо деякі посилання.

  2. Подумаємо, як можна з цим боротися. Для цього розглянемо правила наближених обчислень.

  3. Перейдемо власне до правил округлення. Особливу увагу приділимо так званого “банківського”, або “бухгалтерського” округленню, і покажемо, навіщо воно потрібне, і які ще способи округлення існують.

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

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

Отже, всі ми свого часу виявляли, (а якщо ні – то вас ще чекає це хвилююче відкриття), що 0.1 в комп’ютері не дорівнює 0.1. Читали статтю “Неочевидні особливості речових чисел” Антона Григор’єва, жахалися результатами порівняння та вирахування extended, занурювалися в нетрі внутрішнього подання чисел з плаваючою комою і роздумували про нескінченні цифрових хвостах. Цікавиться цієї кухнею порекомендуємо класику – Дональд Кнут: “Мистецтво програмування”, том 2, Глава 4. “Арифметика”, де захоплююче описана історія позиційних систем числення, і в загальному вигляді розглянуті алгоритми арифметики з плаваючою комою. Вдало доповнює її стаття Девіда Голдберга “Що повинен знати кожен вчений-комп’ютерник про про арифметику з плаваючою комою”. У ній наводяться деякі теореми, що дозволяють оцінити величину помилок, що виникають в машинної арифметики, розглядаються відповідні IEEE-стандарти, і питання їх реалізації.


Повертаючись з цих наукових світів в реальне життя, доводиться зробити невтішний висновок: у порівнянні зі шкільним курсом арифметики комп’ютери вважають неточно. Ця думка досить важко вкладається в голові. Дійсно, з перших уроків математики нам показували рулетки, лінійки, штангенциркулі і мікрометри, і говорили про те, що всі вимірювання мають похибку. Демонстрували картинки з оптичними обманами і говорили про недосконалість органів почуттів. І на противагу цьому звеличували саму математику, її арифметичні дії, як символ непорушною правильності. Адже ще Піфагор зрозумів, що числа існують незалежно від нашої свідомості, і логічні міркування над ними переживуть століття. У народі це відбилося в приказці “як двічі два – чотири” – тобто свідомо вірно.


Щоб зняти з комп’ютера незаслужений ореол наукової святості, уявімо собі, що це всього лише один з шкільних вимірювальних приладів, зі своїми обмеженнями і деяким класом точності. А всередині нього відбувається ось що. Наприклад, нехай нам потрібно скласти два числа: 10 і 20. Береться шкільна лінійка і огірок. Відрізається від огірка два шматочки: 10 і 20 мм довжиною, складається вздовж, і цієї ж кривуватою лінійкою вимірюється довжина результату. Отримуємо відповідь з деякою погрішністю. Саме з таким ступенем довіри нам доведеться ставитися до результатів комп’ютерних обчислень з плаваючою комою.


Іноді доводиться чути, ніби комп’ютери вважають наближено. Але це зовсім не так. Для обговорення цього питання терміново визначимося з термінами.


Наближені обчислення – давній і поважний розділ математики. Він дає набір якихось практичних правил з обробки даних, отриманих нашими недосконалими вимірювальними і обчислювальними приладами. Саме дотримуючись цих правил, наша цивілізація вершила справжні дива – будувала храми і мости, пророкувала затемнення, літала на фанерних аеропланах і гасових ракетах і стріляла з гармат за горизонт. І все це – зауважте – абсолютно без допомоги комп’ютерів. Що ж це за такий чудові правила?


Згідно з Правилами наближених обчислень (ППВ), кожному числу, отриманому в результаті вимірювань або обчислень, невідривно супроводжує якась величина, що характеризує його точність. Ця величина може бути виражена абсолютної похибкою, наприклад (10 + / – 1); відносною похибкою (10 + / – 10%); кількістю значущих цифр, яке зазвичай виражається в запису (1.000); або записом числа у вигляді інтервалу можливих значень (99 .. 101). Ці форми запису в общем-то еквівалентні, і можуть бути отримані одна з іншою. Суть в тому, що ми:



  1. Маємо на вході наближені числа з відомою похибкою.

  2. Виробляємо над ними арифметичні дії як з точними числами

  3. На виході маємо результат також з відомою похибкою.

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


Повернемося до наших баранів – комп’ютерам. Що роблять вони?



  1. Мають на вході деякі числа.

  2. Вносять в них деяку похибка навіть при присвоєння змінним, як результат особливостей внутрішнього уявлення.

  3. Проводять над ними арифметичні дії, вносячи деякі похибки як результат реалізації обчислювача

  4. На виході мають неточний результат з невідомою похибкою.

Як бачимо, на алгоритми ППВ схоже з точністю до навпаки.


Що ж за дивну завдання вирішує комп’ютер, і кому вона така потрібна?


Подібний порядок обчислень має сенс лише тоді, коли контроль точності результату ведеться окремо, або закладений в саму задачу. У першому випадку нам необхідно паралельно з формулами обчислень написати формули обчислення похибки. Ілюстрацією другого може бути рішення якогось рівняння чисельними методами, коли беремо результат в “артилерійську вилку”, і итеративно наближаємося до нього з обох сторін, припиняючи обчислення при досягненні кроком ітерації якогось малого значення. Звичайно, для вирішення обліково-бухгалтерських завдань такі підходи виглядають екзотично. Але якщо згадати, що перші комп’ютери використовувалися в основному для вирішення суто наукових або військових завдань (наприклад, балістики), то стане зрозуміло, що іншого від комп’ютерів тоді й не було потрібно.


Вчені того часу звикли до норовливої ​​обчислювальної техніки, і враховували це у відповідних математичних моделях. Уявіть, наприклад, балістичну ракету. Десятиметрова бандура, зроблена з жерсті. Вона не стоїть, а швидше висить на захопленні стартовою вишки, бо жорсткості у неї ніякої – вона грає як гумова. Включаються двигуни, захоплення відпускають, і уявіть, що ви вертикально тримайте її на кінчику пальця, як олівець. Олівець норовить впасти, ви ловите відхилення і підсовує палець. А ракета, крім того, реве двигунами, норовить скластися навпіл, скрутитися по спіралі, та й потрібно її не просто утримати, а запустити саме в задану сторону. Все це норовливість враховується в математичній моделі стійкості, вивертається навиворіт і кладеться в систему стабілізації і наведення – ящик між гіроскопами і двигунами. А перед тим ганяємо математичну модель ракети на аналогової ЕОМ – наборі операційних підсилювачів і деталюшек, виготовлених з точністю в кращому випадку 0.1%. І все працює. Ну і скажіть, чи має значення деяка похибка, якщо пихкаюче аналогову ЕОМ замінити не зовсім точною цифрової? Якщо вихідна математика правильна і зворотні зв’язки моделі відпрацьовую вірно, то все буде працювати в будь-якому випадку. Тому не лайте комп’ютери – при правильному підході з них можна отримати чималу користь.


Пройшли часи залізних людей, які керують залізними комп’ютерами. Тепер ми хочемо вважати на машинах кілограми, штуки, і звичайно ж гроші. І щоб все сходилося до копієчки. А floating-point-обчислювачі-то залишилися колишніми – пахнуть ракетної кіптявою та наукової романтикою. І якщо ми хочемо отримати передбачувані результати і нести за них якусь відповідальність, то нам в наших програмах доведеться повернутися до Правил наближених обчислень, і спробувати організувати їх самостійно, не сподіваючись на комп’ютер, а використовуючи його як допоміжний інструмент.


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



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

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

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

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

  5. У всіх проміжних результатах слід зберігати однією цифрою більше, ніж рекомендують попередні правила. В остаточному результаті ця “запасна” цифра відкидається.

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

  7. Якщо дані можна брати з довільною точністю, то для отримання результату з K цифрами дані слід брати з таким числом цифр, яке дає згідно з правилами 1-4 (К +1) цифру в результаті.

Тут доречно навести вислів видатного інженера-кораблебудівника академіка Крилова, який говорив: “Зайва обчислена цифра є ПОМИЛКА”. Всупереч поширеній думці, тягання хвостів незначущих цифр замість їх округлення зовсім не підвищує точності обчислень, а навпаки – може привести до накопичення помилок в найнесподіваніших місцях. Ну і крім цього, мова йде про елементарну грамотності та акуратності – При виведенні результату 10,1230 зайві цифри можуть створити враження їх достовірності, скажімо, до четвертого знака, в той час як вихідні дані задавалися плюс-мінус личак. А це вже загрожує звалилися мостами і підірваними реакторами. Недостовірні розряди слід округляти. Тому перейдемо до фундаментальної операції наближених обчислень – округленню.


Згідно шкільного курсу математики, при округленні чисел ми відкидаємо непотрібні розряди, причому якщо перша відкидається цифра більше або дорівнює 5, то остання зберігається цифра збільшується на одиницю. Будемо називати цей спосіб “Арифметичні округленням”.


Недовірливий читач, напевно, вже запідозрив, що зараз піде мова і про інші способи округлення, і тягне руку з питанням: а навіщо, власне? Чим не влаштовує цей, настільки рідний і знайомий?


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


Контора заробила рівно мільйон рублів, і доручила бухгалтеру розділити їх на тисячу працівників пропорційно коефіцієнту трудової участі (КТУ). Той виконав арифметичні дії і отримав для кожного працівника деяке число Ni, з деяким хвостиком дробових копійок. Переконався, що сума всіх Ni дає рівно мільйон (ми опускаємо проблеми неподільності нацело і нескінченних дробів – нехай хоч сьогодні у нас все поділилося). Розрахував суму до видачі – округлив кожне Ni до копійок, і підбив підсумок. І що ж він бачить?


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


КТУ в конторі за старою радянською традицією ставили зі стелі, тому він досить добре підкорявся закону розподілу випадкових чисел. Відповідно, коли справа доходила до округлення, то серед “перших відкидаємо цифр “було приблизно порівну нулів, одиничок, двійок і всіх інших цифр. Кожна операція округлення вносила свою похибка (різницю між початковим і округленим значенням) залежно від відкинутого хвостика. При цьому похибки відкинутих одиничок (-0.001) компенсувалися похибками дев’яток (+0.001), двійки компенсували вісімки, і так далі, і лише похибки, що вносяться при відкиданні п’ятірок (+0.005), залишалися нескомпенсованими, і накопичувалися. У середньому на тисячу чоловік зустрілося 100 операцій відкидання п’ятірки, кожна з яких дала похибка пів-копійки. Звідси й набігли злощасні 50 копійок.


Ось фрагмент розрахункової відомості, що демонструє набігання однієї копійки при роздачі суми в 2000 руб.21 коп.:


 



















































































































































КТУ


Розрахунок (Ni)


До видачі


Похибка


1


100001


100.0010


100.00


-0.0010


2


100002


100.0020


100.00


-0.0020


3


100003


100.0030


100.00


-0.0030


4


100004


100.0040


100.00


-0.0040


5


100005


100.0050


100.01


0.0050 


6


100006


100.0060


100.01


0.0040


7


100007


100.0070


100.01


0.0030


8


100008


100.0080


100.01


0.0020


9


100009


100.0090


100.01


0.0010


10


100010


100.0100


100.01


0.0000


11


100011


100.0110


100.01


-0.0010


12


100012


100.0120


100.01


-0.0020


13


100013


100.0130


100.01


-0.0030


14


100014


100.0140


100.01


-0.0040


15


100015


100.0150


100.02


0.0050 


16


100016


100.0160


100.02


0.0040


17


100017


100.0170


100.02


0.0030


18


100018


100.0180


100.02


0.0020


19


100019


100.0190


100.02


0.0010


20


100020


100.0200


100.02


0.0000


2000.21 


2000.22 


0.01


Якби бухгалтер був магом і чарівником, він безсумнівно вирішив би проблему так, щоб який-небудь шаблезубий тигр відкусив руку, або хоча б непарна кількість пальців нашому волосатому пращуру, придумав десяткову систему числення, щоб в ній не залишилося “середини”. Але він викрутився хитріше – половину відкидаємо п’ятірок став округляти вгору, а половину – вниз. Щоб його не звинуватили в особистих пристрастях, критерієм стала цифра перед п’ятіркою – якщо вона парна, то округлення вниз, інакше вгору. Це правило і називається правилом “Бухгалтерського” (або “Банківського”) округлення.


У нашому прикладі в рядку 5 сума стала округлятися до 100.00, що вноситься похибка стала -0.005, компенсувати рядок 15, і сума до видачі збіглася з вихідною.


Тепер, коли у нас є більше одного способу округлення, виникає одвічне запитання: а який з них правильний?


Цікаво, що в обговоренні цього питання сперечальники зазвичай начисто забувають про область застосування алгоритму, і взагалі будь-яких критеріях правильності, а шукають якусь правильність в метафізичному, вселенському сенсі. Доводилося зустрічати думку, що банківське округлення характерно для капіталістичних країн, а арифметичне – для СРСР. Інші доводили, що арифметичному вчать в школі, а банківському – в ВУЗах. Коли з’ясовувалося, що в деяких інститутах теж застосовують арифметичне, на повному серйозі становили “чорний список” таких ВНЗ і піддавали їх осміянню. Треті говорили, що це баг від Microsoft, або глюк всіх Pentium-ів (або AMD, в залежності від особистих пристрастей). Тому хотілося б знати, чи існує якийсь загальноприйнятий документ щодо способів округлення. І такий документ дійсно існує. Це знаменитий стандарт IEEE 754.


Цікава історія створення цього документа. У 60-ті – 70-і роки, коли комп’ютери були великими, кожна лінійка комп’ютерів мала свою програмну реалізацію обчислень з плаваючою комою, свої формати подання чисел, точність, представимо діапазони і правила округлення. Відповідно, чудеса, на зразок описаних в “неочевидних особливості речових чисел”, були в кожного свої. За спогадами старожилів, на деяких машинах число могло виглядати відмінним від нуля в операціях порівняння та додавання, але бути чистим нулем при множенні і діленні. Щоб без страху поділити на таке число, його слід було помножити на 1.0 і лише потім порівняти з нулем. А інші машини могли видати помилку переповнення при множенні на 1.0 цілком нормального числа. Були такі малесенькі числа (але не нулі), які давали переповнення при діленні на самих себе. У програмах були звичайними шаманські вставки на кшталт X = (X + X) – X. Відповідно, одна і та ж програма, навіть написана на стандартному FORTRAN “е, могла давати різні результати на різних машинах.


Для вирішення цієї проблеми в середині 70-х під егідою IEEE неквапливо почав роботу комітет з вироблення стандарту 754 – про реалізаціях обчислень з плаваючою комою. Приблизно в цей же час Intel почав розробку арифметичного співпроцесора i8087 для своїх процесорів i8086/88. В якості консультанта був запрошений професор Вільям Каган, відомий успішною співпрацею з Hewlett-Packard.


Проект підходив до завершення. В цей співпроцесор вдалося втиснути все краще, що було на той момент. Професор Каган вирішив взяти участь в роботі комітету IEEE, отримав від Intel дозвіл відкрити деякі специфікації нового співпроцесора (без розкриття подробиць його реалізації), і представив їх як проект стандарту. Враховуючи, що у конкурентів співпроцесори були поки лише в планах, Intel-івські специфікації вигідно відрізнялися продуманістю і завершеністю. Крити було нічим. Проект де-факто ліг в основу стандарту.


Текст стандарту по ідеї можна отримати в першоджерелі (ieee.org), Але зазвичай посилаються на збірку пов’язаної з ним інформації від IBM (www2.hursley.ibm.com/decimal/).


Цей стандарт описує п’ять способів округлення, обов’язкових для реалізації, і два опціональних.



А адже це далеко не всі способи, які можна уявити. Запитаєте, навіщо потрібно більше? Відповімо.


Банківське округлення – зовсім не панацея для придушення статистичної похибки. Вона врятувала нашого бухгалтера лише тому, що округляються числа були досить випадкові, тобто поява парних і непарних цифр перед п’ятіркою було равновероятно. Але прийшли в контору нові часи, і КТУ стали хитро вираховувати на підставі витраченого робочого часу. За безглуздого збігу, для стандартного робочого місяця це виявилося одно 100005 (як в рядку 5). А так як більшість людей працюють без прогулів, то значення це стало зустрічатися дуже часто, і підсумок “До видачі” знову виявився більшим, ніж “Зароблено”.


Для вирішення цієї проблеми відомі наступні алгоритми:



  1. Random Rounding. Округляти відкидаємо п’ятірки вгору або вниз випадковим чином. В принципі, задовільно пригнічує статистичну похибку навіть на невипадкових наборах даних. Прикрі побічні ефекти – непередбачуваність і неповторяемость результату. Може бути, цей метод і застосуємо в якихось науково-статистичних процедурах обробки інформації, але в бухгалтерії він навряд чи приживеться.

  2. Alternate Rounding. При кожному черговому виклику для округлення п’ятірки округляти черзі – один раз вгору, один раз униз. Зрозуміло, що для цього доведеться зберігати стан функції між викликами. Хоча результат її роботи не вдасться пояснити кожному конкретному роботязі з операційної відомості (чому у нас з сусідом КТУ однаковий, а мені до видачі на копійку менше), але принаймні результат розрахунків буде повторюємо.

  3. Нарахування по ланцюжку. Суть в тому, що після округлення першого рядка відомості отримана сума “до видачі” віднімається із загальної распределяемой суми (мільйони рублів). Перший рядок як би відкидається з розгляду, і залишок відомості перераховується виходячи із залишку распределяемой суми на що залишилися 999 чоловік. Після округлення наступного рядка вона знову відкидається, і так далі. Цей алгоритм гарантує, що підсумок округлених сум “до видачі” обов’язково зійдеться з вихідною (неокругленних) сумою при будь-якому наборі даних. Недолік його в тому, що він працює тільки “купою” – перерахувати одного людини буде неможливо, і знову ж таки для однакових КТУ округлення може статися по різному.

Думаю, за такої великої кількості алгоритмів питання “який з них єдино вірний” ставити якось незручно. Правда, IEEE 754 вимагає, щоб проміжні результати обчислень округлювалися по-банківський. Але стандарт цей стосується реалізаторів співпроцесорів, і покликаний лише забезпечити переносимість програм в сенсі однаковості результатів на різних системах, а про бухгалтерію там нічого немає. Тому постановники і розробники повинні самі опрацювати це питання, і вибрати відповідний алгоритм. Але чим керуватися? Нормативні документи рідко опускаються до таких “дрібниць”. Тому на практиці звичайно спонтанно використовують арифметичне або бухгалтерське округлення – яке реалізовано у мові, а при розрахунках з рідною державою – вважають частки копійки на його користь, від гріха подалі – інакше дорожче вийде, якщо перевіряючі почнуть прораховувати контрольні приклади.


Щоб відпустити на обід нашого багатостраждального бухгалтера, обговоримо останній на сьогодні аспект розподілу грошей. Для програміста він підступний тим, що зовні виглядає як проблема з округленням – розбіжність підсумку округлених сум з вихідною. Тому бухгалтери нерідко беруть розрахункову відомість, і, як сказав класик, “ейною мордою починають мені в харю тикати”. А проблема не стосується ні машинної арифметики, ні алгоритму округлення. Сформулювати її можна так: якщо троє домовилися ділити дохід порівну, а заробили 10 копійок, то як бути з зайвою копійкою?


Правильна відповідь – вирішити це питання повинна сама бухгалтерія. Варіантів можна запропонувати три:



  1. Різницю, що виникають в результаті помилок неподільності, слід відносити на фінансові результати. Для виявлення суми різниці сформувати спеціальний звіряльні звіт, (окремо по витраті і приходу або за видами операцій), який показує точні суми прибуткування / списання, і округлені суми, прийняті до обліку. На виявлену сумову різницю слід зробити проводку по бухгалтерській довідці.

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

  3. Використовувати описаний вище спосіб нарахування по ланцюжку. Крім питань з округленням, він вирішує і цю проблему. Правда, зайва копійка буде віддана останнього (а в загальному випадку – невідомо кому). Самі вирішуйте, коли це припустимо.

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





 












































Мова
 


Функція арифметичного округлення
 


Функція бухгалтерського округлення
 


Excel
 


ОКРУГЛ (викликається із списку функцій для використання в формулах таблиці)
 


Round (функція VBA, яка використовується в макросах)
 


FoxPro 2.6 (DOS)
 


ROUND
 



 


Perl
 


Math::Round::round
 


Math::Round::round_even


MySQL
 


ROUND – алгоритм залежить від системних бібліотек. Може виявитися зовсім не арифметичним і не бухгалтерським.
 


FLOOR(n * 100 + 0.500001 ) / 100
 


PostgreSQL
 


ROUND
 



 


Delphi
 


SimpleRoundTo (працює з помилками)
 


RoundTo (працює з помилками)


RoundTo(n + 0.000001, -2)
 


StrToFloat(FloatToStr(n, ffFixed, 15, 2))
 


Trunc (n * 100 + 0.5) / 100 при SetRoundMode (rmUp)
 


DecimalRoundExt(n, 2, drHalfUp) by John Herbster


DecimalRoundExt(n, 2, drHalfEven) by John Herbster

 

Думаю, найбільша увага читачів привернула рядок з вбудованими функціями Delphi. Обговоримо їх.


Насамперед зазначимо: на роботу функцій Delphi сильно впливає установка режиму округлення процесора командою SetRoundMode. У документації лише невиразно згадано її вплив на функцію RoundTo, але на самому ділі вона впливає і на SimpleRoundTo, і вооще практично на всі результати обчислень. У зв’язку з цим категорично не рекомендується змінювати режим округлення, а при необхідності – робити це лише короткочасно, і тут же повертати в значення за замовчуванням (rmNearest). Прикладом безглуздого впливу SetRoundMode може служити функція EndOfTheMonth, яка за документацією повертає останній момент поточного місяця, а при SetRoundMode (rmUp) – починає повертати перший момент такого. Доводилося чути також про проблеми з незрозумілими помилками “Invalid floating point operations” всередині ADO, пов’язані з відзнакою RoundMode від стандартного.


Отже, згідно з документацією, SimpleRoundTo реалізує арифметичне округлення, а RoundTo – банківське. Але насправді вони витворяють такі чудеса:


 













































Аргумент


Аріфм.окр.


SimpleRoundTo


Банк.окр.


RoundTo


0.0150


0.02


0.01


0.02


0.01


0.0250


0.02


0.03


0.0450


0.05


0.04


0.0550


0.06


0.05


0.06


0.05


0.0650


0.06


0.07


0.0750


0.08


0.07


0.08


0.07


Результати отримані на Delphi 7 при режимі округлення за замовчуванням (rmNearest). Результати тестування при інших режимах наведені в додатку, але безпомилкового поведінки згідно якого алгоритму досягти так і не вдалося.


Взагалі, результат RoundTo лише в 50% випадків округлення п’ятірки збігається з правилом банківського округлення. Статистична похибка пригнічується огидно – набігає 13 руб. 45 коп різниці на мільйон записів. Ще менше схожа її подружка SimpleRoundTo на обіцяне арифметичне округлення – менше 40% збігів, всі помилки в одну сторону (див. результати тестів в додатку).


Виникає питання: невже в Borland не знають про цю помилку? Виявляється, знають ще з серпня 2003 року. Соответствущіе Public Reports в Quality Central на Borland Developer Network: 5486, 8070, 8143. До речі, перший же коментар під заявкою такий: “Хто-небудь може пояснити, чому вони привласнили цю помилку такий низький рейтинг? …”. Я не можу.


На щастя, знайшовся небайдужий чоловік на ім’я John Herbster, який запропонував власну реалізацію функцій округлення, і виклав її для загального використання. Взяти їх можна там же, на Borland Developer Network (посилання під згаданими Public Reports, реєстрація на BDN безкоштовна). У моїх тестах вони не дали жодної помилки, так що всіляко рекомендую.


Коли виявилося, що багато функцій округлення працюють абияк, виникла ідея тестування всіх можливих функцій округлення різних мов. Методика тестування і результати тестів – в додатку.


Висновок


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


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


Однією із спроб в цьому напрямку є введення типу Currency, який веде обчислення з автоматичним округленням проміжних результатів. Недоліком його є жорстко задана точність (мабуть, для обчислення квадратних грошей). Є також неясні відомості про не цілком коректних перетвореннях цього типу в процесі обчислень (приведення до float), що загрожує помилками. Так що це питання чекає своїх дослідників.


І ще. Деякі з функцій містять “чарівні” вставки на зразок x + 0.000001. Нерідко ці функції показували безпомилкові результати, і я як чесна людина був змушений про це повідомити. Але в глибині душі я підозрюю, що такі вставки можуть привести до помилок на інших наборах даних. Так що якщо все ж таки вирішите їх використовувати – будьте обережні. Не потрібно забувати, що ми тестували тільки позитивні числа. На негативних, мабуть, такі “хвостики” теж повинні бути з мінусом. Для мене також незрозумілий питання, чи не призведуть такі вставки до систематичного накопичення помилки при будь-яких умовах. Так що є про що задуматися.


Ну і зрозуміло, хотілося б з’ясувати, як йдуть справи з округленням в інших продуктах, зокрема NET, і інших від Microsoft. Якщо хтось має такі дані – прошу доповнити список.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*