Εξωπραγματικά χαρακτηριστικά των πραγματικών τύπων ή να είστε προσεκτικοί με το REAL

Μετά τη δημοσίευση Άρθρο σχετικά με τα χαρακτηριστικά της πληκτρολόγησης στην PostgreSQL, το πρώτο σχόλιο αφορούσε τις δυσκολίες εργασίας με πραγματικούς αριθμούς. Αποφάσισα να ρίξω μια γρήγορη ματιά στον κώδικα των ερωτημάτων SQL που έχω στη διάθεσή μου για να δω πόσο συχνά χρησιμοποιούν τον τύπο REAL. Αποδεικνύεται ότι χρησιμοποιείται αρκετά συχνά και οι προγραμματιστές δεν καταλαβαίνουν πάντα τους κινδύνους πίσω από αυτό. Και αυτό παρά το γεγονός ότι υπάρχουν αρκετά καλά άρθρα στο Διαδίκτυο και στο Habré σχετικά με τις δυνατότητες αποθήκευσης πραγματικών αριθμών στη μνήμη του υπολογιστή και σχετικά με την εργασία με αυτούς. Επομένως, σε αυτό το άρθρο θα προσπαθήσω να εφαρμόσω τέτοιες δυνατότητες στο PostgreSQL και θα προσπαθήσω να ρίξω μια γρήγορη ματιά στα προβλήματα που σχετίζονται με αυτά, ώστε να είναι ευκολότερο για τους προγραμματιστές ερωτημάτων SQL να τα αποφύγουν.

Η τεκμηρίωση της PostgreSQL αναφέρει συνοπτικά: «Η διαχείριση τέτοιων σφαλμάτων και η διάδοσή τους κατά τον υπολογισμό είναι αντικείμενο ενός ολόκληρου κλάδου των μαθηματικών και της επιστήμης των υπολογιστών και δεν καλύπτεται εδώ» (ενώ σοφά παραπέμπει τον αναγνώστη στο πρότυπο IEEE 754). Τι είδους σφάλματα εννοούνται εδώ; Ας τα συζητήσουμε με τη σειρά και σύντομα θα φανεί γιατί ξαναπήρα το στυλό.

Ας πάρουμε για παράδειγμα ένα απλό αίτημα:

********* ЗАПРОС *********
SELECT 0.1::REAL;
**************************
float4
--------
    0.1
(1 строка)

Ως αποτέλεσμα, δεν θα δούμε τίποτα ιδιαίτερο - θα λάβουμε το αναμενόμενο 0.1. Αλλά τώρα ας το συγκρίνουμε με το 0.1:

********* ЗАПРОС *********
SELECT 0.1::REAL = 0.1;
**************************
?column?
----------
f
(1 строка)

Όχι ίσα! Τι θαύματα! Αλλά περαιτέρω, περισσότερα. Κάποιος θα πει, ξέρω ότι το REAL συμπεριφέρεται άσχημα με τα κλάσματα, οπότε θα εισάγω ακέραιους αριθμούς εκεί και σίγουρα όλα θα πάνε καλά με αυτούς. Εντάξει, ας ρίξουμε τον αριθμό 123 στο REAL:

********* ЗАПРОС *********
SELECT 123456789::REAL::INT;
**************************
   int4   
-----------
123456792
(1 строка)

Και αποδείχτηκαν άλλα 3! Αυτό ήταν όλο, η βάση δεδομένων έχει επιτέλους ξεχάσει πώς να μετρήσει! Ή μήπως κάτι παρεξηγούμε; Ας το καταλάβουμε.

Αρχικά, ας θυμηθούμε το υλικό. Όπως γνωρίζετε, οποιοσδήποτε δεκαδικός αριθμός μπορεί να επεκταθεί σε δυνάμεις του δέκα. Άρα, ο αριθμός 123.456 θα είναι ίσος με 1*102 + 2*101 + 3*100 + 4*10-1 + 5*10-2 + ​​6*10-3. Αλλά ο υπολογιστής λειτουργεί με αριθμούς σε δυαδική μορφή, επομένως πρέπει να αναπαρασταθούν με τη μορφή επέκτασης σε δυνάμεις δύο. Επομένως, ο αριθμός 5.625 στο δυαδικό σύστημα αντιπροσωπεύεται ως 101.101 και θα είναι ίσος με 1*22 + 0*21 + 1*20 + 1*2-1 + 0*2-2 + 1*2-3. Και αν οι θετικές δυνάμεις δύο δίνουν πάντα ακέραιους δεκαδικούς αριθμούς (1, 2, 4, 8, 16, κ.λπ.), τότε με τους αρνητικούς όλα είναι πιο περίπλοκα (0.5, 0.25, 0.125, 0,0625, κ.λπ.). Το πρόβλημα είναι ότι Δεν μπορεί να αναπαρασταθεί κάθε δεκαδικό ως πεπερασμένο δυαδικό κλάσμα. Έτσι, το περιβόητο 0.1 με τη μορφή δυαδικού κλάσματος εμφανίζεται ως η περιοδική τιμή 0.0(0011). Κατά συνέπεια, η τελική τιμή αυτού του αριθμού στη μνήμη του υπολογιστή θα ποικίλλει ανάλογα με το βάθος bit.

Τώρα είναι η ώρα να θυμηθούμε πώς αποθηκεύονται οι πραγματικοί αριθμοί στη μνήμη του υπολογιστή. Σε γενικές γραμμές, ένας πραγματικός αριθμός αποτελείται από τρία κύρια μέρη - πρόσημο, μάντισσα και εκθέτη. Το πρόσημο μπορεί να είναι είτε συν είτε μείον, οπότε εκχωρείται ένα bit για αυτό. Αλλά ο αριθμός των δυαδικών ψηφίων της μάντισσας και του εκθέτη καθορίζεται από τον πραγματικό τύπο. Έτσι, για τον τύπο REAL, το μήκος της μάντισσας είναι 23 bit (ένα bit ίσο με 1 προστίθεται σιωπηρά στην αρχή της μάντισσας και το αποτέλεσμα είναι 24) και ο εκθέτης είναι 8 bit. Το σύνολο είναι 32 bit, ή 4 byte. Και για τον τύπο ΔΙΠΛΗΣ ΑΚΡΙΒΕΙΑΣ, το μήκος του mantissa θα είναι 52 bit και ο εκθέτης θα είναι 11 bit, για ένα σύνολο 64 bit, ή 8 byte. Η PostgreSQL δεν υποστηρίζει υψηλότερη ακρίβεια για αριθμούς κινητής υποδιαστολής.

Ας συσκευάσουμε τον δεκαδικό μας αριθμό 0.1 σε τύπους ΠΡΑΓΜΑΤΙΚΟ και ΔΙΠΛΗΣ ΑΚΡΙΒΕΙΑΣ. Δεδομένου ότι το πρόσημο και η τιμή του εκθέτη είναι τα ίδια, θα επικεντρωθούμε στη μάντισσα (παραλείπω σκόπιμα τα μη προφανή χαρακτηριστικά της αποθήκευσης των τιμών του εκθέτη και των μηδενικών πραγματικών τιμών, καθώς περιπλέκουν την κατανόηση και αποσπούν την προσοχή από την ουσία του προβλήματος, εάν ενδιαφέρεστε, δείτε το πρότυπο IEEE 754). Τι θα πάρουμε; Στην επάνω γραμμή θα δώσω το "mantissa" για τον τύπο REAL (λαμβάνοντας υπόψη τη στρογγυλοποίηση του τελευταίου bit κατά 1 στον πλησιέστερο αντιπροσωπεύσιμο αριθμό, διαφορετικά θα είναι 0.099999...), και στην κάτω γραμμή - για ο τύπος ΔΙΠΛΗΣ ΑΚΡΙΒΕΙΑΣ:

0.000110011001100110011001101
0.00011001100110011001100110011001100110011001100110011001

Προφανώς πρόκειται για δύο εντελώς διαφορετικούς αριθμούς! Επομένως, κατά τη σύγκριση, ο πρώτος αριθμός θα συμπληρώνεται με μηδενικά και, επομένως, θα είναι μεγαλύτερος από τον δεύτερο (λαμβάνοντας υπόψη τη στρογγυλοποίηση - αυτόν που σημειώνεται με έντονους χαρακτήρες). Αυτό εξηγεί την ασάφεια από τα παραδείγματά μας. Στο δεύτερο παράδειγμα, ο ρητά καθορισμένος αριθμός 0.1 μεταφέρεται στον τύπο ΔΙΠΛΗΣ ΑΚΡΙΒΕΙΑΣ και, στη συνέχεια, συγκρίνεται με έναν αριθμό του τύπου REAL. Και τα δύο έχουν μειωθεί στον ίδιο τύπο, και έχουμε ακριβώς αυτό που βλέπουμε παραπάνω. Ας τροποποιήσουμε το ερώτημα έτσι ώστε όλα να μπουν στη θέση τους:

********* ЗАПРОС *********
SELECT 0.1::REAL > 0.1::DOUBLE PRECISION;
**************************
?column?
----------
t
(1 строка)

Και πράγματι, πραγματοποιώντας διπλή αναγωγή του αριθμού 0.1 σε ΠΡΑΓΜΑΤΙΚΟ και ΔΙΠΛΗ ΑΚΡΙΒΕΙΑ, παίρνουμε την απάντηση στο αίνιγμα:

********* ЗАПРОС *********
SELECT 0.1::REAL::DOUBLE PRECISION;
**************************

      float8       
-------------------
0.100000001490116
(1 строка)

Αυτό εξηγεί επίσης το τρίτο παράδειγμα παραπάνω. Ο αριθμός 123 είναι απλός είναι αδύνατο να χωρέσει η μάντισσα σε 24 bit (23 ρητά + 1 υπονοούμενο). Ο μέγιστος ακέραιος που μπορεί να χωρέσει σε 24 bit είναι 224-1 = 16. Επομένως, ο αριθμός μας 777 στρογγυλοποιείται στο πλησιέστερο αναπαραστάσιμο 215. Αλλάζοντας τον τύπο σε ΔΙΠΛΗ ΑΚΡΙΒΕΙΑ, δεν βλέπουμε πλέον αυτό το σενάριο:

********* ЗАПРОС *********
SELECT 123456789::DOUBLE PRECISION::INT;
**************************
   int4   
-----------
123456789
(1 строка)

Αυτό είναι όλο. Αποδεικνύεται ότι δεν υπάρχουν θαύματα. Αλλά όλα όσα περιγράφονται είναι ένας καλός λόγος για να σκεφτείτε πόσο πραγματικά χρειάζεστε τον ΠΡΑΓΜΑΤΙΚΟ τύπο. Ίσως το μεγαλύτερο πλεονέκτημα της χρήσης του είναι η ταχύτητα των υπολογισμών με γνωστή απώλεια ακρίβειας. Θα ήταν όμως αυτό ένα καθολικό σενάριο που θα δικαιολογούσε τόσο συχνή χρήση αυτού του τύπου; Μη νομίζεις.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο