Błąd zaokrągleń liczb rzeczywistych

0

Zauważyłem, że gdy program porównuje dwie bardzo zbliżone do siebie liczby double, różniące się o około mniej niż 10 do potęgi -10, to przyjmuje je jako równe. Jeśliby się zdarzyło że te liczby jednak wskazywałyby na obiekty różne, to program błędnie sklasyfikuje te obiekty jako równe. Byłby problem z rozróżnieniem obiektów które jednak minimalnie by się różniły.

0
lion137 napisał(a):

Rzuc okiem na to:
https://stackoverflow.com/questions/6106119/how-to-compare-double-in-delphi

Tak, wiem o tym. Czytałem o tym problemie jeszcze w książce Dennie Van Tassel "Praktyka programowania" wydaną koło roku 1970 i ten sam problem zauważył i zastosował te poprawki dr Bernard Baron w książce "Metody numeryczne w Delphi". Chodzi o to, że dużo ludzi nie wie o tym że kompilator zaokrągla wartości przy porównywaniu liczb rzeczywistych znakiem równości i to może dać błędne wyniki przy liczbach naprawdę różniących się bardzo niewiele od siebie, a nie na skutek błędu zaokrągleń. Trudno mieć pewność do tego jak działa kompilator przy zaokrąglaniu, dlatego ja zawsze wolę porównywać zakresy.

1

Ale tak nie do końca rozumiem, o co Ci chodzi. Czy po prostu chciałeś nas uświadomić, że takie zjawisko istnieje, czy może o coś pytasz? Bo w pierwszym poście nie widzę żadnego pytania, a jedynie stwierdzenie faktu.

Wyjaśnij też proszę, co w Twoim drugim poście kryje się pod hasłem "porównywania zakresów".

1
cerrato napisał(a):

Ale tak nie do końca rozumiem, o co Ci chodzi. Czy po prostu chciałeś nas uświadomić, że takie zjawisko istnieje, czy może o coś pytasz? Bo w pierwszym poście nie widzę żadnego pytania, a jedynie stwierdzenie faktu.

Wyjaśnij też proszę, co w Twoim drugim poście kryje się pod hasłem "porównywania zakresów".

Porównywanie zakresów zostało opisane w
https://stackoverflow.com/questions/6106119/how-to-compare-double-in-delphi
Nie porównujemy przez znak równości tylko czy pierwsza liczba leży w zakresie (operatory mniejsze, większe) bardzo małego otoczenia drugiej liczby.

Chciałem uświadomić o tym, bo wiele osób w innym wątku twierdziło, że nie ma problemu i po prostu stosuje porównania liczb rzeczywistych przez znak równości. Sprawa nie jest taka całkiem prosta z tym stosowaniem znaku równości. Jak wiadomo kompilator nieprawidłowo uznaje liczby rzeczywiste prawie równe za równe. Jeśli obliczenia dają 1) bardzo mały błąd zaokrągleń a jednocześnie potrzebujemy 2) rozróżnić liczby prawie równe, że jednak nie są równe, wtedy będzie błąd bo porównywanie przez znak równości ich nie rozróżni, gdyż kompilator ma nastawioną dużo większa różnicę uznającą liczby za równe. Do tego przypadku stosujemy porównywanie przez zakresy i ustawiamy bardzo mały zakres, mniejszy niż taki który ustawia kompilator automatycznie przy stosowaniu znaku równości. Być może są to przypadki szczególne i rzadkie, bo te dwa warunki muszą być spełnione, ale lepiej mieć świadomość tego jak działa kompilator.

Być może jednak twórcy Delphi zorientowali się, że zawsze, nawet przy najprostszych operacjach, błędy zaokrągleń są dosyć duże więc i tak nie dałoby się rozpoznać liczb zbliżonych do siebie jako różne, to już lepiej uznać je za równe bo dużo częściej właśnie taka sytuacja zachodzi. Czyli trzeba znać konkretny problem jaki się rozwiązuje i wiedzieć że mogą zachodzić przypadki bardzo małej różnicy liczb. Wtedy prawdopodobnie standardowe liczby rzeczywiste by w ogóle nie zadziałały prawidłowo i bezpieczniej byłoby użyć osobnej biblioteki symulującej działania na BigFloat.

1

wiele osób w innym wątku twierdziło, że nie ma problemu i po prostu stosuje porównania liczb rzeczywistych przez znak równości.

możesz dać link do tego?

3

Wszystko pięknie ładnie. Jednak to nie jest żadne zaokrąglenie, a błąd reprezentacji liczb rzeczywistych w pamięci komputera związana z dokładnością oraz sposobem jej przechowywania w pamięci. Co więcej, nie następuje tutaj żadne zaokrąglanie przy porównaniu liczb, ani w ogóle. Jak masz dwie zmienne a oraz b, to zawsze możesz je porównać używając znaku równości i tu nic się nie zaokrągla. Żeby było ciekawiej porównanie będzie zawsze prawidłowe. Jeśli obie liczby mają taką samą reprezentację binarną porównanie się powiedzie -> liczby są równe. Nawet jeśli reprezentują trochę inne wartości niż my chcemy, jak w następującym przykładzie:

Na przykład liczba 123,4599781 może nie być dokładnie zapisana w pamięci dokładnie tak jaka jest jej wartość, ale z powodu skończonej dokładności jako na przykład 123,4599780000000009. I popatrz, że to nie jest zaokrąglanie, a błąd reprezentacji liczby rzeczywistej.

Również same obliczenia ze skończoną dokładnością mogą powodować błędy. Dlatego należałoby porównywać liczby zmiennopozycyjne z zadaną dokładnością.

Jeszcze jedna kwestia, to nie jest cecha specyficzna Delphi, ale każdemu językowi programowania który ma liczby zmiennopozycyjne o skończonej precyzji.

2

jeśli chcesz tylko się dowiedzieć czy dwie liczby są sobie równe po prostu zamień je na stringi i je porównaj, tutaj nie będzie niedokładności. Zadziała to dość szybko i już.

Następna sprawa to zaokrąglenia. Nie jestem tutaj specjalistą ale wydaje mi się że dokładność na poziomie kilku miejsc po przecinku jest wystarczająca w rzeczywistym świecie (poza operacjami finansami).
Skąd taka wiedza? Czytałem kiedyś ciekawy artykuł o liczbie pi. Wszyscy wiemy że cały czas istnieje parcie na obliczenie jej z najwyższą możliwą dokładnością. Fajnie, tylko że to jest ważne tylko dla matematyków (i uniwersytetów / krajów itp. w celu pochwalenia się). Dla realnego świata nie ma to znaczenia. W artykule tym było napisane że najwyższą precyzję wykorzystuje się w technologii kosmicznej i jest to najczęściej 3 do 4 miejsc po przecinku.
Ale jak napisałem - ja specjalistą w tej dziedzinie nie jestem.

Ps.
szukałem ale nie mogę znaleźć źródła artykułu o którym pisałem.

3

Jeszcze jedna kwestia. Wiemy już, że liczby zmiennoprzecinkowe muszą być przechowywane ze skończoną dokładnością (w końcu rozmiar zmiennej jest skończony) i że żadne zaokrąglenie nie ma miejsca, o czym wspomniał @Mr.YaHooo. Druga sprawa to zwalenie winy na kompilator.

Nie, to nie jest wina kompilatora, bo ten to zwykły program, taki sam jak ten który kompiluje, jeśli chodzi o architekturę. Natomiast to w jaki sposób floaty reprezentowane są w pamięci określone przez standard IEEE 754. Całą magię wykonuje procesor, nie oprogramowanie.


robertz68 napisał(a):

Następna sprawa to zaokrąglenia. Nie jestem tutaj specjalistą ale wydaje mi się że dokładność na poziomie kilku miejsc po przecinku jest wystarczająca w rzeczywistym świecie (poza operacjami finansami).

Do operacji finansowych wykorzystuje się inny typ danych – Currency – który charakteryzuje się stałą precyzją wynoszącą cztery miejsca po przecinku. Natomiast wewnętrznie jest po prostu 64-bitową liczbą całkowitą za znakiem, dlatego typ ten nie jest obarczony błędem reprezentacji.

1 użytkowników online, w tym zalogowanych: 0, gości: 1