Περαιτέρω για Συναρτήσεις Πέρασµα µέσω διευθύνσεως παράµετροι εξόδου Εµβέλεια ονοµασιών Παράµετροι συναρτήσεις οκιµή και αποσφαλµάτωση ενός προγράµµατος
Παράδειγµα: Ρίζες ευτεροβάθµιας Εξίσωσης ax 2 + bx + c = 0 b ± (b 2 4ac) x = 2a Περιπτώσεις: y y y x x x δύο ρίζες µία ρίζα καµµία ρίζα Ορισµός Συνάρτησης quadroots a how many b quadroots root 1 c root 2 /* πρωτότυπο συνάρτησης */ void quadroots (double a, double b, double c, int *how_many, double *root1, double *root2); 2
/* Oρισµός */ void quadroots (double a, double b, double c, int *how_many, double *root1, double *root2){ double diff = b*b 4*a*c; if (diff < 0.0) {*how_many = 0; else if (diff == 0.0) {*how_many = 1; *root1 = b/(2*a); else {*how_many = 2; *root1 = ( b + sqrt(diff))/(2*a); *root2 = ( b sqrt(diff))/(2*a); /* Ορισµός main */ void main () { int num_roots; double a, b, c, root_1, root_2; printf( \nenter coefficients: ); scanf( %lf%lf%lf, &a, &b, &c); quadroots(a, b, c, &num_roots, &root_1, &root_2); switch (num_roots) { case 0 : printf( \nthere are no roots\n ); break; case 1 : printf( \nthere is one root which is %f\n, root_1); break; case 2: printf( \nthere are two roots which are ); printf( %f and %f\n, root_1, root_2); 3
$ roots Enter coefficients: 2 4 2 There is one root which is 1.000000 y 2x 2 + 4x + 2-1 x $ roots Enter coefficients: 1 5 3 There are two roots which are 0.697224 and 4.302776 y x 2 + 5x + 3 x $ roots Enter coefficients: 3 1 1 There are no roots y 3x 2 + x + 1 x 4
a 2.0 main num_roots 1 b root_1 4.0-1.0 c 2.0 root_2 quadroots a how_many 2.0 b diff root1 4.0 0.0 c 2.0 root2 5
Οι παράµετροι χωρίζονται σε Παραµέτρους εισόδου (τιµές) Παραµέτρους εξόδου (διευθύνσεις) Οι παραµέτροι εισόδου (input parameters) της συνάρτησης quadroots είναι οι a, b και c. Οι παραµέτροι εξόδου (output parameters) αυτής της συνάρτησης είναι οι η how_many, root1 και root2. Οι παράµετροι εισόδου διοχετεύουν πληροφορίες στην συνάρτηση. Οι παράµετροι εξόδου επιτρέπουν στην συνάρτηση να διοχετεύει προς τα έξω πληροφορίες, µάλιστα πέραν της µίας πληροφορίας. Επίσης παράµετροι εξόδου µπορούν να διοχετεύσουν και πληροφορίες προς τη συνάρτηση. Με άλλα λόγια οι χώροι των οποίων οι διευθύνσεις αποτελούν αυτές τις παραµέτρους δεν είναι κατ ανάγκη κενοί χώροι που θα γεµίσουν από τη συνάρτηση, αλλά µπορεί να περιέχουν πληροφορίες τις οποίες θα επεξεργαστεί η συνάρτηση. Εποµένως παράµετροι εξόδου µπορεί να είναι ταυτόχρονα και παράµετροι εισόδου. 6
Παράλληλα, µία συνάρτηση µπορεί να έχει και τιµή εξόδου, η οποία διοχετεύεται προς τα έξω διαµέσου της εντολής return. Η εντολή return επιτρέπει τη ρητή επιστροφή, από µέρους της συνάρτησης, µίας µόνο πληροφορίας. Παράδειγµα: Επεξεργασία Κλασµατικών Αριθµών Ένας κλασµατικός αριθµός αντιπροσωπεύεται από δύο ακέραιους αριθµούς, τον αριθµητή (numerator) και τον παρανοµαστή (denominator). Ο κλασµατικός αριθµός m/n είναι σε κανονική µορφή εάν n > 0, και ο µέγιστος κοινός διαιρέτης ανάµεσα στην απόλυτη τιµή του m και τη τιµή του n είναι το 1 Ένας αρνητικός κλασµατικός αριθµός έχει αρνητικό αριθµητή και θετικό παρανοµαστή. Το 0 αναπαριστάται ως 0/1. 7
/* Συνάρτηση sign για το πρόσηµο ακεραίου */ int sign (int n) { if (n == 0) return 0; else if (n > 0) return 1; else return 1; /* Συνάρτηση norm η οποία µετατρέπει κλασµατικό αριθµό σε κανονική µορφή */ int norm (int *nup, int *denop) { int divisor; if (*denop == 0) return 0; else { *nup = *nup * sign(*denop); *denop = abs(*denop); divisor = gcd(abs(*nup), *denop); *nup = *nup / divisor; *denop = *denop / divisor; return 1; nup denop norm µπορούσε να γίνει η κανονικοποίηση; 8
H συνάρτηση norm έχει δύο παραµέτρους, οι οποίες ταυτόχρονα αποτελούν εισόδους και εξόδους. Αυτό γιατί οι εν λόγω χώροι, οι διευθύνσεις των οποίων θα είναι τα ορίσµατα σε κλήσεις της συνάρτησης norm, αναµένεται να περιέχουν έγκυρες πληροφορίες, τον αρχικό αριθµητή και παρανοµαστή, οι οποίοι θα αντικατασταθούν από τις κανονικοποιηµένες τους τιµές, βάσει της διεργασίας της συνάρτησης. Η ρητή τιµή εξόδου της συνάρτησης ερµηνεύεται ως λογική τιµή, η οποία επισηµαίνει κατά πόσον η κανονικοποίηση του κλασµατικού αριθµού ήταν εφικτή, κατά πόσον δηλαδή ο παρανοµαστής δεν ήταν το µηδέν. Άλλο παράδειγµα συνάρτησης όπου οι παράµετροι αποτελούν ταυτόχρονα εισόδους και εξόδους είναι η συνάρτηση swap. Όµως στην συνάρτηση quadroots, οι παράµετροι εξόδου είναι µόνο για έξοδο. Οι χώροι, οι διευθύνσεις των οποίων αποτελούν τα εν λόγω ορίσµατα σε κλήσεις της συνάρτησης, δεν περιέχουν έγκυρες πληροφορίες. Είναι κενοί χώροι οι οποίοι γεµίζουν ως αποτέλεσµα της διεργασίας της quadroots. 9
Έστω nu deno 6 36 norm(&nu, &deno) 1 και nu deno 1 6 Τα Βήµατα Αναλυτικά nu deno 6 36 nup norm denop divisor Αρχικά 10
nu 6 deno 36 nup norm denop divisor 1 ο Βήµα nu deno 1 6 norm nup denop divisor 6 1 2 ο Βήµα 11
Αριθµητικοί Τελεστές x/y + u/v = (xv + uy) / yv x/y u/v = (xv uy) / yv x/y u/v = xu / yv x/y u/v = xv / yu /* Συναρτήσεις για πρόσθεση, αφαίρεση, πολλαπλασιασµό και διαίρεση κλασµατικών αριθµών */ int add (int n1, int d1, int n2, int d2, int *n, int *d) { *n = n1 * d2 + n2 * d1; *d = d1 * d2; return norm(n,d); int sub (int n1, int d1, int n2, int d2, int *n, int *d) { *n = n1 * d2 n2 * d1; *d = d1 * d2; return norm(n,d); int mult (int n1, int d1, int n2, int d2, int *n, int *d) { *n = n1 * n2; *d = d1 * d2; return norm(n,d); int div (int n1, int d1, int n2, int d2, int *n, int *d) { *n = n1 * d2; *d = d1 * u2; return norm(n,d); 12
Σύγκριση Κλασµατικών Αριθµών x/y = u/v xv = yu x/y < u/v xv < yu x/y > u/v xv > yu /* Ορισµοί Συναρτήσεων */ int equals (int n1, int d1, int n2, int d2) { return n1 * d2 == d1 * n2; int less (int n1, int d1, int n2, int d2) { return n1 * d2 < d1 * n2; int greater (int n1, int d1, int n2, int d2) { return n1 * d2 > d1 * n2; Γενίκευση int compare (int op (int, int), int n1, int d1, int n2, int d2) { return op(n1 * d2, d1 * n2); Η πρώτη παράµετρος της compare είναι συνάρτηση. equals(1, 4, 2, 8) compare(equals_p, 1, 4, 2, 8) less(1, 4, 1, 2) compare(less_p, 1, 4, 1, 2) greater(1, 4, 1, 8) compare(greater_p, 1, 4, 1, 8) 13
Παράδειγµα: Ταξινόµηση τριών πραγµατικών αριθµών σε φθίνουσα σειρά. Τη βασική διεργασία θα την κάνει η συνάρτηση order. fst snd order Οι παράµετροι fst και snd είναι ταυτόχρονα είσοδοι και έξοδοι. void order (double *fst, double *snd){ double temp; if (*fst < *snd) {temp = *fst; *fst = *snd; *snd = temp; Εναλλακτικά void order (double *fst, double *snd){ if (*fst < *snd) swap(fst, snd); Εάν ο πρώτος αριθµός είναι µοκρότερος από το δεύτερο, εναλλάσσονται τα περιεχόµενά τους. Γιατί η κλήση swap(*fst, *snd) θα ήταν λανθασµένη; 14
Συνάρτηση main void main () { double num1, num2, num3; printf( \nenter three numbers ); scanf( %lf%lf%lf, &num1, &num2, &num3); order(&num1, &num2); order(&num1, &num3); order(&num2, &num3); printf( \nthe numbers in descending order are %f %f %f, num1, num2, num3); $ order3 Enter three numbers 5.3 9.8 10.2 The numbers in descending order are 10.2 9.8 5.3 Τα Βήµατα Αναλυτικά Αρχικά, µετά δηλαδή τη σάρωση των τριών αριθµών num1 num2 num3 5.3 9.8 10.2 15
Μετά την κλήση order(&num1, &num2) num1 num2 num3 9.8 5.3 10.2 Μετά την κλήση order(&num1, &num3) num1 num2 num3 10.2 5.3 9.8 Μετά την κλήση order(&num2, &num3) num1 num2 num3 10.2 9.8 5.3 Τροποποίηση της order ούτως ώστε η ταξινόµηση να γίνεται σε αύξουσα σειρά. void order (double *fst, double *snd) { if ( *fst > *snd) swap(fst, snd); Γενίκευση Η ταξινόµηση να γίνεται είτε σε φθίνουσα, είτε σε αύξουσα σειρά. 16
Η σειρά ταξινόµησης πρέπει να αποτελεί παράµετρο της order. σειρά fst snd order void order (char how, double *fst, double *snd) { switch (how) { case A : if (*fst > *snd) swap(fst, snd); break; case D : if (*fst < *snd) swap(fst, snd); Αυτή η λύση συνεπάγεται περιορισµούς. Καλύτερα να διοχετεύεται στη συνάρτηση το κατηγόρηµα, βάσει του οποίου θα γίνεται η ταξινόµηση. O τύπος (δηλαδή η διεπαφή) αυτού του κατηγορήµατος είναι int sop (double, double) όπου sop είναι η εικονική ονοµασία του κατηγορήµατος. void order (int sop (double, double), double *fst, double *snd){ if (sop(*fst, *snd)) swap(fst, snd); 17
Κατηγορήµατα ταξινόµησης int lessp (double x, double y) {return x < y; int greaterp (double x, double y) {return x > y; Επαναδιατύπωση της main για ταξινόµηση σε φθίνουσα σειρά. void main () { double num1, num2, num3; printf( \nenter three numbers ); scanf( %lf%lf%lf, &num1, &num2, &num3); order(lessp, &num1, &num2); order(lessp, &num1, &num3); order(lessp, &num2, &num3); printf( \nthe numbers in descending order are %f %f %f, num1, num2, num3); 18
Παράµετροι Συναρτήσεις Function Parameters Η χρήση τους παρέχει υψηλότερη αφαιρετικότητα. Παράδειγµα: Η µέθοδος του Νεύτωνα για τον υπολογισµό µίας ρίζας δεδοµένης συνάρτησης η οποία είναι συνεχής και παραγωγίσιµη γύρω από αυτό το σηµείο. Εάν το y είναι µία προσέγγιση στη ρίζα της συνάρτησης f, η έκφραση y f(y) f (y) δίνει µία καλύτερη προσέγγιση f(y) f(y) y o y o y o y o 19
Εφαρµογή µεθόδου για τον υπολογισµό τετραγωνικών ριζών, x = y Η τετραγωνική ρίζα αντιστοιχεί στη ρίζα της συνάρτησης f(y) = y 2 x η επόµενη προσέγγιση υπολογίζεται ως y f(y) / f (y) = y (y 2 x) / 2y = (y + x / y) / 2 Εφαρµογή µεθόδου για τον υπολογισµό τριγωνικών ριζών, 3 x = y Η τριγωνική ρίζα αντιστοιχεί στη ρίζα της συνάρτησης f(y) = y 3 x η επόµενη προσέγγιση υπολογίζεται ως y f(y) / f (y) = y (y 3 x) / 3y 2 = (2y + x / y 2 ) / 3 Συνάρτηση για την παράγωγο Αρχικά χρειάζεται να ορισθεί µία συνάρτηση, έστω deriv, η οποία υπολογίζει την παράγωγο δεδοµένης µαθηµατικής συνάρτησης, f, σε δεδοµένο σηµείο, x, νοουµένου ότι η f είναι συνεχόµενη στο σηµείο x. f (f(x+dx) f(x)) / dx x dx x+dx 20
f x deriv (f(x+dx) f(x)) / dx Ο τύπος (διεπαφή) της f είναι double f (double) Ορισµός deriv double deriv (double f (double), double x) { double dx = 0.0001; return (f(x+dx) f(x)) / dx; Ορισµός της συνάρτησης newton f y o η ρίζα της f newton f, µαθηµατική συνάρτηση, δηλαδή double f (double) και y o η αρχική προσέγγιση /* αναδροµικός ορισµός */ double newton (double f (double), double y) { double eps = 0.0001; if (abs(f(y)) < eps) return y; else return newton(f, y (f(y) / deriv(f,y))); 21
/* επαναληπτικός ορισµός */ double newton (double f (double), double y) { double eps = 0.0001; while (abs(f(y)) > eps) y = y (f(y) / deriv(f,y)); return y; Παραδείγµατα µαθηµατικών συναρτήσεων που µπορούν να χρησιµοποιηθούν ως ορίσµατα στην newton και deriv. double quadfn_1 (double x) { return x * x + 5.0 * x + 3.0; double quadfn_2 (double x) { return 2.0 * x * x + 4.0 * x + 2.0; newton(quadfn_1, 6.0) 4.302776 newton(quadfn_2, 0) 0.996044 Υπολογισµός τετραγωνικής ρίζας του 2 double sqrt_2 (double y) { return y * y 2.0 ; newton(sqrt_2, 2.0) 1.414216 22
Παράδειγµα: Τετραγωνικές ρίζες συναρτήσεων µε την µέθοδο του διατµηµατισµού (bisection method) f(x) f(x) a m b a m b a < b και πρόσηµο(f(a)) πρόσηµο(f(b)) Αλγόριθµος για τον υπολογισµό της ρίζας f(x) = 0 στο πεδίο [α,β] ε 0.0001 Επανέλαβε m (a+b) / 2 Εάν f(m) f(a) 0 b m /* η ρίζα είναι στο πεδίο [a,m] */ ιαφορετικά a m /* η ρίζα είναι στο πεδίο [m,b] */ ενόσω f(m) > ε m είναι η ρίζα της f 23
Εκλεπτυσµένος Αλγόριθµος Υπάρχει κάποιο µέγιστο όριο, MaxIter, στον αριθµό των επαναλήψεων. ε 0.0001 count 0 Επανέλαβε m (a+b) / 2 count count + 1 Εάν f(m) f(a) 0 b m /* η ρίζα είναι στο πεδίο [a,m] */ ιαφορετικά a m /* η ρίζα είναι στο πεδίο [m,b] */ ενόσω f(m) > ε και count < MaxIter m είναι η ρίζα της f, εάν f(m) ε Σηµείωση: Η επανάληψη ολοκληρώνεται όταν f(m) ε count = MaxIter (κανόνας DeMorgan): ή f(m) ε σηµαίνει επιτυχία count = MaxIter σηµαίνει αποτυχία νοουµένου ότι f(m) > ε 24
Ορισµός Συνάρτησης Bisect f a Bisect b ε επιτυχία ή όχι (παράµετρος εξόδου) ρίζα (ρητή τιµή εξόδου) double Bisect (double f (double), double a, double b, double eps, int *success) { const int MaxIter = 20; int count = 0; double Fa = f(a), Fb= f(b), mid = 1.0E37, Fmid; if (Fa * Fb > 0.0) *success = 0; else { do { mid = (a+b) / 2.0; Fmid = f(mid); if (Fa * Fmid <= 0.0) {b = mid; Fb = Fmid; else {a = mid; Fa = Fmid; count++; while (fabs(fmid) > eps && count < MaxIter); *success = fabs(fmid) <= eps; return mid; 25
Υπολογισµός της τετραγωνικής ρίζας του 2 double sqrt_2 (double y) { return y * y 2.0; int Found; double SQRT2 = Bisect(sqrt_2, 1.0, 2.0, 0.0001, &Found); Found 1 Bisect sqrt_2 1.0 2.0 0.0001 success SQRT2 1.414185 26
Η τιµή εξόδου διοχετεύεται στη µεταβλητή SQRT2 Παράδειγµα: Προσέγγιση του εµβαδού κάτω από µία καµπύλη µε τη µέθοδο του Simpson. y f(x) h a a+h a+2h a+3h b x Θεωρείται ότι x [a,b], f(x) 0. a b h εµβαδόν: h(a+b)/2 b a Εποµένως, b f(x) dx h/2 {[f(a) + f(a+h)] + [f(a+h) + f(a+2h)] + a + [f(b h) + f(b)] 27
Συνάρτηση integrate f a b integrate a f(x) dx b double integrate(double f (double), double a, double b) { const double h = 0.001; double x = a, sum = 0.0; while (x < b) { sum += f(x) + f(x+h); x += h; sum *= h/2.0; return sum; οκιµή συνάρτησης integrate double fn (double x) { return x*x + 2.0*x + 3.0; integrate(fn, 0.0, 5.0) 81.666667 28
29