Μετάβαση στο περιεχόμενο
Topos Uranos

Περίληψη

Τα μαθήματα που προηγήθηκαν σε αυτή την ενότητα δίδαξαν πώς γράφονται βρόχοι και πώς συλλογιζόμαστε γι’ αυτούς με μια αναλλοίωτη και μια φθίνουσα ποσότητα· αυτό το μάθημα εφαρμόζει τον ίδιο συλλογισμό σε τέσσερις κλασικούς αριθμητικούς αλγορίθμους, παλαιότερους από τους υπολογιστές και ακόμη σε χρήση. Ο έλεγχος πρώτου με δοκιμαστικές διαιρέσεις, που σταματά στο n\sqrt{n} για έναν λόγο που αποδεικνύεται, και του οποίου η συνθήκη τερματισμού υπερχειλίζει αν γραφτεί απρόσεκτα. Ο αλγόριθμος του Ευκλείδη για τον μέγιστο κοινό διαιρέτη, με την αναλλοίωτή του, τον τερματισμό του, την παγίδα του με τους αρνητικούς και τη σύγκρισή του με τη std::gcd. Η αλλαγή βάσης με διαδοχικές διαιρέσεις και ο κανόνας του Χόρνερ, που ανασυνθέτει έναν αριθμό από τα ψηφία του και υπολογίζει την τιμή ενός πολυωνύμου με το ίδιο σχήμα. Και η μέθοδος της διχοτόμησης, που προσεγγίζει μια ρίζα χωρίζοντας ένα διάστημα στα δύο, με εγγύηση το θεώρημα ενδιάμεσων τιμών και με αριθμό γύρων που υπολογίζεται εκ των προτέρων. Κάθε αλγόριθμος παρουσιάζεται ως μαθηματικό αντικείμενο: με απόδειξη ότι κάνει ό,τι λέει, απόδειξη ότι τερματίζει και μια πρώτη καταμέτρηση των βημάτων του.

Μαθησιακοί στόχοι

  1. Να υλοποιείτε τον έλεγχο πρώτου με διαιρέσεις έως το n\sqrt{n} και να αιτιολογείτε γιατί αρκεί να φτάσει κανείς στο n\sqrt{n}.
  2. Να υλοποιείτε τον αλγόριθμο του Ευκλείδη, να διατυπώνετε την αναλλοίωτή του (ο μέγιστος κοινός διαιρέτης δεν αλλάζει) και να τον συγκρίνετε με τη std::gcd.
  3. Να μετατρέπετε έναν αριθμό σε άλλη βάση με διαδοχικές διαιρέσεις και να υπολογίζετε την τιμή μιας θεσιακής αναπαράστασης, ή ενός πολυωνύμου, με τον κανόνα του Χόρνερ.
  4. Να προσεγγίζετε μια ρίζα με τη μέθοδο της διχοτόμησης έως μια ανοχή, και να υπολογίζετε πόσους γύρους απαιτεί.

Διαιρέτες και πρώτοι αριθμοί

Δοκιμαστικές διαιρέσεις

Ένας ακέραιος n≥2n \geq 2 είναι πρώτος αν οι μόνοι θετικοί διαιρέτες του είναι το 1 και ο nn· αν έχει κι άλλον, είναι σύνθετος· το 0 και το 1 δεν είναι ούτε το ένα ούτε το άλλο. Ο ορισμός υποδεικνύει αμέσως έναν αλγόριθμο: να δοκιμάσουμε κάθε υποψήφιο dd από το 2 έως το n−1n - 1 και να κηρύξουμε τον αριθμό σύνθετο μόλις κάποιος τον διαιρέσει. Είναι ο βρόχος του μικρότερου διαιρέτη από το μάθημα Βρόχοι, και είναι σωστός. Ωστόσο, για έναν πρώτο με δέκα ψηφία κάνει σχεδόν δέκα δισεκατομμύρια διαιρέσεις, και σχεδόν όλες είναι άχρηστες, για τον εξής λόγο.

ΠρότασηΑρκεί η δοκιμή έως το n\sqrt{n}

Αν ο n≥2n \geq 2 είναι σύνθετος, έχει διαιρέτη dd με 2≤d≤n2 \leq d \leq \sqrt{n}.

Απόδειξη

  1. n=a⋅b,2≤a≤bn = a \cdot b, \quad 2 \leq a \leq b

    Αν ο nn είναι σύνθετος, γράφεται ως γινόμενο δύο παραγόντων, κανένας από τους οποίους δεν ισούται με 1 ούτε με nn. Ονομάζουμε aa τον μικρότερο από τους δύο.

  2. a2≤a⋅b=na^2 \leq a \cdot b = n

    Πολλαπλασιάζοντας την a≤ba \leq b με τον θετικό aa παίρνουμε a2≤ab=na^2 \leq ab = n, οπότε a≤na \leq \sqrt{n}: ο μικρότερος παράγοντας κάθε παραγοντοποίησης βρίσκεται αριστερά του n\sqrt{n}.

  3. d≤n  ⟺  nd≥nd \leq \sqrt{n} \iff \frac{n}{d} \geq \sqrt{n}

    Οι διαιρέτες εμφανίζονται, συνεπώς, σε ζεύγη (d,n/d)(d, n/d) συμμετρικά γύρω από το n\sqrt{n}: για n=36n = 36, τα ζεύγη είναι (2,18)(2, 18), (3,12)(3, 12), (4,9)(4, 9) και (6,6)(6, 6). Αν κανένας υποψήφιος έως το n\sqrt{n} δεν διαιρεί τον nn, δεν τον διαιρεί ούτε κανένας πέρα από αυτό, γιατί το ταίρι του θα είχε εμφανιστεί νωρίτερα.

Από αυτό έπεται ότι το πλήθος των διαιρέσεων πέφτει από περίπου nn σε περίπου n\sqrt{n}: για nn κοντά στα δύο δισεκατομμύρια, από δύο δισεκατομμύρια σε σαράντα έξι χιλιάδες. Το πρόγραμμα χρησιμοποιεί το μοτίβο της σημαίας από το μάθημα Σχεδίαση σωστών βρόχων: η isPrime ξεκινά αληθής και ο βρόχος τη σβήνει αν βρει διαιρέτη.

Πρόγραμμα σε C++
#include <iostream>#include <print>​int main(){    int n{};    std::cin >> n;    bool isPrime{n >= 2};    for (int d{2}; isPrime && d <= n / d; ++d) {        if (n % d == 0) {            isPrime = false;        }    }    if (isPrime) {        std::println("Ο {} είναι πρώτος", n);    } else {        std::println("Ο {} δεν είναι πρώτος", n);    }}
Είσοδος του προγράμματος
2147483647
Έξοδος
Ο 2147483647 είναι πρώτος

Τρεις αποφάσεις αξίζουν σχόλιο. Η σημαία αρχικοποιείται με n >= 2, ώστε το 0, το 1 και οι αρνητικοί να κηρύσσονται μη πρώτοι χωρίς να μπουν στον βρόχο: η παράλειψη αυτής της περίπτωσης είναι το τυπικό σφάλμα, γιατί με n=1n = 1 ο βρόχος δεν κάνει κανέναν γύρο και το πρόγραμμα θα κήρυσσε πρώτο το 1. Η συνθήκη του for έχει δύο μέρη: τη σημαία, που σταματά τον βρόχο στον πρώτο διαιρέτη, και το όριο. Και το όριο γράφεται d <= n / d, που, για θετικούς ακεραίους, ισοδυναμεί με d2≤nd^2 \leq n (η ακέραια διαίρεση αποκόπτει, αλλά ο dd δεν υπερβαίνει το αποκομμένο πηλίκο ακριβώς όταν δεν υπερβαίνει το n/dn/d, επειδή ο dd είναι ακέραιος). Ο αριθμός της εισόδου, ο 231−12^{31} - 1, είναι ο μεγαλύτερος int και είναι πρώτος· το πρόγραμμα το αποφασίζει με 46 33946\,339 υπόλοιπα, εκείνα των υποψηφίων από το 2 έως το 46 34046\,340.

Η αναλλοίωτη του βρόχου είναι: κανένας ακέραιος από το 2 έως το d−1d - 1 δεν διαιρεί τον nn, όσο η σημαία μένει αληθής. Ισχύει κατά την είσοδο, όταν το διάστημα από το 2 έως το 1 είναι κενό· διατηρείται, γιατί αν ο dd δεν διαιρεί τον nn το διάστημα μεγαλώνει κατά έναν αριθμό που δεν διαιρεί· και κατά την έξοδο με τη σημαία αληθή, η αναλλοίωτη και η ψευδής συνθήκη, d2>nd^2 > n, λένε μαζί ότι κανένας υποψήφιος έως το n\sqrt{n} δεν διαιρεί τον nn, που είναι ό,τι απαιτεί η πρόταση. Η φθίνουσα ποσότητα είναι το πλήθος των υποψηφίων που μένουν να δοκιμαστούν, δηλαδή των ακεραίων μεταξύ dd και n\sqrt{n}: μειώνεται κατά ένα σε κάθε γύρο και δεν πέφτει κάτω από το μηδέν.

Η συνθήκη που υπερχειλίζει

Ο φυσικός τρόπος να γραφτεί το όριο είναι d * d <= n, και είναι λάθος. Με την ίδια είσοδο, ο βρόχος φτάνει στο d=46 341d = 46\,341, και τότε το γινόμενο 46 3412=2 147 488 28146\,341^2 = 2\,147\,488\,281 υπερβαίνει το μέγιστο του int.

Πρόγραμμα σε C++
#include <iostream>#include <print>​int main(){    int n{};    std::cin >> n;    bool isPrime{n >= 2};    for (int d{2}; isPrime && d * d <= n; ++d) {        if (n % d == 0) {            isPrime = false;        }    }    std::println("Πρώτος; {}", isPrime);}
Είσοδος του προγράμματος
2147483647
Έξοδος
main.cpp:9:33: runtime error: signed integer overflow: 46341 * 46341 cannot be represented in type 'int'

Το πρόγραμμα τερμάτισε με κωδικό 1.

Ο ανιχνευτής απροσδιόριστης συμπεριφοράς σταματά το πρόγραμμα στο γινόμενο. Χωρίς αυτόν, η υπερχείλιση με πρόσημο είναι απροσδιόριστη συμπεριφορά, και το πρότυπο δεν λέει τι συμβαίνει. Αυτό που παρατηρείται με τον GCC σε Linux, με και χωρίς βελτιστοποίηση, είναι διδακτικό: το γινόμενο κάνει αναδίπλωση και γίνεται αρνητικό ή μικρότερο από τον nn, και, αφού κανένας int δεν υπερβαίνει το μέγιστο, η συνθήκη d * d <= n δεν παύει πια να ισχύει· ο βρόχος συνεχίζει να δοκιμάζει υποψηφίους για περίπου δύο δισεκατομμύρια γύρους, ώσπου να φτάσει στο d=nd = n, που διαιρεί τον nn, και το πρόγραμμα κηρύσσει σύνθετο έναν πρώτο αριθμό. Ένα σφάλμα υπερχείλισης δεν εκδηλώνεται ως υπερχείλιση, αλλά ως λανθασμένη απάντηση. Η μορφή d <= n / d δεν μπορεί να υπερχειλίσει, γιατί διαιρεί αντί να πολλαπλασιάζει· το μάθημα Bit και μαθηματική βιβλιοθήκη πρόσφερε άλλη διέξοδο, τον υπολογισμό του ορίου μία φορά με τη std::sqrt, αλλά τότε η ακρίβεια του ορίου εξαρτάται από τη στρογγυλοποίηση της κινητής υποδιαστολής, ενώ η ακέραια διαίρεση δεν έχει αυτό το πρόβλημα.

Το μάθημα «Πρώτοι αριθμοί και το θεμελιώδες θεώρημα της αριθμητικής», του κύκλου μαθημάτων Άλγεβρα και γεωμετρία I, αποδεικνύει ότι κάθε ακέραιος μεγαλύτερος από 1 είναι γινόμενο πρώτων με έναν μόνο τρόπο· οι δοκιμαστικές διαιρέσεις είναι ο πιο άμεσος τρόπος να βρεθεί αυτό το γινόμενο, διαιρώντας τον nn με κάθε διαιρέτη που βρίσκεται και συνεχίζοντας με το πηλίκο.