Відносне порівняння чисел з плаваючою комою


10

У мене є числова функція, що f(x, y)повертає подвійне число з плаваючою комою, яке реалізує певну формулу, і я хочу перевірити, чи правильно воно проти аналітичних виразів для всіх комбінацій параметрів xі yщо мене цікавить. Який правильний спосіб порівняння обчислених та аналітичні числа з плаваючою комою?

Скажімо, два числа є aі b. Поки я переконався, що як абсолютні ( abs(a-b) < eps), так і відносні ( abs(a-b)/max(abs(a), abs(b)) < eps) помилки менше, ніж eps. Таким чином він виявить числові неточності, навіть якщо цифри, скажімо, будуть приблизно 1e-20.

Однак сьогодні я виявив проблему, числове значення aта аналітичне значення bбули:

In [47]: a                                                                     
Out[47]: 5.9781943146790832e-322

In [48]: b                                                                     
Out[48]: 6.0276008792632078e-322

In [50]: abs(a-b)                                                              
Out[50]: 4.9406564584124654e-324

In [52]: abs(a-b) / max(a, b)                                                  
Out[52]: 0.0081967213114754103

Тож абсолютна помилка [50] (очевидно) мала, але відносна похибка [52] велика. Тому я подумав, що у мене в програмі помилка. За допомогою налагодження я зрозумів, що ці числа денормальні . Як такий, я написав таку процедуру, щоб зробити належне порівняльне порівняння:

real(dp) elemental function rel_error(a, b) result(r)
real(dp), intent(in) :: a, b
real(dp) :: m, d
d = abs(a-b)
m = max(abs(a), abs(b))
if (d < tiny(1._dp)) then
    r = 0
else
    r = d / m
end if
end function

Де tiny(1._dp)повертається 2.22507385850720138E-308 на мій комп'ютер. Тепер все працює, і я просто отримую 0 як відносну помилку, і все в порядку. Зокрема, вищезгадана відносна помилка [52] є помилковою, вона просто викликана недостатньою точністю денормальних чисел. Чи rel_errorправильна моя функція? Чи потрібно просто перевірити, що abs(a-b)менше крихітного (= денормальне), і повернути 0? Або я повинен перевірити якусь іншу комбінацію, наприклад max(abs(a), abs(b))?

Я просто хотів би знати, що таке "правильний" шлях.

Відповіді:


11

Ви можете безпосередньо перевірити денормалізованних чисел , використовуючи isnormal()з math.h(C99 або більш пізньої версії, POSIX.1 або більш пізньої версії). У Fortran, якщо модуль ieee_arithmeticдоступний, ви можете використовувати ieee_is_normal(). Щоб бути більш точним щодо нечіткої рівності, ви повинні розглянути представлення плаваючої точки деннормалів і вирішити, що ви маєте на увазі, щоб результати були досить хорошими.

Більше того, щоб вважати, що будь-який результат правильний, ви повинні бути впевнені, що ви не втратили занадто багато цифр на проміжному кроці. Обчислення з деннормалами, як правило, недостовірне, і цього слід уникати, якщо алгоритм міняє внутрішню шкалу. Для того, щоб ваше внутрішнє масштабування було успішним, я рекомендую активувати винятки feenableexcept()з плаваючою комою, використовуючи C99 або ieee_arithmeticмодуль у Fortran.

Хоча ви можете змусити вашу програму вловлювати сигнал, який піднімається за винятками з плаваючою комою, всі ядра, які я намагався скинути прапор обладнання fetestexcept(), не приносять корисного результату. При запуску -fp_trapпрограми PETSc (за замовчуванням) надрукують слід стека, коли помилка з плаваючою точкою буде підвищена, але не буде ідентифікувати рядок порушень. Якщо ви працюєте в налагоджувальній машині, налагоджувач зберігає апаратний прапор і порушується на образливий вираз. Ви можете перевірити точну причину, зателефонувавши fetestexceptз налагоджувача, де результатом є побітове АБО наступних прапорів (значення можуть залежати від машини, див fenv.h. Ці значення для x86-64 з glibc).

  • FE_INVALID = 0x1
  • FE_DIVBYZERO = 0x4
  • FE_OVERFLOW = 0x8
  • FE_UNDERFLOW = 0x10
  • FE_INEXACT = 0x20

Дякую за відмінну відповідь. Аналітичний вираз, з яким я порівнюю в асимптотичному режимі, exp(log_gamma(m+0.5_dp) - (m+0.5_dp)*log(t)) / 2при m = 234, t = 2000. Коли я збільшуюсь, вона швидко переходить до нуля m. Все, що я хочу переконатися, що мій цифровий режим повертає "правильні" числа (щоб повернути нуль теж прекрасно) принаймні до 12 значущих цифр. Тож якщо обчислення повертає денормальне число, то це просто нуль, і проблем не повинно бути. Тому просто порядок порівняння повинен бути стійким проти цього.
Ondřej Čertík

5

Дональд Кнут має пропозицію щодо алгоритму порівняння з плаваючою комою у томі 2 «Напівлінійні алгоритми» «Мистецтва комп’ютерного програмування». Він був реалізований в C Th. Зварювання (див. Пакет fcmp ) і доступне в GSL .


2
Ось моя реалізація Fortran: gist.github.com/3776847 , зауважте, що мені потрібно явно обробити денормальні числа в будь-якому випадку. В іншому випадку я думаю, що це майже рівнозначно відносній помилці, різниця полягає лише в тому, що замість того, щоб робити abs(a-b)/max(a, b) < eps, ми робимо abs(a-b)/2**exponent(max(a, b)) < eps, що в значній мірі просто скидає мантісу в max(a, b), тому, на мою думку, різниця незначна.
Ondřej Čertík

5

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

Але близько до нуля обчислювати відносні помилки безглуздо.

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

Тому я б запропонував протестувати обчислені ух|у-х|абсаcc+rелаccмакс(|х|,|у|)

Тоді користувачі вашого коду точно знають, скільки точності вони мають.


Ви впевнені, що безглуздо обчислювати відносні помилки, близькі до нуля? Я думаю, що це безглуздо, лише якщо є втрата точності (з будь-якої причини). Якщо, наприклад, є втрата точності для x <1e-150 через деякі числові проблеми (наприклад, віднімання двох великих чисел), то ви маєте рацію. У моєму випадку, однак, цифри здаються точними аж до нуля, за винятком випадків, коли вони потрапляють до денормальних чисел. Тож у моєму випадку absacc = 1e-320 або близько того, і я можу просто перевірити, abs(a-b) < tiny(1._dp)як це роблю вище.
Ondřej Čertík

@ OndřejČertík: У такому випадку замініть 1e-150 на 1e-300 або будь-яке обмежене, що ви можете перевірити. У будь-якому випадку, дуже близькому до нуля, ви робите абсолютну помилку, і ваша заява про помилку повинна відображати це, а не оголошувати відносну помилку нульовою.
Арнольд Ноймаєр

Розумію. Я можу переконатися, що все працює на числа вище tiny(1._dp)=2.22507385850720138E-308(я помилився в своєму попередньому коментарі, це 2e-308, а не 1e-320). Отже, це моя абсолютна помилка. Тоді мені потрібно порівняти відносну помилку. Я бачу вашу думку, я думаю, ви маєте рацію. Дякую!
Ondřej Čertík

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