Mam funkcję numeryczną f(x, y)
zwracającą podwójną liczbę zmiennoprzecinkową, która implementuje pewną formułę i chcę sprawdzić, czy jest ona poprawna w stosunku do wyrażeń analitycznych dla wszystkich kombinacji parametrów x
i y
że jestem zainteresowany. Jaki jest właściwy sposób porównania obliczonego i analityczne liczby zmiennoprzecinkowe?
Powiedzmy, że dwie liczby to a
i b
. Do tej pory upewniłem się, że zarówno błędy bezwzględne ( abs(a-b) < eps
), jak i względne ( abs(a-b)/max(abs(a), abs(b)) < eps
) są mniejsze niż eps. W ten sposób wykryje niedokładności liczbowe, nawet jeśli liczby będą powiedzmy około 1e-20.
Jednak dzisiaj odkryłem problem, wartość liczbowa a
i wartość analityczna b
były następujące:
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
Zatem błąd bezwzględny [50] jest (oczywiście) mały, ale błąd względny [52] jest duży. Pomyślałem więc, że mam błąd w moim programie. Podczas debugowania zdałem sobie sprawę, że liczby te są normalne . Jako taki napisałem następującą procedurę, aby wykonać właściwe porównanie względne:
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
Gdzie tiny(1._dp)
zwraca 2.22507385850720138E-308 na moim komputerze. Teraz wszystko działa i po prostu dostaję 0 jako błąd względny i wszystko jest w porządku. W szczególności powyższy błąd względny [52] jest błędny, jest po prostu spowodowany niewystarczającą dokładnością liczb denormalnych. Czy moja implementacja rel_error
funkcji jest poprawna? Czy powinienem po prostu sprawdzić, czy abs(a-b)
jest mniejszy niż mały (= denormalny) i zwrócić 0? Czy powinienem sprawdzić inną kombinację, na przykład
max(abs(a), abs(b))
?
Chciałbym tylko wiedzieć, jaki jest „właściwy” sposób.
źródło
exp(log_gamma(m+0.5_dp) - (m+0.5_dp)*log(t)) / 2
dla m = 234, t = 2000. Wraz ze wzrostem szybko spada do zeram
. Chcę tylko upewnić się, że moja procedura numeryczna zwraca „poprawne” liczby (powrót do zera też jest w porządku) do co najmniej 12 cyfr znaczących. Jeśli więc obliczenia zwracają liczbę normalną, to jest to po prostu zero i nie powinno być problemu. Dlatego właśnie procedura porównawcza musi być odporna na to.Donald Knuth ma propozycję algorytmu porównywania zmiennoprzecinkowego w tomie 2 „Algorytmy seminumeryczne” z „The Art of Computer Programming”. Został zaimplementowany w C przez Th. Belding (patrz pakiet fcmp ) i jest dostępny w GSL .
źródło
abs(a-b)/max(a, b) < eps
, robimyabs(a-b)/2**exponent(max(a, b)) < eps
, co dość dużo po prostu spada mantysę Wmax(a, b)
, więc moim zdaniem różnica jest znikoma.Optymalnie zaokrąglone liczby zdormalizowane mogą rzeczywiście mieć wysoki błąd względny. (Spłukanie go do zera, wciąż nazywając go błędem względnym, wprowadza w błąd).
Ale bliskie zeru, obliczanie względnych errosów jest bez znaczenia.
Dlatego nawet przed osiągnięciem liczb zdormalizowanych powinieneś prawdopodobnie przejść do absolutnej dokładności (mianowicie tej, którą chcesz zagwarantować w tym przypadku).
Dzięki temu użytkownicy Twojego kodu dokładnie wiedzą, ile naprawdę mają dokładności.
źródło
abs(a-b) < tiny(1._dp)
jak to robię powyżej.tiny(1._dp)=2.22507385850720138E-308
(popełniłem błąd w poprzednim komentarzu, to 2e-308, a nie 1e-320). To jest mój absolutny błąd. Następnie muszę porównać błąd względny. Widzę twój punkt, myślę, że masz rację. Dzięki!