Porównywanie liczb zmiennoprzecinkowych w C#

0

Często podczas porównywania dwóch liczb double dostaję niespodziewane wyniki. Mam dwa takie same punkty, stworzone z tych samych liczb double w różnych miejscach programu:

PunktA.ToString(): {(22,1556416672667; 51,9875303124212; 0)}
X = 22.1556416672667
Y = 51.9875303124212

PunktB.ToString(): {(22,1556416672667; 51,9875303124212; 0)}
X = 22.155641667266682
Y = 51.987530312421228

Jak się okazuje liczby z niewiadomych przyczyn przestają być takie same (Equals zwraca false). Czasem porównanie tych punktów działa prawidłowo, a czasem nie. Czy ktoś wie dlaczego tak się dzieje i jak w takim razie można porównać prawidłowo te liczby?

0

liczby zmiennoprzecinkowe powinno się porównywać tak

if (Math.Abs(x - y) < 0.00000001)
//takie same

to 0.00000001 to dokładność, jaka Cię zadowala
0

misiekd:
Źle. Prawidłowe to:

Math.abs((x - y) / x) < dokladnosc

Gdzie dokładność zależy od typu danych - dla floatów np 0.0001 dla doubli np 0.0000001.

0

to powiedz mi wg Twojego wzoru czy liczby 0.00001 i 0.00002 dla dokładności 0.0001 są równe czy nie :>

0

@donkey, dlaczego? Czasami chcemy uznać, że liczby są równe gdy ich różnica jest bliska 0, a czasami wtedy gdy ich iloraz jest bliski 1. Nie ma chyba "jedynie słusznego" kryterium.
Kiedy Twoje kryterium uzna za równe liczby y=0.0 oraz x=0.000000000000000000000000000000000001?

0

misiekd:
Oczywiście, że nie. Dokładność 0.01 oznacza że liczby różnią się o maks 1 % wartości, w przypadku liczb 0.001 i 0.002 druga liczba jest o 100 % większa od pierwszej, a więc różnica jest kolosalna.

bo:
Liczba y jest nieskończenie razy mniejsza od x, więc dla żadnej dokładności nie mogą być równe.

Generalnie używanie odległości między liczbami do przybliżonego sprawdzania ich równości jest bez sensu gdy nie znamy wielkości porównywanych liczb lub zakresy są bardzo duże. Dla przykładu możemy np mieć dokładność bezwzględną 1 centymetr. Przy porównywaniu wielkości ślimaków jest zbyt mała, a przy porównywaniu odległości planet jest zbyt duża. Przy błędzie względnym np 1 % wszystko byłoby w porządku.

0

Dodam jeszcze, że analiza numeryczna zajmuje się tylko błędami względnymi.

0

@donkey, jak typowy osioł ;-) upierasz się, że istnieje jedyne słuszne kryterium - iloraz ma być bliski 1. Na dodatek przekonujesz do oczywistego faktu, że czasami nie można się posługiwać błędem bezwzględnym, a trzeba względnym. Jeżeli znany jest zakres liczb, które chcemy porównywać, to kryterium różnicy jest odpowiednie. Wspomniane przeze mnie wielkości 0.0mm oraz 0.000000000000000000000000000000000001mm, są może bardzo różne dla odległości między atomami, są natomiast nierozróżnialne dla wielkości ślimaków, lub odległości planet. Twoje kryterium równości jest czasami zupełnie bezwartościowe.

0

Używanie błędów bezwzględnych jest z reguły bezcelowe. Mało jest sensownych sytuacji kiedy są potrzebne (np formatowanie).

Poza tym co zrobisz jeśli zmienisz sobie jednostkę z km na mm? Będziesz zmieniał stałe w każdym miejscu programu?

0
  1. Będę zmieniał stałe jednym miejscu programu. Ja definiuje stałe typu EPSILON=0.0000000001 i sprawdzam czy miara różnicy liczb jest mniejsza od EPSILON.
  2. Twoje kryterium ma paradoksalną (i niekiedy nie do przyjęcia) konsekwencję:
    Liczby a i b są "równe" ("nierówne"), liczby a+k i b+k są "nierówne" ("równe").
0

Margines błędu może wynosić np 1 cm. Licząc w centymetrach stała powinna wynosić 1, licząc w metrach stała powinna wynosić 0.01. Dlatego wraz ze zmianą jednostki trzeba zmienić stałą EPSILON u ciebie, albo zastosować błąd względny i się tym nie martwić.

Double ma chyba 14 cyfr precyzji. Dlatego np mając dodatnie liczby x i y rzędu 10^14 ich różnica jest mniej więcej wielokrotnością jedynki. Przyjmując bezwzględny epsilon 0.000001 wymuszamy identyczność tych liczb.

java.lang.Math.ulp(liczba) podaje różnicę pomiędzy podaną, a następną reprezentowalną liczbą. Jeśli epsilon jest mniejszy od ulp to jest równoważy epsilonowi zerowemu.

Mając x i y tże x = 1 +- 1 oraz y = 1 +- 1 to x + 1014 == y + 1014 w doublach. Tego nie da się obejść. Dlatego w stabilnym numerycznie algorytmie powinno dodawać się liczby podobnego rzędu, natomiast nigdy nie powinno się odejmować bliskich liczb (co powoduje utratę cyfr znaczących).

Poza tym błąd względny jest najbardziej naturalnym rodzajem błędu. Jeśli porównujemy np wysokość człowieka, długość drogi do pracy czy szkoły, obwód bicepsa itd to generalnie przyjmujemy że wartości są równe jeżeli różnią się mniej niż np 1 %, a nie np 1 cm. Odchylenie 10 cm od pionu przy budowie wieżowca jest dużo mniej zauważalne niż przy budowie krzesła, gdyż jest dużo mniejsze względnie.

Kod:

package test;

public class Main {

    static void print(double x) {
        System.out.println("Liczba: " + x + ", ulp = " + Math.ulp(x));
    }

    public static void main(String[] args) {
        print(1e-80);
        print(1);
        print(1e80);
    }
}

Daje:

Liczba: 1.0E-80, ulp = 1.8726705418768793E-96
Liczba: 1.0, ulp = 2.220446049250313E-16
Liczba: 1.0E80, ulp = 1.3164036458569648E64

Jak można się było domyślić, ulp jest wprost proporcjonalny do wielkości liczby.

0
donkey napisał(a)

misiekd:
Oczywiście, że nie. Dokładność 0.01 oznacza że liczby różnią się o maks 1 % wartości

0.01 oznacza dokładnie 0.01 a 1% posiada różną wartość w zależności od tego od czego ten 1% liczymy!! I to powinieneś zaznaczyć w pierwszym swoim poście, a tam podałeś wzór z kosmosu, nie pisząc ani słowa o tym, że masz na myśli wartość względną

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