Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++: Pallokolmion ratkaisu

jormi [26.04.2008 18:21:51]

#

Toivon tähän kehittävää arvostelua, puutteita on esitetty kommenteissa. JVM

/*--- tunvastkul.c ---*/
/*--- Lasketaan pallokolmion 2 kulmaa ja toisen vastainen sivu,
 kun 2 sivua ja toisen vastainen kulma tunnetaan.
26.04.2008 testaus kesken.
Oppikirja Hannu Lappalainen Merenkulun Pallotrigonometria.
Tekijä Jorma miettinen, kääntäjä lcc-win32.
---*/

#include <stdio.h>
#include <math.h>

struct OSA  /*- Yksikköpallo, kulmat ja sivut kulmayksiköissä. -*/
{
	double rad;
	double aste;
	double min;
	double sek;
};
struct KOL
{
	struct OSA sivu[3];
	struct OSA kulma[3];
	double ala;
}kol1, kol2;

void Radtodeg( struct OSA *osa );
void Degtorad( struct OSA *osa );
struct OSA Haeosa( void );
void TuoOsa( struct OSA osa );
void Haetunnetut( void );
void Tuolasketut1( void );
void Tuolasketut2( void );
int Laske_BcC( void );

/*--- Pääfunktio, muiden funktioiden ohjaus ---*/
int main(void)
{
	int virhe;
	char merkki;
	do
	{
	Haetunnetut();
	virhe = Laske_BcC(); /*- Kulmat isoilla, sivut pienillä kirjaimilla. -*/
	if( virhe == 1 )
		printf( "sin( B ) > 1" );
	printf( "\n Anna merkki, 0 lopettaa: " );
	merkki = getchar(); /*- Kömpelö rakaisuni Enterin syömiseksi pois. -*/
	merkki = getchar();
	}while( merkki !='0' );
	return 0;
}

/*-- Muuttaa radiaanit asteiksi, minuuteiksi ja sekunneiksi. --*/
void Radtodeg( struct OSA *osa )
{
	double aste, min, sek, kokaste, kokmin;
	aste = osa->rad * 180.0/M_PI;
	min = modf( aste, &kokaste );
	osa->aste = kokaste;
	min = min * 60.0;
	sek = modf( min, &kokmin );
	osa->min = kokmin;
	osa->sek = sek * 60.0;
}

/*-- Muuttaa asteet, minuutit ja sekunnit radiaaneiksi. --*/
void Degtorad( struct OSA *osa )
{
   osa->rad = ( osa->aste + osa->min/60.0 + osa->sek/3600.0 ) * M_PI/180.0;
}

/*-- Kysyy osan tiedot.--*/
struct OSA Haeosa( void )
{
	 struct OSA temp;
	 printf( " Anna aste min sek (max 180 aste): ");
   	scanf( "%lf %lf %lf", &temp.aste, &temp.min, &temp.sek );
   	Degtorad( &temp );
   	return temp;
}

/*-- Esittää osan tiedot. --*/
void TuoOsa( struct OSA osa )
{
	printf( " %6.5f  %3.1f  %3.1f  %3.1f \n", osa.rad, osa.aste, osa.min, osa.sek );
}

/*-- Kysyy lähtötiedot. --*/
void Haetunnetut( void )
{
	printf( "\n Anna sivu a:\n" );
	kol1.sivu[0] = Haeosa();
	printf( "Anna sivu b;\n" );
	kol1.sivu[1] = Haeosa();
	printf( "Anna kulma A:\n" );
	kol1.kulma[0] = Haeosa();
}

/*-- Esittää lasketut osat --*/
void Tuolasketut1( void )
{
	printf( "\n Lasketut osat rad  aste  min  sek \n" );
	printf( " Kulma B " );
	TuoOsa( kol1.kulma[1] );
	printf( " sivu c " );
	TuoOsa( kol1.sivu[2] );
	printf( " kulma C " );
	TuoOsa( kol1.kulma[2] );
	kol1.ala = kol1.kulma[0].rad + kol1.kulma[1].rad + kol1.kulma[2].rad - M_PI;
	printf( "  Kolmion ala = PII * %f \n", kol1.ala/M_PI );
}

/*-- Esittää lasketut osat --*/
void Tuolasketut2( void )
{
	printf( "\n Lasketut osat rad  aste  min  sek \n" );
	printf( " Kulma B " );
	TuoOsa( kol2.kulma[1] );
	printf( " sivu c " );
	TuoOsa( kol2.sivu[2] );
	printf( " kulma C " );
	TuoOsa( kol2.kulma[2] );
	kol2.ala = kol2.kulma[0].rad + kol2.kulma[1].rad + kol2.kulma[2].rad - M_PI;
	printf( "  Kolmion ala = PII * %f \n", kol2.ala/M_PI );
} /*- Kömpelö ratkaisu tehdä melkein sama kahteen kertaan,
	kompastuin osoittimiin, kun yritin yhdistää. -*/

/*-- Tunnetaan 2 sivua ja toisen vastainen kulma, --*/
/*-- Lasketaan 2 kulmaa ja toisen vastainen sivu. --*/
/*-- Ratkaisuja on 0, 1 tai 2. --*/
int Laske_BcC( void )
{
	int ratkaisut;
	double dA, dB, dC, da, db, dc;
	double sinB, tanc2, tanC2 ;

	da = kol1.sivu[0].rad;
	db = kol1.sivu[1].rad;
	dA = kol1.kulma[0].rad;

	if(sin(dA)*sin(db) > sin(da))
		return 1; /*- Ei ratkaisua. -*/

	if(fabs(M_PI/2-da)<=fabs(M_PI/2-db))
		printf( "\n Kolmiolla on 1 ratkaisu. \n" );
	if(fabs(M_PI/2-da)>fabs(M_PI/2-db))
		printf( "\n Kolmiolla on 2 ratkaisua. \n" );

	sinB = sin(db)*sin(dA)/sin(da);
	dB = kol1.kulma[1].rad = asin(sinB);
	tanc2 = tan((da-db)/2)*sin((dA+dB)/2)/sin((dA-dB)/2);
	tanC2 = sin((da-db)/2)/(tan((dA-dB)/2)*sin((da+db)/2));

	kol1.sivu[2].rad = dc = 2*atan(tanc2);
  	kol1.kulma[2].rad = dC = 2*atan(tanC2);

	Radtodeg( &kol1.kulma[1] );
	Radtodeg( &kol1.sivu[2] );
	Radtodeg( &kol1.kulma[2] );
	Tuolasketut1();

		kol2 = kol1;  /*- Kopioidaan tunnetut osat. -*/
		dB = kol2.kulma[1].rad = M_PI - dB;
		tanc2 = tan((da-db)/2)*sin((dA+dB)/2)/sin((dA-dB)/2);
		tanC2 = sin((da-db)/2)/(tan((dA-dB)/2)*sin((da+db)/2));

		kol2.sivu[2].rad = dc = 2*atan(tanc2);
   		kol2.kulma[2].rad = dC = 2*atan(tanC2);

		Radtodeg( &kol2.kulma[1] );
		Radtodeg( &kol2.sivu[2] );
		Radtodeg( &kol2.kulma[2] );
		Tuolasketut2();
	/*- Väliaikainen ratkaisu esittää aina molemmat, koska en ole
	vielä löytänyt menetelmää päätellä oikea tulos
	yhden ratkaisun tapaukessa. -*/
	return 0;
}

/*-- Tämä ohjelma on osa laajemmaksi tarkoitettua kokonaisuutta,
  mutta toimii käännettynä itsenäisesti.  Ohjelmassa on vielä
  trigonometrisiä miinoja, joita en ole löytänyt.--*/

/* End of File, */

Päärynämies [27.04.2008 23:30:32]

#

No mitä tuota koodaustapaa nopeasti katselin, niin muutamia seikkoja nousi mieleen. Funktioiden nimeämiskäytäntö ei ole täysin yhtenäinen. TuoOsa -funktio poikkeaa nimeämiseltään, sillä siinä on keskellä iso kirjain toisin kuin muissa. Radtodeg ja Degtorad ovat nimetty englanniksi, kun muut funktiot on nimetty suomeksi.

Sisennykset ovat myös joissakin kohdin hieman oudosti, kun nille ei näy syytä olevan. Esimerkkinä vaikka tuo viimeinen funktio Laske_BbC.

Nuo olivat lähinnä tuollaisia muotoseikkoja, mutta hyviä huomioida varsinkin, jos koodin koko kasvaa tai koodia pitää muidenkin lukea tai muokata. Itse koodin sisältöön ja toimintaan en jaksanut perehtyä.

jormi [29.04.2008 09:20:17]

#

Olen saanut valmiiksi 6 perustapausta vinokulmaisen pallokolmion ratkaisemiseksi, tämä esimerkki on niistä yksi, testailen niitä merenkulun laskutehtäviin.
1. Haetunnetut() ja Tuolasketut() funktioissa kannattaa osien tekstejä parantaa, nyt tulee annetuksi osat väärässä järjestyksessä tai tulkittua tuloksia ensin väärin.
2. Hyvää tulostuksessa on kaikkien kolmen tuntemattoman esitys, vaikka tehtävä vaatii vain yhtä. Kaikkien osien näkeminen helpottaa helpottaa asian ymmärtämistä.
3. main() fuktion kaksinkertainen getchar() näyttää toimivan hyvin, mutta kyllä harmittaa, kun se on niin tökerön näköinen.
4. Kirjastofunktio modf() toimii tässä oikein, koska osat ovat positiivisia. Jos käyttää funktiota negatiivisille luvuille, kanattaa sen toiminta omassa kirjastossa tarkastaa.
Tämä on ehkä turhan iso esimerkki koknaisuutena käsiteltäväksi, olen iloinen pienistäkin vihjeistä. Yritin karsia pois lcc-Win32 kääntäjän erikoisuudet, jotta ohjelma kääntyisi muillakin kääntäjillä. JVM

os [29.04.2008 14:37:31]

#

Minkälaisia vihjeitä oikeastaan kaipaat?

Matemaattiset kaavat voi tarkistaa ainakin MathWorldista tai Wikipediasta, mutta ne eivät tietenkään ole ongelman suora ratkaisu. Lineaarialgebra on tällaisissa ongelmissa yleensä analyyttistä geometriaa helpompi peruslähestymistapa.

Itse lähtisin tuota kyseistä ongelmaa (yksikköpallolla olevan kolmion 2 sivua (alfa,beta) ja toisen vastainen kulma (gamma) tunnetaan) ratkaisemaan niin, että laitetaan yksi tunnettu sivukaari xy-tasolle, toinen päätepiste y-akselilla pisteessä (0,1), ja toinen pisteessä (sin(alfa), cos(alfa)). Tästä saadaan ratkaistua tuntematonta sivua vastaavan isoympyrän tason yhtälö muodossa, z = sin(gamma) x. Tuntemattoman pisteen paikkavektorin ja xy-tason toisen pisteen paikkavektorin välisestä pistetulosta saadaan yhtälö: sin(alfa) x + cos(alfa) y = cos(beta). Viimeinen tarvittava yhtälö on x2 + y2 + z2 = 1. Näistä saadaan ratkaistua (yhtälöryhmä -> toisen asteen yhtälö) tuntemattoman pisteen koordinaatit, x,y,z, jolloin voidaan helposti laskea kolmiosta kaikki loput tiedot. Muidenkin pallokolmio-ongelmien luulisi ratkeavan vaihtoehtoisesti tällä tyylillä.

Tietenkin jos olet soveltanut jonkin kirjan kaavoja, niin ne varmaankin ovat oikein.

Tuossa C-koodissa ei sinänsä ole (tämän kokoluokan ohjelmassa) mitään vakavia virheitä, mutta turhien globaalien muuttujien (kol1, kol2) käyttöä ja omintakeisia sisennyksiä kannattaa silti välttää. Standardisyöte- ja tulostevirtoja ei ole tarkoitettu kovin monimutkaisten "interaktiivisten" ohjelmien tekoon, joten pelkästään näitä käyttäen tulee "näppäinkomentojen" lukemisesta yms. väkisinkin melko kömpelöä. Tuollaisten getchar-viritelmien tilalle ei siis C:n standardikirjastoista löydy mitään elegantimpia vaihtoehtoja.

Tuo OSA-rakenne on kyllä hiukan omituinen. Parempi olisi tallentaa kulmat suosiolla radiaaneina ja tehdä muunnos toiseen kulmanotaatioon tulostuksen ja syötteen lukemisen yhteydessä. Ja minkä takia muuten asteet ja minuutit tallennetaan double-tyyppisinä, vaikka ne ovat aina kokonaislukuja?

jormi [29.04.2008 15:40:15]

#

Asteet, minuutit ja sekuntit olivat alunperin int-tyyppisiä, mutta merenkulun oppikirjojen esimerkit ovat usein annettu desimaaliminuutin ( 12.3' ) tarkkuudella tai desimaaliasteina. Näinollen katsoin double-tyypin omaan käyttööni helpoimmaksi ratkaisuksi.
Kokeilun arvoinen asia voisi olla koko kuuden ohjelman sarjan muuttaminen long double tyyppiseksi, tälle löytyy kirjastosta trigonometriset funktiot. Minulla on juuri testissä 'mukava kolmio', kulmat asteissa 30, 40 ja 120, vastaavat sivut 30 ,40 ja 60. Kun tämän syöttää johonkin näistä ohjelmista, vastaukset eivät koskaan ole tasa-asteisia, vaan aina on minuutin luokkaa olevia eroja.
Oppikirjan vastaukset on laskettu 1970-luvun funktiolaskimilla, omani on vähän uudempi, näiden tulokset poikkevat hiukan ohjelmilla lasketuista. Nuo struktuurit ovat aivan C-kielen, varsinkin osoittimien harjoittelua. JVM (Mistähän tuo kehys tämän ympärille putkahti ?)

jormi [30.04.2008 08:53:28]

#

os kirjoitti:

Minkälaisia vihjeitä oikeastaan kaipaat?

Tämä aihe näyttää jakautuvan kolmeen näkökulmaan.
1 Ohjelmointi. Tähän kuuluu omalta osaltani mm. osoittimien ja kirjastojen käytön opiskelua. Isompi haaste olisi C++ ja 3D grafiikka.
2. Matematiikka. Omalta osaltani trigonometrian opiskelua luotettavien kaavojen löytämiseksi. Ongelmia ovat esimerkiksi nollaa lähestyvät jakajat ja erotukset.
3 sovellus. Itse asiassa tarvitaan kaksi kolmiota.
3A. Maapallolla oleva avomeripurjehduksen kolmio, jonka kärjet ovat aluksen lähtöpaikka, tulopaikka ja pohjoisnapa.
3B. Toinen on taivaanpallolla oleva nautinen kolmio, jonka kärjet ovat horisonttikoordinaatiston Zenit, ekvaattorikoordinatiston pohjoisnapa ja tähden sijainti. Tämä on vaikea opiskeltava.
P. S. Tähtimittaus näyttää olleen esillä 1950-luvun lopun kertausharjoituksissa, vaan taisipa asia mennä Rovajärven metsissä kaikilta yli hilseen. JVM.

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta