Η Julia είναι μια γλώσσα γενικού σκοπού, που μπορεί να χρησιμοποιηθεί για τις περισσότερες προγραμματιστικές εργασίες. Στην πράξη, ωστόσο, οι κύριες χρήσεις της τείνουν να είναι στη μηχανική και τις επιστήμες. Γρήγοροι, ευέλικτοι, εκλεπτυσμένοι αριθμητικοί υπολογισμοί βρίσκονται στον πυρήνα του σχεδιασμού της.
Ένας ακέραιος είναι ένας "στρογγυλός" αριθμός χωρίς υποδιαστολή.
Στην έννοια Basics είδαμε ότι μια ακέραια τιμή μπορεί να ανατεθεί σε μια μεταβλητή χωρίς να προσδιορίσουμε τύπο.
Για λόγους αναγνωσιμότητας, μπορούν να χρησιμοποιηθούν κάτω παύλες ως διαχωριστικό ψηφίων. Αγνοούνται από τον μεταγλωττιστή.
julia> x = 3
3
julia> typeof(x)
Int64
julia> large_number = 1_234_567_890
1234567890
Εσωτερικά, ο μεταγλωττιστής θα χρησιμοποιήσει όποιον προσημασμένο ακέραιο τύπο είναι καταλληλότερος για τη CPU σου. Στους σύγχρονους υπολογιστές αυτός θα είναι συνήθως ο Int64, που αρκεί απόλυτα για τις περισσότερες εργασίες.
Οι τύποι θα συζητηθούν πληρέστερα σε μια μελλοντική έννοια. Για την ώρα, οι αριθμητικοί τύποι ελπίζουμε να είναι αρκετά κατανοητοί ώστε το παρόν κείμενο να βγάζει νόημα.
Όπως θα δούμε σε μια επόμενη έννοια, η Julia υποστηρίζει εγγενώς πολύ μεγάλους, πολυδιάστατους πίνακες. Αυτοί μπορούν να πιέσουν τόσο την ποσότητα της διαθέσιμης μνήμης όσο και το εύρος ζώνης για τη μεταφορά δεδομένων.
Για να δώσουμε λεπτότερο έλεγχο στον προγραμματιστή, είναι και δυνατό και αρκετά συνηθισμένο να προσδιορίζουμε τον επιθυμητό προσημασμένο ή μη προσημασμένο ακέραιο τύπο. Οι πολλοί μαθητές της Julia που έχουν χρησιμοποιήσει στο παρελθόν Python ίσως θελήσουν να το συγκρίνουν με το NumPy, αντί με την απλή Python.
julia> y = Int8(42)
42
julia> typeof(y)
Int8
julia> z = UInt32(1024)
0x00000400
Σημείωσε ότι η Julia από προεπιλογή εμφανίζει τους μη προσημασμένους ακεραίους σε δεκαεξαδική μορφή, επειδή οι μη προσημασμένοι τύποι χρησιμοποιούνται συχνά για πράξεις σε bits χαμηλού επιπέδου.
Οι ακέραιοι μπορούν επίσης να εισαχθούν ως δυαδικοί, οκταδικοί ή δεκαεξαδικοί, με προθέματα 0b, 0o και 0x αντίστοιχα.
julia> a = 0x10
0x10
julia> Int(a)
16
julia> b = 0b1010 # displays as hexadecimal
0x0a
BigInt
Κάθε ακέραιος τύπος έχει μια μέγιστη και μια ελάχιστη τιμή που μπορεί να αποθηκεύσει:
julia> typemax(Int8)
127
julia> typemin(Int8)
-128
Η έξοδος από αυτό το έγκυρο εύρος θα προκαλέσει υπερχείλιση ακεραίου, με αποτελέσματα που φαίνονται πολύ παράξενα.
julia> c = Int8(126)
126
julia> c * c
4
Μάλλον μπορούμε να συμφωνήσουμε ότι το 126 * 126 δεν πρέπει να ισούται με 4!
Μια λεπτομερής εξήγηση απαιτεί γνώση του πώς οι καταχωρητές της CPU αποθηκεύουν ακεραίους, κάτι που ξεφεύγει από τα όρια αυτής της έννοιας.
Το πρόβλημα μπορεί να περιοριστεί χρησιμοποιώντας έναν "πλατύτερο" τύπο, μέχρι τους Int128 ή UInt128 (η σημασία του "πλατύτερος" συζητείται παρακάτω). Ωστόσο, όλοι έχουν σκληρά όρια πριν πέσουν σε προβλήματα υπερχείλισης.
Μια λύση είναι να χρησιμοποιήσεις τον τύπο BigInt, ο οποίος περιορίζεται μόνο από τη μνήμη του υπολογιστή σου. Είναι η υλοποίηση της Julia για αριθμητική αυθαίρετης ακρίβειας.
julia> 2 ^ 70
0
julia> big = BigInt(2) ^ 70
1180591620717411303424
julia> UInt128(big)
0x00000000000000400000000000000000
Επειδή το 2 υψωμένο στη δύναμη 70 δεν χωράει σε έναν Int64, ο πρώτος υπολογισμός στο παραπάνω παράδειγμα αποτυγχάνει: πρόσεξε όμως ότι δεν υπάρχει μήνυμα σφάλματος. Προσοχή, προγραμματιστή!
Η μετατροπή του 2 σε BigInt δίνει τη σωστή απάντηση, και η εμφάνισή του σε δεκαεξαδική μορφή κάνει πιο σαφές ότι πρόκειται για δύναμη του 2.
Δεν θα αποτελέσει έκπληξη ότι οι αριθμοί κινητής υποδιαστολής έχουν προαιρετικά υποδιαστολή και ένα κλασματικό μέρος μετά από αυτήν.
julia> f = 3.45
3.45
julia> typeof(f)
Float64
Φυσικά, υποστηρίζεται και η επιστημονική σημειογραφία.
julia> avogadro = 6.02e23
6.02e23
Όπως και με τους ακεραίους, ο προεπιλεγμένος τύπος αρκεί για τις περισσότερες χρήσεις, αλλά υπάρχουν και άλλοι προσημασμένοι τύποι. Δεν υπάρχουν τύποι μη προσημασμένης κινητής υποδιαστολής.
Ως συντομία, τιμές Float32 μπορούν να δημιουργηθούν χρησιμοποιώντας το επίθεμα f0.
julia> f32 = 4.56f0
4.56f0
julia> typeof(f32)
Float32
Άλλα μεγέθη κινητής υποδιαστολής χρειάζονται ρητή μετατροπή, όπως και με τους ακεραίους.
Οι μέγιστες και οι ελάχιστες τιμές μπορεί να σε εκπλήξουν:
julia> typemax(Float64)
Inf
julia> typemin(Float64)
-Inf
Το άπειρο είναι έγκυρη τιμή!
Ωστόσο, το χρήσιμο εύρος των αριθμών κινητής υποδιαστολής είναι περιορισμένο, με οτιδήποτε πολύ μεγάλο να παίρνει απλώς την τιμή Inf, και οτιδήποτε πολύ μικρό να στρογγυλοποιείται στο 0.0.
Μπορούμε να χρησιμοποιήσουμε ένα διαφορετικό ζεύγος συναρτήσεων για να δούμε αυτά τα όρια: περίπου ± 10 ^ 308.
julia> floatmax(Float64)
1.7976931348623157e308
julia> floatmin(Float64)
2.2250738585072014e-308
Επίσης, η ακρίβεια είναι περιορισμένη, με έναν μεγάλο αλλά πεπερασμένο αριθμό σημαντικών ψηφίων να αναπαρίσταται (περίπου 15 για τον Float64).
Όπως συζητήθηκε στην έννοια Basics, οι αριθμητικοί τελεστές λειτουργούν ως επί το πλείστον όπως η τυπική αριθμητική, όπως τη διδάσκουν στα παιδιά. Σημείωσε ότι η ύψωση σε δύναμη χρησιμοποιεί το ^, όχι το ** (και τα δύο είναι συνηθισμένα σε άλλες γλώσσες).
2 + 3 # 5 (addition)
2 - 3 # -1 (subtraction)
2 * 3 # 6 (multiplication)
8 / 2 # 4.0 (division)
8 % 3 # 2 (remainder)
2 ^ 3 # 8 (exponentiation)
Ωστόσο, μερικές λεπτομέρειες ειδικές για τη Julia αξίζει να συζητηθούν.
julia> x = 4.2
4.2
julia> 2 * x
8.4
julia> 2x
8.4
julia> 2.4x
10.08
Αυτό μπορεί να εκπλήσσει.
Είναι πάντα δυνατό να χρησιμοποιήσεις το * ως μεσεμβολικό τελεστή, όπως στις περισσότερες άλλες γλώσσες υπολογιστών.
Ωστόσο, η Julia σχεδιάστηκε από ανθρώπους που πιστεύουν ότι ο κώδικας πρέπει να μοιάζει όσο το δυνατόν περισσότερο με μαθηματικές εξισώσεις.
Επειδή τα ονόματα μεταβλητών πρέπει να ξεκινούν με γράμμα, το να βάλεις έναν αριθμό (ακέραιο ή κινητής υποδιαστολής) μπροστά από το όνομα αντιμετωπίζεται ως υπόρρητος πολλαπλασιασμός.
Για παράδειγμα, αν θέλουμε το εμβαδόν της επιφάνειας μιας σφαίρας, αντί για 4 * pi * r * r μπορούμε να κάνουμε αυτό:
julia> surface(r) = 4π * r^2
surface (generic function with 1 method)
julia> surface(3)
113.09733552923255
Αν και το π είναι ενσωματωμένη σταθερά, είναι επίσης ένα (ελληνικό) γράμμα. Ο αναλυτής λοιπόν χρειάζεται ακόμα ένα ρητό * για να ξεχωρίσει το π από το r.
Η χρήση του / ως μεσεμβολικού τελεστή δίνει πάντα αποτέλεσμα κινητής υποδιαστολής, ακόμα και για ακέραιες εισόδους.
Για ακέραια διαίρεση, υπάρχουν περισσότερες επιλογές:
julia> 10 / 3
3.3333333333333335
julia> div(10, 3)
3
julia> 10 ÷ 3
3
julia> 10 // 3
10//3
Η συνάρτηση div() προορίζεται για ακέραια διαίρεση, με το αποτέλεσμα να περικόπτεται προς το μηδέν: προς τα κάτω για θετικούς αριθμούς, προς τα πάνω για αρνητικούς.
Ως συνώνυμο, μπορούμε να χρησιμοποιήσουμε τον μεσεμβολικό τελεστή ÷, πάλι με στόχο να φαίνεται πιο μαθηματικός. Αν χρησιμοποιείς έναν editor που γνωρίζει τη Julia, πληκτρολόγησέ το ως \div και μετά πάτησε το πλήκτρο <Tab>.
Ο τελεστής // θα χρειαστεί τη δική του έννοια, αργότερα στο πρόγραμμα σπουδών.
Για την ώρα, μπορούμε απλώς να πούμε ότι το αποτέλεσμα του // είναι ένας "ρητός" αριθμός, ο επίσημος όρος για αυτό που οι περισσότεροι αποκαλούν κλάσμα.
Οι κοινοί παράγοντες αφαιρούνται από τον αριθμητή και τον παρονομαστή, ώστε να προκύψει ένας λόγος δύο ακεραίων σε αυτό που ονομάζεται "ανάγωγη μορφή".
julia> rationalnum = 22 // 6
11//3
julia> typeof(rationalnum)
Rational{Int64}
Έχουμε ρητούς αριθμούς. Τι γίνεται με τους "άρρητους" αριθμούς;
julia> π
π = 3.1415926535897...
julia> typeof(π)
Irrational{:π}
Ένας άρρητος αριθμός είναι αυτός που δεν μπορεί να αναχθεί σε λόγο ακεραίων. Συνηθισμένα παραδείγματα περιλαμβάνουν το π, το e (ο αριθμός του Euler) και πολλές ρίζες όπως το √2 (η τετραγωνική ρίζα του 2).
Η Julia προσπαθεί να κάνει μαθηματικά σωστά.
Αυτό μπορεί συχνά να συμβεί αυτόματα:
julia> x = 2 + 3.5
5.5
julia> typeof(x)
Float64
Προσθέσαμε έναν Int64 σε έναν Float64 και πήραμε αποτέλεσμα Float64.
Στην πραγματικότητα, ο ακέραιος μετατράπηκε σιωπηρά σε Float64 πριν γίνει η πρόσθεση.
Η Julia έχει μια έννοια του "πλάτους" των αριθμητικών τύπων.
Int64 είναι πλατύτερος από τον Int16.Αν μέσα σε μια έκφραση χρησιμοποιείται ένα μείγμα τύπων, ο καθένας "προάγεται" όπως χρειάζεται στον πλατύτερο τύπο που χρησιμοποιείται.
Για να επιβάλουμε τη μετατροπή, μπορούμε να μετατρέψουμε έναν ακέραιο σε συγκεκριμένο τύπο, όπως στο Float64(5).
Εναλλακτικά, χρησιμοποίησε απλώς το float(5) και άφησε τον μεταγλωττιστή να διαλέξει κατάλληλο τύπο.
Οι μετατροπές από κινητής υποδιαστολής σε ακέραιο είναι αναπόφευκτα πιο περίπλοκες. Τι θέλεις να κάνεις με ό,τι βρίσκεται μετά την υποδιαστολή;
round() μετατρέπει στον πλησιέστερο ακέραιο, με τις ισοπαλίες, όπως το 4.5, να στρογγυλοποιούνται στον πλησιέστερο άρτιο ακέραιο.floor() στρογγυλοποιεί προς τα κάτω, η ceil() προς τα πάνω, η trunc() προς το μηδέν.Int32(), θα αποτύχει με ένα InexactError.Ωστόσο, από προεπιλογή αυτές οι συναρτήσεις δεν επιστρέφουν τον ακέραιο τύπο που ίσως ήθελες. Ο επιθυμητός τύπος εξόδου μπορεί να προσδιοριστεί.
julia> round(4.5)
4.0
julia> round(Int64, 4.5)
4
julia> round(Int, 4.5) # => default integer type
4
julia> ceil(Int16, 4.3)
5
Η στρογγυλοποίηση σε συγκεκριμένο αριθμό ψηφίων μετά την υποδιαστολή είναι επίσης δυνατή με τη λέξη-κλειδί digits.
julia> round(π, digits=10)
3.1415926536
Δες το εγχειρίδιο για περισσότερες λεπτομέρειες.
Σίγουρα αυτό απλώς πετάει ένα σφάλμα; Στην πραγματικότητα, η κατάσταση δεν είναι τόσο απλή.
Η ακέραια διαίρεση με ÷ ή // θα οδηγήσει σε σφάλμα, όπως ίσως περιμένεις.
Η διαίρεση κινητής υποδιαστολής με / ακολουθεί αυτό που θα μπορούσε να θεωρηθεί προσέγγιση μηχανικού, παρά τυπική προσέγγιση επιστήμης υπολογιστών:
julia> 2 / 0
Inf
julia> 0 / 0
NaN
Όπως συζητήθηκε σε προηγούμενη ενότητα, το άπειρο είναι έγκυρος αριθμός κινητής υποδιαστολής στη Julia, που αναπαρίσταται με Inf.
Όταν ο αριθμητής είναι επίσης μηδέν, το αποτέλεσμα είναι μαθηματικά απροσδιόριστο. Η Julia τότε το αντιμετωπίζει ως "μη αριθμό", που αναπαρίσταται με NaN.
Αν αυτό φαίνεται παράξενο, σκέψου το στο πλαίσιο της εργασίας με μεγάλους πίνακες δεδομένων του πραγματικού κόσμου (άρα συχνά αρκετά ακατάστατων). Για να προχωρήσεις, το καλύτερο είναι απλώς να επισημάνεις τις προβληματικές τιμές και να συνεχίσεις.
Ο ατέλειωτος χειροκίνητος έλεγχος τιμών θα ήταν κουραστικός στον προγραμματισμό και σίγουρα θα έβλαπτε την απόδοση κατά την εκτέλεση.
Το να σταματάς με μήνυμα σφάλματος σε κάθε μικρό πρόβλημα θα έκανε το πρόγραμμά σου εξαιρετικά αντιπαθές στους χρήστες!
Όπως περιγράφεται στην έννοια Conditionals, ο έλεγχος ισότητας γίνεται συνήθως με τον τελεστή ==.
Αυτό λειτουργεί καλά για ακεραίους, χαρακτήρες, συμβολοσειρές κ.λπ. Ωστόσο, οι τιμές κινητής υποδιαστολής έχουν περιορισμένη ακρίβεια, και διαφορετικοί τρόποι υπολογισμού του ίδιου αποτελέσματος μπορούν να οδηγήσουν σε ελαφρώς διαφορετικές τιμές. Για τον Float64, αυτό θα είναι συνήθως γύρω στο 15ο σημαντικό ψηφίο: μια μικρή διαφορά, αλλά όχι "ισότητα".
Παραδοσιακά, η συμβουλή προς τους προγραμματιστές είναι να μην χρησιμοποιούν ποτέ το == με τιμές κινητής υποδιαστολής: τα αποτελέσματα είναι απρόβλεπτα.
Μια ευρέως χρησιμοποιούμενη εναλλακτική είναι να ελέγχεις την απόλυτη τιμή της διαφοράς ως προς κάποια επιτρεπτή ανοχή (που συχνά ονομάζεται έψιλον ή ϵ). Έτσι, αντί για a == b, χρησιμοποίησε abs(a - b) < epsilon.
Η Julia παρέχει μια καθαρότερη εναλλακτική με τη συνάρτηση isapprox().
Για απόλυτη ανοχή, η σύνταξη είναι isapprox(a, b, atol=epsilon)atolatol` απαιτείται σε αυτή την περίπτωση.
Η σχετική ανοχή είναι συχνά πιο χρήσιμη. Αυτή είναι η προεπιλογή, οπότε το isapprox(a, b) θα προσπαθήσει να διαλέξει κάποια λογική τιμή για το rtol (οι κανόνες είναι αρκετά περίπλοκοι).
Μια σχετική ανοχή μπορεί επίσης να προσδιοριστεί, ως κλάσμα των τιμών που συγκρίνονται. Έτσι, το isapprox(a, b, rtol=0.01)` ελέγχει ότι οι τιμές βρίσκονται εντός 1% η μία από την άλλη.
Όπως συνηθίζεται στη Julia, υπάρχει ένας μαθηματικός τελεστής που είναι συνώνυμος με την προεπιλεγμένη περίπτωση: a ≈ b (με τον τελεστή να εισάγεται ως \approx και μετά <tab>).
Εκτός από τους ρητούς αριθμούς, επόμενα μέρη του προγράμματος σπουδών θα συζητήσουν:
2.3 + 4.5im.