Аналіз числової помилки у функції C ++


20

Припустимо, що у мене є функція, яка приймає в якості введення декілька значень з плаваючою комою (одинарне або подвійне), виконує деякі обчислення та виробляє вихідні значення з плаваючою комою (також одиничні чи подвійні). Я працюю в першу чергу з MSVC 2008, але також планую співпрацювати з MinGW / GCC. Я програмую на C ++.

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

Відповіді:


17

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

Мені не вдалося знайти хорошого посилання в Інтернеті, але все це описано у розділі 3.3 книги Ніка Хігема "Точність та стабільність чисельних алгоритмів". Ідея досить проста:

  1. Перефабрикуйте свій код так, щоб у кожному рядку було одне призначення однієї арифметичної операції.
  2. Для кожної змінної, наприклад x, створіть змінну, x_errяка ініціалізується до нуля, коли xїй призначається константа.
  3. Наприклад, для кожної операції, наприклад z = x * y, оновіть змінну, z_errвикористовуючи стандартну модель арифметики з плаваючою комою, а також результуючі zта помилки запуску x_errта y_err.
  4. Після цього значення, що повертається, має також відповідне _errзначення. Це залежно від даних про вашу загальну помилку округлення.

Хитра частина - крок 3. Для найпростіших арифметичних операцій можна використовувати такі правила:

  • z = x + y -> z_err = u*abs(z) + x_err + y_err
  • z = x - y -> z_err = u*abs(z) + x_err + y_err
  • z = x * y -> z_err = u*abs(z) + x_err*abs(y) + y_err*abs(x)
  • z = x / y -> z_err = u*abs(z) + (x_err*abs(y) + y_err*abs(x))/y^2
  • z = sqrt(x) -> z_err = u*abs(z) + x_err/(2*abs(z))

де u = eps/2одиниця округлення. Так, правила для +та -однакові. Правила будь-якої іншої операції op(x)можна легко витягнути за допомогою розширення результатів, застосованих до серії Taylor op(x + x_err). Або ви можете спробувати googling. Або використовуючи книгу Ніка Хігхема.

Як приклад, розглянемо наступний код Matlab / Octave, який оцінює поліноми в коефіцієнтах aу точці xза схемою Хорнера:

function s = horner ( a , x )
    s = a(end);
    for k=length(a)-1:-1:1
        s = a(k) + x*s;
    end

Для першого кроку ми розділили дві операції на s = a(k) + x*s:

function s = horner ( a , x )
    s = a(end);
    for k=length(a)-1:-1:1
        z = x*s;
        s = a(k) + z;
    end

Потім вводимо _errзмінні. Зауважте, що вхідні дані aі xвважаються точними, але ми також могли б також вимагати від користувача передачі відповідних значень для a_errта x_err:

function [ s , s_err ] = horner ( a , x )
    s = a(end);
    s_err = 0;
    for k=length(a)-1:-1:1
        z = x*s;
        z_err = ...;
        s = a(k) + z;
        s_err = ...;
    end

Нарешті, ми застосовуємо описані вище правила, щоб отримати умови помилки:

function [ s , s_err ] = horner ( a , x )
    u = eps/2;
    s = a(end);
    s_err = 0;
    for k=length(a)-1:-1:1
        z = x*s;
        z_err = u*abs(z) + s_err*abs(x);
        s = a(k) + z;
        s_err = u*abs(s) + z_err;
    end

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

Et voilà! Тепер у нас є схема Хорнера, яка повертає залежно від даних оцінку помилок (зверніть увагу: це верхня межа помилки) поряд з результатом.

В якості додаткової примітки, оскільки ви використовуєте C ++, ви можете розглянути можливість створення власного класу для значень з плаваючою комою, який містить _errтермін і перевантажує всі арифметичні операції для оновлення цих значень, як описано вище. Для великих кодів це може бути простіший, хоча і обчислювально менш ефективний, маршрут. Сказавши це, ви можете знайти такий клас в Інтернеті. Швидкий пошук Google дав мені це посилання .

±ух(1±у)


1
+1 для цього аналізу, адже це цікаво. Мені подобається робота Хігхема. Мене хвилює те, що вимагати від користувача написання додаткового коду вручну (замість напівавтоматично подібної інтервальної арифметики) може бути схильною до помилок, оскільки число числових операцій стає великим.
Джефф Оксберрі

1
@GeoffOxberry: Я повністю згоден з проблемою складності. Для більших кодів я б настійно рекомендував написати клас / тип даних, який перевантажує операції на подвійні, такі, що потрібно виконати кожну операцію правильно один раз. Я дуже здивований, що для Matlab / Octave не здається щось подібне.
Педро

Мені подобається цей аналіз, але оскільки обчислення термінів помилки виконуються також із плаваючою комою, чи не будуть ці помилки помилковими через помилки з плаваючою комою?
плазмацель

8

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

На нижчому рівні знаходиться Бібліотека Bignum Multiple Precision (GMP) Multiple Precision (GMP) , також пакет з відкритим кодом.

NTL можна використовувати з GMP, швидше вимагається швидкість роботи, але NTL надає власні базові процедури, які, безумовно, корисні, якщо ви "не піклуєтесь про швидкість". GMP стверджує, що це "найшвидша бібліотека бінтуму". GMP багато в чому написано на C, але має інтерфейс C ++.

Додано: Хоча арифметика інтервалу може давати верхні та нижні межі точної відповіді автоматизованим способом, це не дозволяє точно виміряти помилку в «стандартному» обчисленні точності, оскільки розмір інтервалу зазвичай зростає з кожною операцією (або відносною, або абсолютне почуття помилки).

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

Точку можна проілюструвати, порівнюючи обчислення одно- та подвійної точності. Зауважимо, що в C ++ проміжні вирази завжди обчислюються в (принаймні) подвійній точності, тому, якщо ми хочемо проілюструвати, яким би виглядало обчислення в "чистій" одній точності, нам потрібно зберігати проміжні значення в одній точності.

Фрагмент коду C

    float fa,fb;
    double da,db,err;
    fa = 4.0;
    fb = 3.0;
    fa = fa/fb;
    fa -= 1.0;

    da = 4.0;
    db = 3.0;
    da = da/db;
    da -= 1.0;

    err = fa - da;
    printf("Single precision error wrt double precision value\n");
    printf("Error in getting 1/3rd is %e\n",err);
    return 0;

Вихід зверху (ланцюг інструментів Cygwin / MinGW32 GCC):

Single precision error wrt double precision value
Error in getting 1/3rd is 3.973643e-08

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


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

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

7

GMP (тобто бібліотека множинної точності GNU) - найкраща бібліотека довільної точності, про яку я знаю.

Я не знаю жодного програмного способу вимірювання помилки в результатах довільної функції з плаваючою точкою. Одне, що ви можете спробувати, - це обчислити розширення інтервалу функції за допомогою арифметики інтервалу . У C ++ вам доведеться використовувати якусь бібліотеку для обчислення розширень інтервалу; однією з таких бібліотек є Boost Interval Aritmetic Library. В основному, для вимірювання помилки ви б подавали в якості аргументів функціональні інтервали, які мають ширину в 2 рази одиницю округлення (приблизно), орієнтовану на значення, що цікавлять, і тоді ваш вихід буде сукупністю інтервалів, шириною що дало б вам деяку консервативну оцінку помилки. Складність такого підходу полягає в тому, що арифметика інтервалу, що використовується таким чином, може переоцінити помилку на значні суми, але цей підхід є найбільш "програмним", про який я можу придумати.


Ах, я щойно помітив інтервал арифметики, згаданий у вашій відповіді ... Запропоновано!
Алі

2
Річард Харріс написав чудову серію статей у журналі ACCU Overload про плаваючу точку блюзу . Його стаття про інтервальну арифметику знаходиться в « Перевантаженні 103» ( pdf , p19-24).
Марк Бут

6

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

[a,b] + [c,d] = [min(a+c, a+d, b+c, b+d), max (a+c, a+d, b+c, b+d)] = [a+c, b+d]

Закругленням також можна суворо керуватись, див. Арифметика округлого інтервалу .

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

Я не знаю жодної довільної арифметичної бібліотеки точності інтервалу.

Це залежить від вашої проблеми, чи може інтервальна арифметика задовольняти ваші потреби чи ні.


4

Бібліотека MPFR GNU - це плаваюча бібліотека з довільною точністю, яка має високу точність (зокрема, правильне округлення для всіх операцій, що не так просто, як це звучить), як одна з основних їх точок фокусування. Він використовує GNU MP під кришкою. Він має розширення під назвою MPFI, яке робить інтервал арифметики, який - як підказує відповідь Джеффа - може стати в нагоді для перевірки: продовжуйте збільшувати робочу точність, поки отриманий інтервал не потрапить у невеликі межі.

Це не завжди спрацює; зокрема це не обов'язково ефективно, якщо ви робите щось на зразок числової інтеграції, де кожен крок несе "помилку", незалежну від проблем округлення. У такому випадку спробуйте спеціалізований пакет, такий як нескінченність COZY, який робить це дуже добре, використовуючи конкретні алгоритми для прив’язки помилки інтеграції (та використання так званих моделей Тейлора замість інтервалів).


Я згоден; числова інтеграція, безумовно, є випадком, коли арифметика наївних інтервалів може зіпсуватись. Однак навіть моделі Тейлора використовують інтервальну арифметику. Я знайомий з роботами Макіно та Берца, і я вважаю, що вони використовують модель Тейлора в розумінні Р. М. Мура, хоча вони також використовують трюки, що стосуються того, що вони називають "диференціальною алгеброю".
Джефф Оксберрі

@GeoffOxberry: Так - я думаю, що ця диференціальна алгебра є предметом, щоб визначити помилку на кроці інтеграції.
Ерік П.

0

Мені кажуть, що MPIR - це хороша бібліотека, яку ви можете використовувати, якщо ви працюєте з Visual Studio:

http://mpir.org/


Ласкаво просимо до SciComp.SE! Чи можете ви додати деякі деталі про те, як ця бібліотека може використовуватися для вимірювання помилок обчислень з плаваючою комою?
Крістіан Класон

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