problem z dokładnością typów liczbowych

0

W programie mam metodę sprawdzającą czy dany punkt należy do trójkąta czy nie, w taki sposób, że gdy mamy trójkąt ABC i punkt P to jeżeli pole trójkąta ABC = sumie pól trójkątów PAB + PAC + PBC to punkt należy do tego trójkąta, w przeciwnym razie leży poza nim.
Na kartce wszystko się zgadza, ale niestety podczas wyliczania długości boków trójkątów, oraz pola ze wzoru Hornera, korzystam m.in. z Math.sqrt, w związku z czym wychodzą jakieś miejsca po przecinku i w rezultacie niemal w 100% punkty, które należą do trójkątów są oznaczane jako nienależące - pola różnią się, jak przypuszczam z tego powodu, że typy liczbowe, na których operuje mają określony zakres i dokładność.

Próbowałem już przy obliczeniach stosować double - żeby mieć dokładne wyniki, a następnie rzutować na float, albo stosować chwyty typu :

p = Math.round(p*100);
p /= 100;

Niestety wszystko na nic, wie ktoś jak temu zaradzić ?

1

Równości liczb zmiennoprzecinkowych nie należy sprawdzać przy pomocy operatora równości. Raczej tak:

Math.abs(x-y)<0.00000000001;
0

To też nie bardzo pomaga, bo długości boków są później dodawane, mnożone, dzielone, więc czasem różnica jest na poziomie n-ego miejsca po przecinku, a czasem również przed przecinkiem, w związku z tym ciężko dobrać odpowiedni próg :/
Tak wygląda metoda obliczająca pole trójkąta:

public double triangleArea(MyPoint a, MyPoint b, MyPoint c)
	{		
		double area = 0;

		double abLength =  Math.sqrt(Math.pow((a.x - b.x), 2) + Math.pow((a.y - b.y), 2));
		double acLength =  Math.sqrt(Math.pow((a.x - c.x), 2) + Math.pow((a.y - c.y), 2));
		double bcLength =  Math.sqrt(Math.pow((c.x - b.x), 2) + Math.pow((c.y - b.y), 2));
		
		double p = (abLength + acLength + bcLength) / 2;
		
		area = Math.sqrt(p * (p - abLength) * (p - acLength) * (p - bcLength)); // ze wzoru Hornera

		return area;
	}
0

To skorzystaj z klasy BigDecimal. Spowolnisz program, ale może liczyć z dowolna dokładnością (np. 1000 cyfr po przecinku).

0

No tak, ale w BigDecimal nie znalazłem, żadnej metody liczącej pierwiastki, a to głownie one psują wyniki. Więc przy użyciu BigDecimal mógłbym tylko odjąć, dodać, wziąć moduł, spotęgować i podzielić czyli wykonać niemal wszystkie operacje -oprócz dzielenia- których typ double nie psuje. Natomiast korzyść z podzielenia w BD i tak byłaby żadna bo żeby wykonać sqrt, musiałby z powrotem zamienić na double :(

1

Algorytm masz narzucony? Na pewno nie jest to najlepszy algorytm.

1

Kiedyś znalazłem coś takiego w C++ (poskładałem)
łatwo będzie przepisać w razie potrzeby

static bool PointInTriangle(const Vector2f& v, const Triangle& t)
    {
        bool b1, b2, b3;
        b1 = Triangle(v, t.v[0], t.v[1]).getArea() <= 0.0f;
        b2 = Triangle(v, t.v[1], t.v[2]).getArea() <= 0.0f;
        b3 = Triangle(v, t.v[2], t.v[0]).getArea() <= 0.0f;
        return ((b1 == b2) && (b2 == b3));
    }
   float getArea()//metoda w klasie Triangle.
    {
        return ((v[0].x - v[2].x) * (v[1].y - v[2].y) - (v[1].x - v[2].x) * (v[0].y - v[2].y))/2.0f;
    }

Jednak nie jestem co do niego przekonany. Byłoby fajnie, gdybyś ktoś to przy okazji zweryfikował.
Dodam, że z moich testów wynik, że działa, ale mogłem coś przeoczyć.

0

Dobra, poradziłem sobie. Zmieniłem algorytm na taki, który liczy pole, ze wzoru P = 0.5 * det|ABAC| gdzie AB i AC to wektory a det to wyznacznik macierzy z pary ABAC, tylko, że pominąłem mnożenie * 0.5, żeby nie psuć dokładności. Niestety nadal otrzymywałem często niepoprawne wyniki i okazało się, że miałem błąd w innej metodzie (algorytmie znajdowania otoczki wypukłej punktów), która korzystała z powyższej. Czyli wcześniejsza metoda być może też by działała przy ustaleniu jakiegoś minimalnego marginesu błędu :P .Tak czy inaczej, dzięki za pomoc, i porady ;) .Gdyby ktoś też chciał porównywać pola trójkątów, nie używając funkcji "psujących" wyniki to poniżej zamieszczam kod.

 
public double triangleArea(MyPoint a, MyPoint b, MyPoint c)
	{		
		double area = 0;
		
		MyPoint vectorAB = new MyPoint(a.x - b.x, a.y - b.y);
		MyPoint vectorAC = new MyPoint(a.x - c.x, a.y - c.y);
		
		int detABAC = (vectorAB.x * vectorAC.y) - (vectorAB.y * vectorAC.x);
		area = Math.abs(detABAC);

		return area;
	}
0
bogdans napisał(a):

Równości liczb zmiennoprzecinkowych nie należy sprawdzać przy pomocy operatora równości. Raczej tak:

Math.abs(x-y)<0.00000000001;

Błąd ma być względny, żeby to miało sens. Np:
Math.abs(x - y) < 0.00001 * Math.min(Math.abs(x), Math.abs(y));

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