Рішення помилок округлення з плаваючою комою


18

Створюючи додаток, який займається великою кількістю математичних обчислень, я зіткнувся з проблемою, що певні числа викликають помилки округлення.

Хоча я розумію, що плаваюча точка не є точною , проблема полягає в тому, як я маю справу з точними числами, щоб переконатися, що коли обчислення попередньо формуються на них, округлення плаваючої точки не викликає жодних проблем?


2
Чи є певна проблема, з якою ви стикаєтесь? Існує багато способів зробити тестування, все в порядку проблеми. Питання, на які може бути кілька відповідей, погано підходять для формату питань і відповідей. Було б найкраще, якщо ви могли б визначити проблему, яка виникає, таким чином, що вона могла б мати правильну відповідь, а не робити мережу для ідей та рекомендацій.

Я будую програмне забезпечення з великою кількістю математичних обчислень. Я розумію, що тестування NUNIT або JUNIT було б добре, але я хотів би мати уявлення про те, як підійти до проблем із математичними обчисленнями.
JNL

1
Чи можете ви навести приклад розрахунку, який ви б протестували? Як правило, це не буде одиничне тестування сировинної математики (якщо ви не тестуєте власні числові типи), але тестування чогось подібного distanceTraveled(startVel, duration, acceleration)було б протестовано.

Одним із прикладів буде розгляд десяткових знаків. Наприклад, скажімо, що ми будуємо стіну зі спеціальними настройками для dist x-0 до x = 14.589, а потім деякі домовленості від x = 14.589 до x = кінець стіни. Відстань .589 при перетворенні на двійкові не однакова .... Особливо, якщо ми додамо деякі відстані ... як, наприклад, 14.589 + 0.25 не буде рівним 14.84 у двійковій .... Я сподіваюся, що це не заплутається?
JNL

1
@MichaelT дякую за редагування питання. Допомагав багато. Оскільки я новачок у цьому, не надто добре, як вирішувати питання. :) ... Але скоро буде добре.
JNL

Відповіді:


22

Існують три основні підходи до створення альтернативних числових типів, які не мають округлення з плаваючою комою. Загальною темою для них є те, що вони використовують цілу математику замість різних способів.

Раціональні

Представити число як цілу частину та раціональне число за допомогою чисельника та знаменника. Число 15.589буде представлено як w: 15; n: 589; d:1000.

Коли додається 0,25 (що є w: 0; n: 1; d: 4), це включає обчислення LCM, а потім додавання двох чисел. Це добре спрацьовує в багатьох ситуаціях, хоча це може призвести до дуже великої кількості, коли ви працюєте з багатьма раціональними числами, які є відносно простими один одному.

Фіксована точка

У вас є ціла частина, і десяткова частина. Всі числа округлені (є це слово - але ви знаєте, де воно) до такої точності. Наприклад, ви могли мати фіксовану точку з 3 десятковими знаками. 15.589+ 0.250стає додаванням 589 + 250 % 1000для десяткової частини (а потім будь-яке перенесення на всю частину). Це дуже добре працює з існуючими базами даних. Як уже згадувалося, існує округлення, але ви знаєте, де воно знаходиться, і можете вказати його таким чином, щоб воно було більш точним, ніж потрібно (ви вимірюєте лише 3 знаки після коми, тому зробіть це фіксованим 4).

Плаваюча нерухома точка

Збережіть значення та точність. 15.589зберігається як 15589для значення, так і 3для точності, а 0.25зберігається як 25і 2. Це може впоратися з довільною точністю. Я вважаю, що це те, що використовує внутрішня програма Java BigDecimal (ще недавно не розглядала її). У якийсь момент ви захочете вивести його з цього формату і відобразити його - і це може включати округлення (знову ж, ви керуєте, де він знаходиться).


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


2
Це вдалий початок, але, звичайно, це не вирішує повністю проблему округлення. Ірраціональні числа, такі як π, e і √2, не мають строго числового представлення; вам потрібно представити їх символічно, якщо ви хочете точного подання, або оцінити їх якомога пізніше, якщо ви просто хочете мінімізувати помилку округлення.
Калеб

@Caleb для ірраціоналів потрібно оцінити їх за межами, де будь-яке округлення може спричинити проблеми. Наприклад, 22/7 точне до 0,1% пі, 355/113 - точне до 10 ^ -8. Якщо ви працюєте лише з цифрами до трьох десяткових знаків, маючи 3.141592653, слід уникати помилок округлення у 3-х десяткових знаках.

@MichaelT: Для додавання раціональних чисел вам не потрібно знаходити LCM, і це швидше не робити (і швидше скасовувати "нулі LSB" після, а лише завжди повністю спрощувати, коли це абсолютно необхідно). Для раціональних чисел зазвичай це лише "чисельник / знаменник", або "чисельник / знаменник << показник" (а не "ціла частина + чисельник / знаменник"). Також ваша "плаваюча фіксована точка" є поданням з плаваючою точкою, і її краще описати як "плаваючу точку довільного розміру" (щоб відрізнити її від "плаваючої точки з фіксованим розміром").
Брендан

дещо з вашої термінології дещо іффі - плаваюча фіксована точка не має сенсу - я думаю, ви намагаєтесь сказати плаваючу десяткову.
jk.

10

Якщо у значень з плаваючою комою є проблеми із округленням, і вам не хочеться стикатися з проблемами округлення, логічно випливає, що єдиний курс дії - не використовувати значення з плаваючою комою.

Тепер стає питання: "як мені зробити математику, що включає не цілі значення без змінних з плаваючою точкою?" Відповідь - з умовно-точними типами даних . Розрахунки проходять повільніше, оскільки їх потрібно впроваджувати в програмне забезпечення, а не в апаратне забезпечення, але вони точні. Ви не сказали, якою мовою ви користуєтесь, тому я не можу рекомендувати пакет, але для більшості популярних мов програмування доступні довільні бібліотеки точності.


Я зараз використовую VC ++ ... Але я буду вдячний за будь-яку інформацію про інші мови програмування.
JNL

Навіть не маючи значень з плаваючою комою, ви все одно будете стикатися з проблемними проблемами.
Чад

2
@Chad Правда, але мета - не усунути проблеми округлення (які завжди будуть існувати, тому що в будь-якій базі, яку ви використовуєте, є деякі числа, які не мають точного представлення, і у вас немає нескінченної пам'яті та потужності обробки), це зменшіть його до того моменту, який він не впливає на розрахунок, який ви намагаєтеся зробити.
Ікер

@Iker Ти маєш рацію. Хоча ви, ні особа, яка задає питання, не вказали, яких саме розрахунків вони намагаються досягти, і точності, яку вони хочуть. Йому потрібно спочатку відповісти на це питання, перш ніж стрибнути пістолет у теорію чисел. Просто сказати lot of mathematical calculationsне корисно, ні відповіді не надано. У переважній більшості випадків (якщо ви не маєте справу з валютою), то плавати дійсно має бути достатньо.
Чад

@Chad це справедливий момент, безумовно, недостатньо даних з ОП, щоб сказати, який саме рівень точності їм потрібен.
Ікер

7

Арифметика з плаваючою комою зазвичай досить точна (15 десяткових цифр за a double) і досить гнучка. Проблеми виникають, коли ви займаєтесь математикою, що значно скорочує кількість цифр точності. Ось кілька прикладів:

  • Відміна віднімання: 1234567890.12345 - 1234567890.12300результат 0.0045має лише дві десяткових цифри точності. Це вражає кожен раз, коли ви віднімаєте два числа однакової величини.

  • Проковтування точності: 1234567890.12345 + 0.123456789012345оцінюється до 1234567890.24691, втрачаються останні десять цифр другого операнда.

  • Множення: Якщо ви помножите два 15-значні числа, результат має 30 цифр, які потрібно зберегти. Але ви не можете їх зберігати, тому останні 15 біт втрачаються. Це особливо неприємно, якщо їх поєднувати з sqrt()(як у sqrt(x*x + y*y): Результат матиме лише 7,5 цифр точності.

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

for(double f = f0; f < f1; f += df) {

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

for(int i = 0; i < (f1 - f0)/df; i++) {
    double f = f0 + i*df;

Оскільки ви поєднуєте прирости в одному множенні, отримане значення fбуде точним до 15 знаків після коми.

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


2

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

Перша проблема - точність. У багатьох мовах у вас є "float" та "double" (подвійне стояння для "подвійної точності"), а в багатьох випадках "float" дає вам приблизно 7 цифр точності, а double - 15. Якщо в точності може виникнути проблема, 15 цифр - це набагато краще, ніж 7 цифр. У багатьох злегка проблемних ситуаціях використання "подвійного" означає, що ви з ним підете, а "пливти" означає, що ви цього не зробите. Скажімо, обмеження ринку на ринку становить 700 мільярдів доларів. Представляйте це поплавком, а найнижчий біт - 65536 доларів. Представляйте його за допомогою подвійного, а найнижчий біт - близько 0,012 копійок. Тож якщо ви насправді, дійсно не знаєте, що ви робите, ви використовуєте подвійний, а не плаваючий.

Друга проблема - це більше питання принципу. Якщо ви робите два різні обчислення, які повинні дати однаковий результат, вони часто не стають через помилки округлення. Два результати, які мають бути рівними, будуть "майже рівними". Якщо два результати близькі між собою, то реальні значення можуть бути рівними. Або вони можуть бути. Вам потрібно пам’ятати про це, і слід писати та використовувати функції, які говорять, що «x, безумовно, більше, ніж y» або «x, безумовно, менше y» або «x і y може бути рівним».

Ця проблема стає набагато гіршою, якщо ви використовуєте округлення, наприклад, "округлення х вниз до найближчого цілого числа". Якщо ви помножите 120 * 0,05, результат повинен бути 6, але те, що ви отримаєте, - це "деяке число, дуже близьке до 6". Якщо потім "округлите до найближчого цілого числа", це "число, близьке до 6", може бути "трохи менше 6" і округлюється до 5. І зауважте, що це не має значення, якою точністю у вас є. Не має значення, наскільки ваш результат ближчий до 6, якщо він менше 6.

І по-третє, деякі проблеми важкі . Це означає, що немає швидкого і простого правила. Якщо ваш компілятор підтримує "довгий подвійний" з більшою точністю, ви можете використовувати "довгий подвійний" і побачити, чи це має значення. Якщо це не має ніякого значення, то або ви добре, або у вас справжня хитра проблема. Якщо це має різницю, яку ви очікували (як, наприклад, зміна у 12-й десятковій), ви, ймовірно, добре. Якщо це дійсно змінює ваші результати, то у вас є проблема. Просити про допомогу.


1
У математиці з плаваючою комою немає нічого "здорового глузду".
whatsisname

Дізнайтеся більше про це.
gnasher729

0

Більшість людей помиляються, бачачи подвійне кричання BigDecimal, адже насправді вони просто перенесли проблему в інше місце. Подвійний дає біт знака: 1 біт, ширина експонента: 11 біт. Значення та точність: 53 біта (52 явно зберігаються). Завдяки природі подвійних, чим більше цілий interger, ви втрачаєте відносну точність. Для розрахунку відносної точності, яку ми використовуємо тут, наведено нижче.

Відносну точність подвійної в обчисленні використовуємо наступну форум 2 ^ E <= abs (X) <2 ^ (E + 1)

epsilon = 2 ^ (E-10)% Для 16-бітного поплавця (напівточність)

 Accuracy Power | Accuracy -/+| Maximum Power | Max Interger Value
 2^-1           | 0.5         | 2^51          | 2.2518E+15
 2^-5           | 0.03125     | 2^47          | 1.40737E+14
 2^-10          | 0.000976563 | 2^42          | 4.39805E+12
 2^-15          | 3.05176E-05 | 2^37          | 1.37439E+11
 2^-20          | 9.53674E-07 | 2^32          | 4294967296
 2^-25          | 2.98023E-08 | 2^27          | 134217728
 2^-30          | 9.31323E-10 | 2^22          | 4194304
 2^-35          | 2.91038E-11 | 2^17          | 131072
 2^-40          | 9.09495E-13 | 2^12          | 4096
 2^-45          | 2.84217E-14 | 2^7           | 128
 2^-50          | 8.88178E-16 | 2^2           | 4

Іншими словами Якщо ви хочете точність +/- 0,5 (або 2 ^ -1), максимальний розмір, який може бути числом, становить 2 ^ 52. Будь-яка більша, ніж ця, і відстань між числами з плаваючою комою перевищує 0,5.

Якщо ви хочете точність +/- 0,0005 (приблизно 2 ^ -11), максимальний розмір, який може бути числом, становить 2 ^ 42. Будь-яка більша, ніж ця, і відстань між числами з плаваючою комою перевищує 0,0005.

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

Хоча це щось і слід сказати, якщо ви лише прагнете імітувати 100-метровий світ на 100 метрів, у вас буде десь у порядку точності близько 2 ^ -45. Це навіть не вникає в те, як сучасні FPU всередині процесора будуть робити обчислення поза розміром нативного типу, і лише після завершення обчислення вони будуть округлені (залежно від режиму округлення FPU) до розміру нативного типу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.