Ένας ακέραιος είναι μια αφαίρεση που αναπαριστά ακέραιους αριθμούς, όπως 4, -2, 0 ή 64532.
Για να αναπαραστήσεις έναν ακέραιο ως ακολουθία από byte, χρησιμοποιείται η δυαδική σημειογραφία. Σε αυτή τη σημειογραφία, κάθε bit της ακολουθίας αναπαριστά μια ξεχωριστή δύναμη του δύο, με την τιμή να αυξάνεται όσο αυξάνεται η θέση του bit από τα δεξιά προς τα αριστερά.
Αν ο αριθμός μπορεί να είναι μόνο μη αρνητικός, ονομάζεται μη προσημασμένος.
Οι μη προσημασμένοι αριθμοί αναπαρίστανται απευθείας ως το άθροισμα των δυνάμεων του δύο που αντιστοιχούν σε όλα τα τεθειμένα bit της ακολουθίας τους.
Το εύρος των αναπαραστάσιμων μη αρνητικών ακεραίων σε έναν καταχωρητή πηγαίνει από το 0 (κανένα bit δεν έχει τεθεί) μέχρι το 2⁶⁴ - 1 (άθροισμα όλων των 64 bit που έχουν τεθεί).
Η διεύρυνση ενός μη προσημασμένου αριθμού σε μεγαλύτερο μέγεθος γίνεται γεμίζοντας όλα τα ανώτερα bit με 0, ώστε κανένα νέο bit να μη συμβάλλει στην τιμή.
Αυτό ονομάζεται επέκταση με μηδενικά.
Η εντολή movzx (το z από το zero) επεκτείνει με μηδενικά έναν τελεστέο πηγής 8 ή 16 bit σε μεγαλύτερο τελεστέο προορισμού.
Ένας τελεστέος πηγής 32 bit επεκτείνεται πάντα με μηδενικά σε όλα τα 64 bit του τελεστέου προορισμού με ένα απλό mov.
Αν ένας ακέραιος μπορεί να πάρει θετικές ή αρνητικές τιμές, ονομάζεται προσημασμένος.
Για να αναπαραστήσει αρνητικούς αριθμούς, το x86-64 χρησιμοποιεί την αναπαράσταση συμπληρώματος ως προς δύο.
Στο συμπλήρωμα ως προς δύο, οι προσημασμένοι αριθμοί αναπαρίστανται επίσης ως το άθροισμα των δυνάμεων του δύο που αντιστοιχούν στα τεθειμένα bit. Ωστόσο, αν το ανώτατο bit έχει τεθεί, αφαιρείται αντί να προστεθεί στα υπόλοιπα.
Επειδή αυτό το bit αντιστοιχεί σε μεγαλύτερη τιμή από το άθροισμα όλων των άλλων, στην πράξη αυτό σημαίνει ότι ένας αριθμός με αυτό το bit τεθειμένο είναι πάντα αρνητικός. Αυτό το ειδικό bit ονομάζεται bit προσήμου.
Η διεύρυνση ενός προσημασμένου αριθμού σε μεγαλύτερο μέγεθος σημαίνει ότι γεμίζεις κάθε νέο ανώτερο bit με ένα αντίγραφο του bit προσήμου, ώστε να διατηρηθεί η τιμή. Αυτό ονομάζεται επέκταση προσήμου.
Η εντολή movsx (το s από το sign) επεκτείνει με πρόσημο έναν τελεστέο πηγής 8 ή 16 bit σε μεγαλύτερο τελεστέο προορισμού.
Μια παραλλαγή της movsx που ονομάζεται movsxd κάνει το ίδιο από έναν τελεστέο πηγής 32 bit σε έναν τελεστέο προορισμού 64 bit.
Η εντολή neg μπορεί να χρησιμοποιηθεί για να αλλάξεις το πρόσημο ενός αριθμού.
Στη γλώσσα assembly, δεν υπάρχει τρόπος να καταλάβεις αν μια ακολουθία από byte αναπαριστά έναν προσημασμένο ή έναν μη προσημασμένο αριθμό. Είναι ευθύνη του προγραμματιστή να δώσει νόημα σε αυτά τα byte.
Η χρήση σχολίων μπορεί να βοηθήσει πολύ σε αυτό το έργο.
Σε μια προηγούμενη έννοια αναφέρθηκε ότι ένας σταθερός αριθμός, όπως 4 ή -15, μπορεί να χρησιμοποιηθεί ως τελεστέος πηγής σε πολλές εντολές.
Αυτοί οι αριθμοί ονομάζονται άμεσες τιμές.
Μια άμεση τιμή δεν κρατείται σε κάποιον καταχωρητή ή στη μνήμη: κωδικοποιείται μέσα στην ίδια την εντολή. Στις περισσότερες εντολές, ο χώρος που της αναλογεί είναι μόνο 32 bit, ανεξάρτητα από το πόσο μεγάλος είναι ο τελεστέος προορισμού.
Όταν ο τελεστέος προορισμού είναι 64 bit, αυτά τα 32 bit επεκτείνονται με πρόσημο για να τον γεμίσουν. Το ανώτερο μισό του τελεστέου γεμίζει εξ ολοκλήρου με αντίγραφα του ανώτατου bit της άμεσης τιμής, οπότε μόνο ένας αριθμός στο εύρος ενός προσημασμένου ακεραίου 32 bit μπορεί να γραφτεί με αυτόν τον τρόπο:
add rax, -1 ; the immediate is sign-extended, so all 64 bits of rax are affected
add rax, 2147483647 ; the largest immediate an instruction like this accepts
Ένας αριθμός έξω από αυτό το εύρος δεν μπορεί να χρησιμοποιηθεί ως άμεση τιμή.
Η εξαίρεση σε αυτόν τον κανόνα είναι η mov, η οποία μπορεί να δεχτεί μια πλήρη άμεση τιμή 64 bit όταν ο τελεστέος προορισμού είναι καταχωρητής.
Αν χρειάζεσαι μια άμεση τιμή 64 bit, πρώτα χρησιμοποίησε την mov για να τη φορτώσεις σε έναν καταχωρητή και μετά χρησιμοποίησε αυτόν τον καταχωρητή:
mov rax, 3435973837 ; this works, mov can take a 64-bit immediate
mov rdx, 18446744073709551615 ; the largest immediate mov accepts
sub rdx, rax
Σημείωσε ότι μια αρνητική άμεση τιμή και ο μη προσημασμένος αριθμός με την ίδια αναπαράσταση bit είναι ισοδύναμοι και συμβολομεταφράζονται στην ίδια ακριβώς τιμή:
mov rax, -1 ; rax = 18446744073709551615
mov rax, 18446744073709551615 ; rax = -1
Η πρόσθεση δύο αριθμών μπορεί να υπολογιστεί με την εντολή add.
Υπάρχει επίσης η εντολή inc, με έναν τελεστέο, που προσθέτει 1 στην τιμή του τελεστέου της:
inc rax ; rax = rax + 1
Η πρόσθεση δύο ακεραίων λειτουργεί με τον ίδιο τρόπο για μη προσημασμένους και για προσημασμένους αριθμούς.
Η αφαίρεση δύο ακεραίων εκτελείται με την εντολή sub.
Υπάρχει επίσης η εντολή dec, με έναν τελεστέο, που αφαιρεί 1 από την τιμή του τελεστέου της:
dec rax ; rax = rax - 1
Η αφαίρεση δύο ακεραίων λειτουργεί επίσης με τον ίδιο τρόπο για μη προσημασμένους και για προσημασμένους αριθμούς.
Υπάρχουν δύο διαφορετικές εντολές για τον πολλαπλασιασμό δύο αριθμών στο x86-64.
Κατά κανόνα, ο μη προσημασμένος πολλαπλασιασμός χρησιμοποιεί την εντολή mul, ενώ ο προσημασμένος χρησιμοποιεί την imul.
Η εντολή mul παίρνει την παρακάτω μορφή με έναν τελεστέο, όπου src είναι ο τελεστέος πηγής:
mul src
Η εντολή imul μπορεί να πάρει μορφή με έναν, δύο ή τρεις τελεστέους:
imul src
imul dest, src
imul dest, src1, src2
Δύο καταχωρητές χρησιμοποιούνται σιωπηρά για τον πολλαπλασιασμό στη μορφή με έναν τελεστέο: οι rax και rdx.
Αν ο πολλαπλασιασμός αφορά δύο αριθμούς 64 bit, τότε τα κατώτερα 64 bit του αποτελέσματος θα βρίσκονται στον rax και τα ανώτερα 64 bit στον rdx.
Αυτό συνήθως ονομάζεται rdx:rax, για να δείξει ότι και οι δύο καταχωρητές χρησιμοποιούνται μαζί:
mul rcx ; rax = lower 64 bits of rax * rcx
; rdx = upper 64 bits of rax * rcx
Το ίδιο συμβαίνει και για άλλα μεγέθη τελεστέων.
Έτσι, για παράδειγμα, αν πολλαπλασιάζονται δύο αριθμοί 32 bit, θα χρησιμοποιηθούν οι eax και edx.
Η εξαίρεση είναι ο πολλαπλασιασμός μεταξύ δύο byte.
Σε αυτή την περίπτωση, αντί για dl:al, θα χρησιμοποιηθεί ο ax.
Το κατώτερο τμήμα του ax (al) θα πάρει τα κατώτερα 8 bit του γινομένου, ενώ το ανώτερο τμήμα (ah) θα πάρει τα ανώτερα 8 bit.
Οι καταχωρητές που χρησιμοποιούνται σιωπηρά σε έναν πολλαπλασιασμό, όπως οι rax και rdx, αντικαθίστανται πάντα.
Οι τιμές σε αυτούς τους καταχωρητές θα πρέπει να αποθηκεύονται πριν από την πράξη, αν τις χρειάζεσαι αργότερα.
Η μορφή της imul με δύο τελεστέους έχει έναν ρητό τελεστέο προορισμού και ακολουθεί τη συνήθη σύνταξη.
Ο rdx δεν χρησιμοποιείται.
Αντ' αυτού, το αποτέλεσμα περικόπτεται για να χωρέσει στον τελεστέο προορισμού.
imul r8, r9 ; r8 = lower 64 bits of r8 * r9
Η μορφή της imul με τρεις τελεστέους έχει δύο τελεστέους πηγής, ο δεύτερος από τους οποίους είναι πάντα μια άμεση τιμή (ένας σταθερός αριθμός).
Και οι δύο τελεστέοι πηγής πολλαπλασιάζονται και το αποτέλεσμα περικόπτεται και τοποθετείται στον τελεστέο προορισμού:
imul r8, r9, 100 ; r8 = lower 64 bits of r9 * 100
Σημείωσε ότι ο τελεστέος προορισμού δεν χρησιμοποιείται στον πολλαπλασιασμό. Απλώς δέχεται το αποτέλεσμα.
Τόσο ο πολλαπλασιασμός με δύο τελεστέους όσο και αυτός με τρεις περικόπτουν το αποτέλεσμα για να χωρέσει στο μέγεθος του τελεστέου προορισμού.
Ο πολλαπλασιασμός με έναν τελεστέο διατηρεί το πλήρες εύρος, αλλά συνήθως μοιράζεται σε δύο καταχωρητές, τους rdx και rax.
Έτσι, μερικές φορές είναι χρήσιμο να διευρύνεις τους τελεστέους πριν από τον πολλαπλασιασμό, ώστε να χωρέσει ολόκληρο το γινόμενο σε έναν μόνο καταχωρητή. Ένας μη προσημασμένος τελεστέος επεκτείνεται με μηδενικά, ενώ ένας προσημασμένος επεκτείνεται με πρόσημο:
movzx eax, di ; di and si hold unsigned 16-bit numbers
movzx ecx, si
mul ecx ; the 32-bit product fits in eax, and edx is cleared
Όπως συμβαίνει και με τον πολλαπλασιασμό, υπάρχουν επίσης δύο εντολές για τη διαίρεση δύο αριθμών.
Η μη προσημασμένη διαίρεση χρησιμοποιεί την εντολή div, ενώ η προσημασμένη χρησιμοποιεί την idiv.
Και οι δύο εντολές λειτουργούν με έναν μόνο τελεστέο:
div src
idiv src
Η διαίρεση 16, 32 και 64 bit χρησιμοποιεί ως διαιρετέο τους dx:ax, edx:eax και rdx:rax, αντίστοιχα.
Σε αυτές τις περιπτώσεις, και οι δύο καταχωρητές λειτουργούν μαζί για να σχηματίσουν μια τιμή 2N bit, όπου N είναι το μέγεθος της πράξης (16, 32 ή 64 bit).
Αυτή η τιμή διαιρείται στη συνέχεια με τον τελεστέο πηγής.
Το πηλίκο γράφεται στον ax, τον eax ή τον rax και το υπόλοιπο γράφεται στον dx, τον edx ή τον rdx, ανάλογα με το μέγεθος της πράξης.
Η διαίρεση μεταξύ byte είναι ειδική περίπτωση: αντί για dl:al, χρησιμοποιείται ο ax.
Τα κατώτερα 8 bit του ax (al) θα πάρουν το πηλίκο της πράξης και τα ανώτερα 8 bit (ah) θα πάρουν το υπόλοιπο.
Σημείωσε ότι όλα τα bit του διαιρετέου θα πρέπει να έχουν οριστεί κατάλληλα πριν από τη διαίρεση.
Κάθε bit που έχει τεθεί στον rdx (ή στον ah για διαίρεση 8 bit) συμβάλλει στην τιμή που διαιρείται.
Στη μη προσημασμένη διαίρεση, όταν η τιμή που διαιρείται χωράει στο κατώτερο μισό, το ανώτερο μισό θα πρέπει να μηδενιστεί.
Οποιαδήποτε εντολή μηδενίζει αυτά τα bit κάνει.
Για παράδειγμα, η mov edx, 0 μηδενίζει τα ανώτερα bit στη διαίρεση 32 bit.
Στην προσημασμένη διαίρεση, αντίθετα, η τιμή θα πρέπει να επεκταθεί με πρόσημο.
Υπάρχουν εντολές που αυτοματοποιούν αυτή τη διαδικασία: οι cbw, cwd, cdq και cqo.
Η πρώτη ορίζει τα bit του ah σύμφωνα με το πρόσημο του al.
Οι υπόλοιπες εκτελούν επέκταση προσήμου από τον ax στον dx, από τον eax στον edx και από τον rax στον rdx, αντίστοιχα.
Οι καταχωρητές που χρησιμοποιούνται σιωπηρά σε μια διαίρεση, όπως οι rax και rdx, αντικαθίστανται πάντα.
Οι τιμές σε αυτούς τους καταχωρητές θα πρέπει να αποθηκεύονται πριν από τη διαίρεση, αν τις χρειάζεσαι αργότερα.
Ένα τοπικό κατάστημα μεταφέρει το απόθεμά του σε μια μεγαλύτερη αποθήκη. Σε προσέλαβαν για να τα συσκευάσεις και να τα μεταφέρεις όλα.
Έχεις τέσσερις εργασίες, όλες σχετικές με τη διαχείριση της μεταφοράς.
Αυτές είναι οι εντολές που αναφέρονται σε αυτή την έννοια:
| Εντολή | Περιγραφή |
|---|---|
| add a, b | a = a + b |
| inc a | a = a + 1 |
| sub a, b | a = a - b |
| dec a | a = a - 1 |
| imul a | rdx:rax = a * rax (signed) |
| imul a, b | a = a * b (signed, truncated) |
| imul a, b, c | a = b * c (signed, truncated) |
| mul a | rdx:rax = a * rax (unsigned) |
| div a | rax = quotient, rdx = remainder of rdx:rax / a (unsigned) |
| idiv a | rax = quotient, rdx = remainder of rdx:rax / a (signed) |
| movzx a, b | a = b, adding 0 to the extra bits |
| movsx a, b | a = b, adding 1 to the extra bits if b < 0 or 0 otherwise |
Θυμήσου ότι μπορείς να προσπελάσεις τον ίδιο καταχωρητή με διαφορετικά μεγέθη αλλάζοντας το όνομα του τελεστέου.
Για παράδειγμα: rax (64-bit), eax (32-bit), ax (16-bit), al (8-bit).
Μπορείς να ανατρέξεις στην προηγούμενη έννοια για τον πλήρη πίνακα.
Τα αντικείμενα συσκευάζονται σε κουτιά που πρέπει να φέρουν ετικέτα με το βάρος τους. Δεν υπάρχει ζυγαριά τριγύρω, αλλά ευτυχώς ξέρεις πόσο ζυγίζει κατά μέσο όρο κάθε αντικείμενο.
Για καλύτερη οργάνωση, ένα κουτί περιέχει μόνο αντικείμενα από δύο διαφορετικά προϊόντα.
Όρισε μια συνάρτηση get_box_weight που επιστρέφει το συνολικό βάρος ενός κουτιού, σε g.
Αυτή η συνάρτηση παίρνει ως παραμέτρους, με αυτή τη σειρά:
g
g
Θεώρησε ότι ένα άδειο κουτί ζυγίζει 500 g.
Μια σταθερά WEIGHT_OF_EMPTY_BOX ορίζεται στην κορυφή του αρχείου λύσης.
Παράδειγμα:
get_box_weight(30, 40, 50, 20);
// => 2700
Όλα τα ορίσματα είναι 16-bit μη αρνητικοί ακέραιοι, και η τιμή επιστροφής είναι ένας 32-bit μη αρνητικός ακέραιος.
Τα κουτιά στοιβάζονται και μεταφέρονται στη νέα αποθήκη με ένα φορτηγό. Ωστόσο, ο κάθετος χώρος μέσα στο φορτηγό είναι περιορισμένος.
Όρισε μια συνάρτηση max_number_of_boxes που επιστρέφει πόσα κουτιά συγκεκριμένου ύψους μπορούν να στοιβαχτούν κάθετα (το ένα πάνω στο άλλο) μέσα στο φορτηγό.
Αυτή η συνάρτηση παίρνει ως παράμετρο το ύψος του κουτιού, σε cm.
Θεώρησε ότι το εσωτερικό ύψος του φορτηγού είναι 300 cm.
Μια σταθερά TRUCK_HEIGHT ορίζεται στην κορυφή του αρχείου λύσης.
Παράδειγμα:
max_number_of_boxes(30);
// => 10
Το όρισμα και η τιμή επιστροφής είναι 8-bit μη αρνητικοί ακέραιοι.
Το ύψος του κουτιού είναι πάντα τουλάχιστον 2, οπότε το αποτέλεσμα χωράει σε 8 bits.
Υπάρχει μια λίστα ελέγχου στη νέα αποθήκη με τον αριθμό των αντικειμένων που δεν έχουν ακόμη λογιστεί για κάθε προϊόν. Για κάθε νέο κουτί που μεταφέρεται εκεί, πρέπει να υπολογίσεις τη νέα τιμή στη λίστα ελέγχου για κάθε προϊόν του κουτιού.
Όρισε μια συνάρτηση items_to_be_moved που επιστρέφει πόσα αντικείμενα απομένουν να μεταφερθούν στη νέα αποθήκη για ένα δεδομένο προϊόν.
Αυτή η συνάρτηση παίρνει ως παραμέτρους, με αυτή τη σειρά:
Παράδειγμα:
items_to_be_moved(76532, 120);
// => 76412
Τα ορίσματα είναι 32-bit μη αρνητικοί ακέραιοι. Η τιμή επιστροφής είναι ένας 32-bit ακέραιος. Σε περίπτωση σφάλματος στη διαδικασία, είναι πιθανό το αποτέλεσμα να είναι αρνητικός αριθμός.
Η αμοιβή σου βασίζεται στο πόσα κουτιά μεταφέρθηκαν και πόσα δρομολόγια με το φορτηγό χρειάστηκαν.
Για κάθε κουτί θα πληρωθείς 5 δολάρια και για κάθε δρομολόγιο θα πληρωθείς 220 δολάρια.
Οι σταθερές PAY_PER_BOX και PAY_PER_TRUCK_TRIP ορίζονται στην κορυφή του αρχείου λύσης.
Σημείωσε ότι μπορεί να έχεις λάβει μέρος αυτής της αμοιβής προκαταβολικά για να καλύψεις αρχικά έξοδα, και αυτή η προκαταβολή πρέπει να αφαιρεθεί από την τελική αμοιβή. Επιπλέον, ορισμένα προϊόντα δεν καλύπτονται από ασφάλεια και η αμοιβή σου θα μειωθεί επίσης κατά την αξία όποιων από αυτά τα αντικείμενα έσπασαν ή χάθηκαν. Είναι πιθανό να καταλήξεις να χρωστάς χρήματα αν δεν προσέξεις!
Αυτό σημαίνει ότι το καθαρό ποσό που σου οφείλεται, ή που οφείλεις, είναι:
net = boxes * PAY_PER_BOX + trips * PAY_PER_TRUCK_TRIP - up_front - broken_items * item_value
Αυτή η αμοιβή, ή το χρέος, θα μοιραστεί εξίσου ανάμεσα σε εσένα και σε έναν αριθμό εργατών που προσέλαβες.
Τα τυχόν υπόλοιπα χρήματα, ή το χρέος, είναι δικά σου.
Για παράδειγμα, αν το καθαρό ποσό των χρημάτων είναι 100 και μοιράζεται σε 6 άτομα (εσύ και 5 εργάτες), παίρνεις 20 (100/(5 + 1) = 16 συν τα υπόλοιπα 4).
Όρισε μια συνάρτηση calculate_payment που επιστρέφει πόσα πρέπει να πληρωθείς, ή να πληρώσεις, στο τέλος.
Αυτή η συνάρτηση παίρνει ως παραμέτρους, με αυτή τη σειρά:
Παράδειγμα:
calculate_payment(2000, 1000, 5, 21, 2, 1);
// => 2029
Η τιμή επιστροφής είναι ένας 64-bit ακέραιος.
Γράψου στο Exercism για να μάθεις και να κατακτήσεις x86-64 Assembly με 22 έννοιες130 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.