Σημειωματάριο Πέμπτης 8/10/2015

Μιλήσαμε σήμερα (α) για τον τρόπο που οι πραγματικοί αριθμοί αποθηκεύονται στον υπολογιστή, (β) για μεταβλητές τύπου string (κείμενο), (γ) για μεταβλητές τύπου λογικού (boolean), για την εκτέλεση εντολών υπό συνθήκη (εντολή if, ή if .. else).

Αναπαράσταση των πραγματικών αριθμών στον υπολογιστή και μερικές συνέπειες

Κάποιοι πραγματικοί αριθμοί (π.χ. αυτοί που έχουν άπειρα δεκαδικά ψηφία) δε μπορούν να αναπαρασταθούν ακριβώς στον υπολογιστή, ούτε γίνονται οι διάφορες πράξεις μεταξύ τους ακριβώς (σε αντίθεση με τους ακεραίους, που οι πράξεις μεταξύ τους γίνονται πάντα ακριβώς, εκτός αν βγούμε εκτός των ορίων που θέτει ο υπολογιστής και πάμε να χειριστούμε πολύ μεγάλους ακεραίους, οπότε κάποιες πράξεις είναι αδύνατο να γίνουν).

Ιδιαίτερα δε όταν οι πράξεις που κάνουμε σε αυτούς δεν είναι απλές αλγεβρικές πράξεις (+, -, *, /) αλλά είναι κάποιες δύσκολες στον υπολογισμό τους συναρτήσεις (τετραγωνικές ή άλλες ρίζες, τριγωνομετρικές συναρτήσεις, λογάριθμοι και εκθετικά, που υπολογίζονται μόνο κατά προσέγγιση ακόμη και αν οι αλγεβρικές μας πράξεις γίνονται ακριβώς) τότε μπορούμε να είμαστε σίγουροι ότι η έλλειψη ακρίβειας στις πράξεις μας είναι κάτι που δε μπορούμε να το αγνοήσουμε.

Δείτε για παράδειγμα τον επόμενο κώδικα, που παίρνει την τετραγωνική ρίζα του 2, την υψώνει στο τετράγωνο και τη συγκρίνει με το 2, παράγοντας το αποτέλεσμα ότι δεν είναι ίσα αυτά τα δύο.

Στο παρακάτω πρόγραμμα η πρώτη γραμμή import math χρησιμεύει ώστε να "φορτώσει" η python τις μαθηματικές συναρτήσεις βιβλιοθήκης, μεταξύ των οποίων και η τετραγωνική ρίζα math.sqrt που χρησιμοποιούμε για να υπολογίσουμε στη float μεταβλητή x την ποσότητα \(\sqrt{2}\).

Η έκφραση x**2==2 που εκτυπώνεται στο τέλος, και της οποίας η τιμή είναι η λογική τιμή False, είναι η λογική έκφραση που είναι True αν και μόνο αν οι δύο εκφράσεις x**2 και 2, αριστερά και δεξιά του τελεστή ελέγχου ισότητας ==, είναι ίσες.

Ενώ λοιπόν μαθηματικά θα περιμέναμε οι δύο αυτές εκφράσεις να είναι ίσες, στην πράξη δεν είναι και αυτό οφείλεται στο ότι όλες οι πράξεις μεταξύ floats γίνονται προσεγγιστικά στον υπολογιστή.

In [4]:
import math

x = math.sqrt(2)

print x**2==2
False

Προσπαθώντας να διαλευκάνουμε το μυστήριο του γιατί σύμφωνα με τον υπολογιστή μας \(\sqrt{2}^2 \neq 2\) υπολογίζουμε τη διαφορά αυτών των δύο ποσοτήτων στη μεταβλητή diff και την τυπώνουμε με print. Βλέπουμε ότι η διαφορά είναι ένας πολύ μικρός (της τάξης του \(10^{-16}\)) αλλά θετικός αριθμός, και έτσι εξηγείται το γιατί ο υπολογιστής μας δεν τα θεωρεί ίσα.

In [2]:
import math

x = math.sqrt(2)

diff = x**2 - 2

print diff 
4.4408920985e-16

Η λύση στο παραπάνω πρόβλημα είναι ποτέ να μη συγκρίνουμε (για ισότητα) δύο πραγματικούς αριθμούς (ποσότητες τύπου float) στον υπολογιστή μας με το σύμβολο == (που δουλεύει όμως μια χαρά για συγκρίσει ακεραίων ποσοτήτων) αλλά πάντα να επιτρέπουμε ένα περιθώριο σφάλματος. Παίρνουμε συνεπώς τη διαφορά των δύο ποσοτήτων, κατ' απόλυτο τιμή (αυτό το επιτυγχάνουμε με τη συνάρτηση abs, που μας επιστρέφει την απόλυτη τιμή της ποσότητας που της περνάμε, οπότε π.χ. το abs(-2) και το abs(2) κάνουν και τα δύο 2), και να θεωρούμε τις δύο ποσότητες ίσες αν η διαφορά τους είναι μικρότερη από κάποιο πολύ μικρό αριθμό. Το πόσο μικρό αριθμό εξαρτάται πραγματικά από το τι πρόβλημα πάμε να λύσουμε, αλλά μια τιμή της τάξης του \(10^{-8}\) όπως χρησιμοποιούμε εδώ είναι στις περισσότερες περιπτώσεις ΟΚ.

In [8]:
import math

x = math.sqrt(2)

diff = x**2 - 2

diff = abs(diff)

print diff < 1e-8
True

Στον παρακάτω κώδικα βλέπουμε μια πιο συντομογραμμένη (αλλά ουσιαστικά ίδια) μορφή του προηγούμενου προγράμματος, όπου δε χρησιμοποιούμε καθόλου κάποια extra μεταβλητή (diff) αλλά τυπώνουμε κατευθείαν την τιμή της λογικής έκφρασης

abs(x**2-2) < 1e-8

η οποία είναι True αν και μόνο αν η ανισότητα που περιγράφει ισχύει (αφού υπολογιστούν τα αριστερά και δεξιά μέλη της), αλλιώς είναι False.

In [9]:
import math

x = math.sqrt(2)

print abs(x**2-2) < 1e-8
True

Στο επόμενο κομμάτι κώδικα βλέπουμε τη μεταβλητή s που είναι τύπου string (κομμάτι κειμένου) και όχι αριθμού. Οι τιμές μιας μεταβλητής τύπου string περιγράφονται στον κώδικά μας πάντα μέσα σε εισαγωγικά, απλά ('...') ή διπλά ("..."). Όταν τυπώνουμε μια τέτοια μεταβλητή τα εισαγωγικά αυτά δεν τυπώνονται.

In [3]:
s = "Mihalis Kolountzakis"
print s
Mihalis Kolountzakis

Βλέπουμε εδώ δύο μεταβλητές f και l τύπου string και πώς αυτές συγκολλούνται με την πράξη +. Μεταξύ strings λοιπόν το σύμβολο + σημαίνει κάτι το τελείως διαφορετικό απ' ό,τι μεταξύ αριθμών.

In [4]:
f = "Mihalis"
l = "Kolountzakis"

s = f + l

print s
MihalisKolountzakis

Στο προηγούμενο κομμάτι κώδικα το όνομα και το επώνυμο συγκολλήθηκαν χωρίς μεταξύ τους κενό. Τώρα το προσδιορίζουμε ρητά και έχουμε μια συγκόλληση από τρία strings το μεσαίο από τα οποία έχει απλά ένα κενό χαρακτήρα μέσα του.

In [5]:
f = "Mihalis"
l = "Kolountzakis"

s = f + " " + l

print s
Mihalis Kolountzakis

Εδώ βλέπουμε πώς σε μια εντολή print μπορούμε και τυπώνουμε αντικείμενα διαφόρων τύπων. Στη συγκεκριμένη εντολή print τυπώνουμε 4 αντικείμενα, το τελευταίο από τα οποία (μεταβλητή h) είναι αριθμός και τα υπόλοιπα είναι strings. Η εντολή print, όταν τυπώνει παραπάνω από ένα αντικείμενα, πάντα τυπώνει και ένα κενό χαρακτήρα ανάμεσα σε δύο διαδοχικά αντικείμενα.

In [6]:
f = "Mihalis"
l = "Kolountzakis"
h = 1.76

s = l+", "+f

print "The name is", s, "and the height is", h
The name is Kolountzakis, Mihalis and the height is 1.76

Στο παρακάτω (παραλλαγή των προγραμμάτων στην αρχή αυτής της σελίδας) χρησιμοποιούμε και μια μεταβλητή condition που είναι λογικού (boolean) τύπου, παίρνει δηλ. τις τιμές True ή False. Το ποια τιμή παίρνει στην εντολή ανάθεσης

condition = abs(x**2-2) < 1e-8

εξαρτάται από το αν ισχύει ή όχι η ανισότητα που είναι δεξιά του συμβόλου της ανάθεσης =.

In [18]:
import math

x = math.sqrt(2)

condition = abs(x**2-2) < 1e-8

print condition
True

Εδώ βλέπουμε ότι δύο λογικές εκφράσεις, οι (x>1) και (x<3) μπορούν να συνδεθούν μεταξύ τους με τον τελεστή and (και). Η τιμή μιας έκφρασης A and B, όπου A και B είναι λογικές εκφράσεις είναι True αν και μόνο αν και η A και η B είναι True, αλλιώς είναι False.

Στη μεταβλητή condition εδώ υπολογίζουμε το αν ο αριθμός x, όποιος και αν είναι αυτός, ανήκει στο διάστημα \((1,3)\), αφού το να ανήκει ένας αριθμός στο διάστημα αυτό ισοδυναμεί με το να είναι μεγαλύτερος του 1 και μικρότερος του 3.

In [20]:
x = 1.5

condition = (x>1) and (x<3)

print condition
True

Στο παρακάτω πρόγραμμα απαντάμε στο ερώτημα αν ο αριθμός x είναι ή όχι στο σύνολο \[ E = (0,1) \cup (2, 3]. \] Στη μεταβλητή condition1 υπολογίζουμε το αν το x ανήκει ή όχι στο διάστημα \((0,1)\) ενώ στη μεταβλητή condition2 υπολογίζουμε το αν το x ανήκει ή όχι στο διάστημα \((2,3]\). Για να ανήκει το x στο \(E\) αρκεί να ισχύει το condition1 ή το condition2 και έτσι παίρνει τιμή η μεταβλητή condition.

In [7]:
x = 0.9

condition1 = (x >= 0) and (x <= 1)

condition2 = (x > 2) and (x <= 3)

condition = condition1 or condition2

print condition
True

Η μόνη διαφορά του επόμενου προγράμματος από το αμέσωως προηγούμενο είναι ότι αντί να τυπώσει True όταν το x ανήκει στο \(E\) τυπώνει False σε αυτήν ακριβώς την περίπτωση. Αυτό επιτυγχάνεται απλά με τη χρήση του τελεστή not που αντιστρέφει τη λογική τιμή αυτού που ακολουθεί.

In [10]:
x = 0.9

condition1 = (x >= 0) and (x <= 1)

condition2 = (x > 2) and (x <= 3)

condition = condition1 or condition2

condition = not condition

print condition
False

Στο παρακάτω πρόγραμμα εισάγουμε την εντολή if ... else ... η οποία μας επιτρέπει να εκτελούμε μια άλλη εντολή (αυτή που "υπάγεται" στο if και βρίσκεται γραμμένη ακριβώς από κάτω του και σπρωγμένη (indented) προς τα μέσα) μόνο αν η συνθήκη (λογική έκφραση) που αναφέρεται στο if είναι True. Αν η έκφραση αυτή είναι False τότε δεν εκτελείται η εντολή (ή εντολές) που υπάγονται στο if αλλά αυτές που υπάγονται στο else. Αν δεν υπάρχει αντίστοιχο else στο if και η συνθήκη είναι False τότε απλά δεν εκτελείται η εντολή που υπάγεται στο if.

Στο πρόγραμμα αυτό συνεχίζουμε τη δουλειά που κάναμε στο προηγούμενο (η λογική μεταβλητή condition είναι True με τον υπολογισμό που κάνουμε αν και μόνο αν το x είναι στο σύνολο \(E = (0, 1) \cup (2, 3]\)) και απλά τυπώνουμε επιπλέον ένα πληροφοριακό μήνυμα που μας λέει αν το x είναι στο σύνολο ή όχι. Το ποιο από τα δύο μηνύματα τυπώνεται εξαρτάται από το αν η λογική συνθήκη που αναφέρεται στο if είναι True ή False.

Όσον αφορά το συντακτικό της εντολής if προσέξτε ότι πάντα απαιτείται η ύπαρξη του συμβόλου : στο τέλος της γραμμής του if (μετά τη λογική έκφραση) και στο τέλος της γραμμής του else.

In [27]:
x = 1.1

condition1 = (x >= 0) and (x <= 1)

condition2 = (x > 2) and (x <= 3)

condition = condition1 or condition2

if condition :
    print "The number", x, "is in the set E."
else:
    print "The number", x, "is NOT in the set E."
The number 1.1 is NOT in the set E.

Στο παρακάτω πρόγραμμα κάνουμε ουσιαστικά την ίδια δουλειά με πριν αλλά χωρίς να υπολογίσουμε τις λογικές μεταβλητές, αλλά γράφοντας κατευθείαν τη συνθήκη που μας λέει αν το x ανήκει στο \(E\) ή όχι δίπλα στο if. Επίσης το σύνολό μας έχει αλλάξει από πριν και αποτελείται πλέον από τρία διαστήματα

\[ E = (0, 1) \cup (2, 3] \cup [5, +\infty). \]

Μια άλλη διαφορά με πριν είναι ότι τώρα υπάρχουν πολλές εντολές που υπάγονται στο if και εκτελούνται αν και μόνο αν η λογική έκφραση του if είναι True. Όλες αυτές οι εντολές εκτελούνται με τη σειρά. Μετά τα δύο print έχουμε άλλες τρεις εντολές if (που υπάγονται όλες στο πρώτο if του προγράμματος--δείτε και πώς είναι στοιχισμένες). Οι εντολές αυτές if μας τυπώνουν το αντίστοιχο μήνυμα αν το x ανήκει στο πρώτο, στο δεύτερο ή στο τρίτο διάστημα του συνόλου.

Τέλος υπάρχει και μια εντολή

print "The end"

η οποία τυπώνεται στο τέλος του προγράμματος. Παρατηρείστε ότι από τον τρόπο που είναι στοιχισμένη αυτή η εντολή προκύπτει ότι αυτή δεν υπάγεται στο else και άρα εκτελείται πάντα, ανεξάρτητα από την πορεία που θα πάρει ο έλεγχος του προγράμματος μέσα στο if. Αν αυτή η τελευταία εντολή ήταν στοιχισμένη πιο μέσα, ακριβώς κάτω από την εντολή που ήδη υπάγεται στο else τότε θα υπαγόταν και αυτή στο else και θα εκτελούνταν μόνο όταν "έπιανε" το else.

Το πού είναι στοιχισμένη μια εντολή στην python έχει άμεση επίπτωση για το status αυτής της εντολής μέσα στο πρόγραμμα και γι' αυτό η στοίχιση θέλει μεγάλη προσοχή (αυτό είναι ιδιαίτερο χαρακτηριστικό της python και δεν το συναντάει κανείς σε άλλες γλώσσες, στις οποίες η ομαδοποίηση εντολών στηρίζεται σε άλλα συντακτικά κατασκευάσματα). Είναι ιδιαίτερα κοινό να πάρει σφάλμα κατά το τρέξιμο ενός προγράμματος python με την επεξήγηση indentation error, πράγμα που σημαίνει ότι κάτι δεν πάει καλά στη στοίχιση.

In [35]:
x = 7

if ((x >= 0) and (x <= 1)) or ((x > 2) and (x <= 3)) or (x>=5) :
    print "The number", x, "is in the set E."
    print "You got it."
    if (x >= 0) and (x <= 1):
        print "First interval"
    if (x > 2) and (x <= 3):
        print "Second interval"
    if x >= 5:
        print "Third interval"
else:
    print "The number", x, "is NOT in the set E."

print "The end"
The number 7 is in the set E.
You got it.
Third interval
The end

Στο επόμενο έχουμε μια πιο οργανωμένη παραλλαγή του προηγούμενου προγράμματος, όπου ξαναεισάγουμε τις μεταβλητές condition1, condition2, condition3, που, με τον τρόπο που υπολογίζονται, είναι True αν και μόνο αν το x ανήκει στο πρώτο, δεύτερο ή τρίτο διάστημα του \(E\) που ορίζεται στο προηγούμενο. Η συνθήκη του "μεγάλου" if τώρα εκφράζεται μέσω των μεταβλητών αυτών και το ίδιο και οι συνθήκες των εσωτερικών if.

Το πρόγραμμα αυτό είναι και πιο ευανάγνωστο από το προηγούμενο αλλά και λίγο πιο γρήγορο μια και δεν ξαναυπολογίζονται χωρίς λόγο λογικές ποσότητες που έχουν ήδη υπολογοστεί, όπως γινόταν στο προηγούμενο στα εσωτερικά if.

In [37]:
x = 7

condition1 = (x >= 0) and (x <= 1)
condition2 = (x > 2) and (x <= 3)
condition3 = x >= 5

if condition1 or condition2 or condition3:
    print "The number", x, "is in the set E."
    print "You got it."
    if condition1:
        print "First interval"
    if condition2:
        print "Second interval"
    if condition3:
        print "Third interval"
else:
    print "The number", x, "is NOT in the set E."

print "The end"
The number 7 is in the set E.
You got it.
Third interval
The end

Εδώ αλλάζουμε πρόβλημα. Σε ένα μάθημα στο πανεπιστήμιο που έχει ένα τελικό \(F\), δύο ενδιάμεσα διαγωνίσματα \(T1, T2\) και βαθμό ασκήσεων στο σπίτι \(A\) (όλα στην κλίμακα 0-10) ο τελικός βαθμός στο μάθημα είναι

\[ 0.5 F + 0.2 T1 + 0.2 T2 + 0.1 A \]

αν \(A \ge 3\), αλλιώς ο τελικός βαθμός είναι

\[ 0.5F + 0.25 T1 + 0.25 T2. \]

Στο παρακάτω πρόγραμμα υπολογίζεται ο τελικός βαθμός κατ' αρχήν στο πρώτο if ... else ... που υπάρχει.

Έπειτα εκτυπώνεται ο βαθμός με την εντολή print grade και τέλος, με το τελευταίο if, τυπώνεται το αν ο φοιτητής πέρασε το μάθημα ή όχι (κάνουμε εδώ τη σύμβαση ότι αν ο τελικός βαθμός είναι 4.75 ή μεγαλύτερος τότε ο φοιτητής περνάσει το μάθημα).

In [42]:
A = 10
T1 = 10
T2 = 10
F = 2.99

if F >= 3:
    grade = 0.5*F + 0.2*T1 + 0.2*T2 + 0.1*A
else:
    grade = 0.5*F + 0.25*T1 + 0.25*T2

print grade

if grade >= 4.75:
    print "Passed"
else:
    print "Failed"
6.495
Passed