Μιλήσαμε σήμερα (α) για τον τρόπο που οι πραγματικοί αριθμοί αποθηκεύονται στον υπολογιστή, (β) για μεταβλητές τύπου string (κείμενο), (γ) για μεταβλητές τύπου λογικού (boolean), για την εκτέλεση εντολών υπό συνθήκη (εντολή if, ή if .. else).
Κάποιοι πραγματικοί αριθμοί (π.χ. αυτοί που έχουν άπειρα δεκαδικά ψηφία) δε μπορούν να αναπαρασταθούν ακριβώς στον υπολογιστή, ούτε γίνονται οι διάφορες πράξεις μεταξύ τους ακριβώς (σε αντίθεση με τους ακεραίους, που οι πράξεις μεταξύ τους γίνονται πάντα ακριβώς, εκτός αν βγούμε εκτός των ορίων που θέτει ο υπολογιστής και πάμε να χειριστούμε πολύ μεγάλους ακεραίους, οπότε κάποιες πράξεις είναι αδύνατο να γίνουν).
Ιδιαίτερα δε όταν οι πράξεις που κάνουμε σε αυτούς δεν είναι απλές αλγεβρικές πράξεις (+, -, *, /)
αλλά είναι κάποιες δύσκολες στον υπολογισμό τους συναρτήσεις (τετραγωνικές ή άλλες ρίζες, τριγωνομετρικές συναρτήσεις, λογάριθμοι και εκθετικά, που υπολογίζονται μόνο κατά προσέγγιση ακόμη και αν οι αλγεβρικές μας πράξεις γίνονται ακριβώς) τότε μπορούμε να είμαστε σίγουροι ότι η έλλειψη ακρίβειας στις πράξεις μας είναι κάτι που δε μπορούμε να το αγνοήσουμε.
Δείτε για παράδειγμα τον επόμενο κώδικα, που παίρνει την τετραγωνική ρίζα του 2, την υψώνει στο τετράγωνο και τη συγκρίνει με το 2, παράγοντας το αποτέλεσμα ότι δεν είναι ίσα αυτά τα δύο.
Στο παρακάτω πρόγραμμα η πρώτη γραμμή import math
χρησιμεύει ώστε να "φορτώσει" η python τις μαθηματικές συναρτήσεις βιβλιοθήκης, μεταξύ των οποίων και η τετραγωνική ρίζα math.sqrt
που χρησιμοποιούμε για να υπολογίσουμε στη float
μεταβλητή x
την ποσότητα \(\sqrt{2}\).
Η έκφραση x**2==2
που εκτυπώνεται στο τέλος, και της οποίας η τιμή είναι η λογική τιμή False
, είναι η λογική έκφραση που είναι True
αν και μόνο αν οι δύο εκφράσεις x**2
και 2
, αριστερά και δεξιά του τελεστή ελέγχου ισότητας ==
, είναι ίσες.
Ενώ λοιπόν μαθηματικά θα περιμέναμε οι δύο αυτές εκφράσεις να είναι ίσες, στην πράξη δεν είναι και αυτό οφείλεται στο ότι όλες οι πράξεις μεταξύ float
s γίνονται προσεγγιστικά στον υπολογιστή.
import math
x = math.sqrt(2)
print x**2==2
Προσπαθώντας να διαλευκάνουμε το μυστήριο του γιατί σύμφωνα με τον υπολογιστή μας \(\sqrt{2}^2 \neq 2\) υπολογίζουμε τη διαφορά αυτών των δύο ποσοτήτων στη μεταβλητή diff
και την τυπώνουμε με print
. Βλέπουμε ότι η διαφορά είναι ένας πολύ μικρός (της τάξης του \(10^{-16}\)) αλλά θετικός αριθμός, και έτσι εξηγείται το γιατί ο υπολογιστής μας δεν τα θεωρεί ίσα.
import math
x = math.sqrt(2)
diff = x**2 - 2
print diff
Η λύση στο παραπάνω πρόβλημα είναι ποτέ να μη συγκρίνουμε (για ισότητα) δύο πραγματικούς αριθμούς (ποσότητες τύπου float
) στον υπολογιστή μας με το σύμβολο ==
(που δουλεύει όμως μια χαρά για συγκρίσει ακεραίων ποσοτήτων) αλλά πάντα να επιτρέπουμε ένα περιθώριο σφάλματος. Παίρνουμε συνεπώς τη διαφορά των δύο ποσοτήτων, κατ' απόλυτο τιμή (αυτό το επιτυγχάνουμε με τη συνάρτηση abs
, που μας επιστρέφει την απόλυτη τιμή της ποσότητας που της περνάμε, οπότε π.χ. το abs(-2)
και το abs(2)
κάνουν και τα δύο 2), και να θεωρούμε τις δύο ποσότητες ίσες αν η διαφορά τους είναι μικρότερη από κάποιο πολύ μικρό αριθμό. Το πόσο μικρό αριθμό εξαρτάται πραγματικά από το τι πρόβλημα πάμε να λύσουμε, αλλά μια τιμή της τάξης του \(10^{-8}\) όπως χρησιμοποιούμε εδώ είναι στις περισσότερες περιπτώσεις ΟΚ.
import math
x = math.sqrt(2)
diff = x**2 - 2
diff = abs(diff)
print diff < 1e-8
Στον παρακάτω κώδικα βλέπουμε μια πιο συντομογραμμένη (αλλά ουσιαστικά ίδια) μορφή του προηγούμενου προγράμματος, όπου δε χρησιμοποιούμε καθόλου κάποια extra μεταβλητή (diff
) αλλά τυπώνουμε κατευθείαν την τιμή της λογικής έκφρασης
abs(x**2-2) < 1e-8
η οποία είναι True
αν και μόνο αν η ανισότητα που περιγράφει ισχύει (αφού υπολογιστούν τα αριστερά και δεξιά μέλη της), αλλιώς είναι False
.
import math
x = math.sqrt(2)
print abs(x**2-2) < 1e-8
Στο επόμενο κομμάτι κώδικα βλέπουμε τη μεταβλητή s
που είναι τύπου string (κομμάτι κειμένου) και όχι αριθμού. Οι τιμές μιας μεταβλητής τύπου string περιγράφονται στον κώδικά μας πάντα μέσα σε εισαγωγικά, απλά ('...'
) ή διπλά ("..."
). Όταν τυπώνουμε μια τέτοια μεταβλητή τα εισαγωγικά αυτά δεν τυπώνονται.
s = "Mihalis Kolountzakis"
print s
Βλέπουμε εδώ δύο μεταβλητές f
και l
τύπου string και πώς αυτές συγκολλούνται με την πράξη +
. Μεταξύ strings λοιπόν το σύμβολο +
σημαίνει κάτι το τελείως διαφορετικό απ' ό,τι μεταξύ αριθμών.
f = "Mihalis"
l = "Kolountzakis"
s = f + l
print s
Στο προηγούμενο κομμάτι κώδικα το όνομα και το επώνυμο συγκολλήθηκαν χωρίς μεταξύ τους κενό. Τώρα το προσδιορίζουμε ρητά και έχουμε μια συγκόλληση από τρία strings το μεσαίο από τα οποία έχει απλά ένα κενό χαρακτήρα μέσα του.
f = "Mihalis"
l = "Kolountzakis"
s = f + " " + l
print s
Εδώ βλέπουμε πώς σε μια εντολή print
μπορούμε και τυπώνουμε αντικείμενα διαφόρων τύπων. Στη συγκεκριμένη εντολή print
τυπώνουμε 4 αντικείμενα, το τελευταίο από τα οποία (μεταβλητή h
) είναι αριθμός και τα υπόλοιπα είναι strings. Η εντολή print
, όταν τυπώνει παραπάνω από ένα αντικείμενα, πάντα τυπώνει και ένα κενό χαρακτήρα ανάμεσα σε δύο διαδοχικά αντικείμενα.
f = "Mihalis"
l = "Kolountzakis"
h = 1.76
s = l+", "+f
print "The name is", s, "and the height is", h
Στο παρακάτω (παραλλαγή των προγραμμάτων στην αρχή αυτής της σελίδας) χρησιμοποιούμε και μια μεταβλητή condition
που είναι λογικού (boolean) τύπου, παίρνει δηλ. τις τιμές True
ή False
. Το ποια τιμή παίρνει στην εντολή ανάθεσης
condition = abs(x**2-2) < 1e-8
εξαρτάται από το αν ισχύει ή όχι η ανισότητα που είναι δεξιά του συμβόλου της ανάθεσης =
.
import math
x = math.sqrt(2)
condition = abs(x**2-2) < 1e-8
print condition
Εδώ βλέπουμε ότι δύο λογικές εκφράσεις, οι (x>1)
και (x<3)
μπορούν να συνδεθούν μεταξύ τους με τον τελεστή and
(και). Η τιμή μιας έκφρασης A and B
, όπου A
και B
είναι λογικές εκφράσεις είναι True
αν και μόνο αν και η A
και η B
είναι True
, αλλιώς είναι False
.
Στη μεταβλητή condition
εδώ υπολογίζουμε το αν ο αριθμός x
, όποιος και αν είναι αυτός, ανήκει στο διάστημα \((1,3)\), αφού το να ανήκει ένας αριθμός στο διάστημα αυτό ισοδυναμεί με το να είναι μεγαλύτερος του 1 και μικρότερος του 3.
x = 1.5
condition = (x>1) and (x<3)
print condition
Στο παρακάτω πρόγραμμα απαντάμε στο ερώτημα αν ο αριθμός x
είναι ή όχι στο σύνολο \[
E = (0,1) \cup (2, 3].
\] Στη μεταβλητή condition1
υπολογίζουμε το αν το x
ανήκει ή όχι στο διάστημα \((0,1)\) ενώ στη μεταβλητή condition2
υπολογίζουμε το αν το x
ανήκει ή όχι στο διάστημα \((2,3]\). Για να ανήκει το x
στο \(E\) αρκεί να ισχύει το condition1
ή το condition2
και έτσι παίρνει τιμή η μεταβλητή condition
.
x = 0.9
condition1 = (x >= 0) and (x <= 1)
condition2 = (x > 2) and (x <= 3)
condition = condition1 or condition2
print condition
Η μόνη διαφορά του επόμενου προγράμματος από το αμέσωως προηγούμενο είναι ότι αντί να τυπώσει True
όταν το x
ανήκει στο \(E\) τυπώνει False
σε αυτήν ακριβώς την περίπτωση. Αυτό επιτυγχάνεται απλά με τη χρήση του τελεστή not
που αντιστρέφει τη λογική τιμή αυτού που ακολουθεί.
x = 0.9
condition1 = (x >= 0) and (x <= 1)
condition2 = (x > 2) and (x <= 3)
condition = condition1 or condition2
condition = not condition
print condition
Στο παρακάτω πρόγραμμα εισάγουμε την εντολή 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
.
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."
Στο παρακάτω πρόγραμμα κάνουμε ουσιαστικά την ίδια δουλειά με πριν αλλά χωρίς να υπολογίσουμε τις λογικές μεταβλητές, αλλά γράφοντας κατευθείαν τη συνθήκη που μας λέει αν το 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, πράγμα που σημαίνει ότι κάτι δεν πάει καλά στη στοίχιση.
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"
Στο επόμενο έχουμε μια πιο οργανωμένη παραλλαγή του προηγούμενου προγράμματος, όπου ξαναεισάγουμε τις μεταβλητές condition1, condition2, condition3
, που, με τον τρόπο που υπολογίζονται, είναι True
αν και μόνο αν το x
ανήκει στο πρώτο, δεύτερο ή τρίτο διάστημα του \(E\) που ορίζεται στο προηγούμενο. Η συνθήκη του "μεγάλου" if
τώρα εκφράζεται μέσω των μεταβλητών αυτών και το ίδιο και οι συνθήκες των εσωτερικών if
.
Το πρόγραμμα αυτό είναι και πιο ευανάγνωστο από το προηγούμενο αλλά και λίγο πιο γρήγορο μια και δεν ξαναυπολογίζονται χωρίς λόγο λογικές ποσότητες που έχουν ήδη υπολογοστεί, όπως γινόταν στο προηγούμενο στα εσωτερικά if
.
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"
Εδώ αλλάζουμε πρόβλημα. Σε ένα μάθημα στο πανεπιστήμιο που έχει ένα τελικό \(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 ή μεγαλύτερος τότε ο φοιτητής περνάσει το μάθημα).
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"