Συνάλλαγμα

Συνάλλαγμα

Άσκηση εκμάθησης

Εισαγωγή

Σε προηγούμενες έννοιες αναφέρθηκε ότι η οδηγία global κάνει μια συνάρτηση ή μεταβλητή που έχει οριστεί σε ένα αρχείο πηγαίου κώδικα ορατή σε άλλα αρχεία. Παρομοίως, η οδηγία extern ενημερώνει τον assembler ότι μια συνάρτηση ή μεταβλητή που χρησιμοποιείται στο τρέχον αρχείο πηγαίου κώδικα έχει οριστεί σε κάποιο άλλο.

Αυτές οι δύο οδηγίες είναι ο κύριος τρόπος με τον οποίο ο κώδικας assembly συνεργάζεται με άλλα αρχεία πηγαίου κώδικα για να επιτύχει τμηματοποίηση.

Για τους σκοπούς αυτών των οδηγιών, δεν έχει σημασία αν ο εξωτερικός κώδικας ορίζεται ή χρησιμοποιείται σε assembly ή σε γλώσσα υψηλού επιπέδου. Έτσι, για παράδειγμα, ακόμη και μια συνάρτηση που έχει οριστεί σε γλώσσα υψηλού επιπέδου μπορεί να χρησιμοποιηθεί σε κώδικα x86-64 αν δηλωθεί ως extern.

Με τον ίδιο τρόπο, η σύμβαση κλήσης είναι κοινή για όλες αυτές τις συναρτήσεις, είτε έχουν οριστεί σε assembly είτε σε γλώσσες υψηλού επιπέδου, ακόμη και με το Λειτουργικό Σύστημα (ΛΣ).

Ωστόσο, επειδή στο assembly τα δεδομένα είναι απλώς μια ακολουθία από bytes, είναι σημαντικό να γνωρίζεις πώς η γλώσσα υψηλού επιπέδου με την οποία συνεργάζεσαι διαχειρίζεται τη μνήμη.

Τύποι της C

Η γλώσσα C είναι μια συνηθισμένη επιλογή ως γλώσσα υψηλού επιπέδου για να συνεργαστείς με κώδικα assembly. Κάνει το έργο της αλληλεπίδρασης με το ΛΣ ευκολότερο, προσφέροντας wrappers υψηλού επιπέδου για πολλές σημαντικές εργασίες, όπως η εκτύπωση στην οθόνη, η ανάγνωση από το πληκτρολόγιο ή η δέσμευση δυναμικής μνήμης.

Σε αυτό το track, τα tests είναι γραμμένα σε C, οπότε είναι σημαντικό να δώσουμε μια γενική επισκόπηση της γλώσσας.

Note

Οποιοσδήποτε από τους τύπους που αναφέρονται παρακάτω μπορεί να χαρακτηριστεί με const. Αυτό τους κάνει μόνο για ανάγνωση.

Αρχέγονοι τύποι

Υπάρχουν πολλοί αρχέγονοι τύποι στη C και το μέγεθός τους, σε bytes, μπορεί να ποικίλλει. Κάποιοι από αυτούς συνοψίζονται στον παρακάτω πίνακα, με το τυπικό μέγεθός τους σε ένα σύστημα x86-64:

τύπος αριθμός bytes ακέραιος/κινητής υποδιαστολής
_Bool 1 ακέραιος
char 1 ακέραιος
short 2 ακέραιος
int 4 ακέραιος
long 4 ή 8 ακέραιος
long long 8 ακέραιος
float 4 κινητής υποδιαστολής
double 8 κινητής υποδιαστολής

Καθένας από αυτούς τους ακέραιους τύπους μπορεί να είναι προσημασμένος (η προεπιλογή) ή απροσήμαντος. Αν είναι απροσήμαντοι, αυτό πρέπει να δηλωθεί, για παράδειγμα: unsigned int.

Υπάρχουν ψευδώνυμα για τους ακέραιους τύπους, που έχουν οριστεί για να παρέχουν ακριβή έλεγχο του μεγέθους τους:

ψευδώνυμο αριθμός bytes
int8_t 1
int16_t 2
int32_t 4
int64_t 8

Αυτά τα ψευδώνυμα μπορούν να έχουν πρόθεμα u για να δηλώσουν απροσήμαντο τύπο, για παράδειγμα: uint64_t.

Άλλα αξιοσημείωτα ψευδώνυμα είναι:

ψευδώνυμο τύπος
bool _Bool
size_t συνήθως ένας απροσήμαντος ακέραιος 8 bytes

Το _Bool, ή το ψευδώνυμό του bool, είναι μοναδικό στο ότι, παρόλο που καταλαμβάνει 1 ολόκληρο byte, μπορεί να πάρει μόνο μία από δύο τιμές: true (1) και false (0).

Οι τιμές αυτών των αρχέγονων τύπων περνούν ως ορίσματα σε συναρτήσεις και επιστρέφονται από αυτές σύμφωνα με τους συνήθεις κανόνες για ακέραιες τιμές και τιμές κινητής υποδιαστολής.

Απαριθμήσεις

Μια enum είναι ένας τύπος που μπορεί να πάρει ένα από έναν αριθμό ρητά δηλωμένων στοιχείων:

enum example {
    example_1,
    example_2,
    example_3,
    example_4
};

Στη C, οι enum μετατρέπονται σιωπηρά σε έναν ακέραιο τύπο, που είναι συνήθως int, δηλαδή ένας ακέραιος 4 bytes.

Από προεπιλογή, το πρώτο δυνατό στοιχείο μιας enum μετατρέπεται σε 0 και όλα τα επόμενα στοιχεία μετατρέπονται στον επόμενο ακέραιο. Έτσι, στην enum example που ορίστηκε παραπάνω, το example_1 θα είχε τιμή 0, το example_2 θα είχε τιμή 1 και ούτω καθεξής.

Είναι δυνατό να δηλώσεις την τιμή για οποιοδήποτε στοιχείο μιας enum και τότε, από προεπιλογή, το επόμενο στοιχείο θα έχει ως τιμή τον επόμενο ακέραιο:

enum example_2 {
    example_1 = 4,
    example_2 = 8,
    example_3
}; // example_3 is equal to 9

Διευθύνσεις μνήμης

Στη C, μια διεύθυνση μνήμης αναφέρεται μέσω ενός δείκτη προς έναν τύπο και συμβολίζεται με τον τελεστή *. Έτσι, για παράδειγμα, το int64_t * αναφέρεται σε μια θέση μνήμης για ένα ή περισσότερα int64_t.

Οι διευθύνσεις αντιμετωπίζονται ως ακέραιοι 8 bytes, όπως συνήθως.

Πίνακες

Οι πίνακες περνούν ως ορίσματα σε συναρτήσεις και επιστρέφονται από αυτές ως δείκτες προς το πρώτο τους στοιχείο.

int64_t example_arr[] = {1, 2, 3}; // this is an array of 3 signed 8-byte integers
fn(example_arr); // this is a function that passes a pointer (int64_t *) to the beginning of the array as an argument

Κάθε στοιχείο σε έναν πίνακα έχει το μέγεθος του τύπου στοιχείου του πίνακα. Ο example_arr που ορίστηκε παραπάνω, για παράδειγμα, έχει 3 στοιχεία των 8 bytes το καθένα, δηλαδή 24 bytes συνολικά.

Επειδή οι πίνακες δεν αποθηκεύουν το μήκος τους, συνήθως χρειάζεται μια ξεχωριστή τιμή που να δηλώνει τον αριθμό των στοιχείων, εκτός αν μια τιμή-φρουρός σηματοδοτεί το τέλος.

Συμβολοσειρές

Οι συμβολοσειρές στη C είναι ένας πίνακας από char, που είναι ένας τύπος 1 byte. Οι περισσότερες συμβολοσειρές αποτελούνται από χαρακτήρες ASCII και τερματίζονται με NUL, οπότε τελειώνουν όταν βρεθεί ένα byte με τιμή 0. Αυτό σημαίνει ότι το μήκος μιας συμβολοσειράς συνήθως δεν χρειάζεται να περαστεί ως ξεχωριστό όρισμα.

Οδηγίες

Ο φίλος σου ο Chandler σχεδιάζει να επισκεφτεί εξωτικά μέρη σε όλο τον κόσμο. Δυστυχώς, ο Chandler δεν τα πάει καλά με τα μαθηματικά. Ανησυχεί αρκετά μήπως τον εξαπατήσουν τα ανταλλακτήρια συναλλάγματος στο ταξίδι του, και θέλει να του φτιάξεις έναν υπολογιστή συναλλάγματος.

Αυτές είναι οι προδιαγραφές του για την εφαρμογή.

Note

Οι συναρτήσεις αυτής της άσκησης δηλώνονται σε ένα αρχείο πηγαίου κώδικα C με τις υπογραφές που δίνονται. Ο κώδικας σου σε assembly x86-64 πρέπει να ακολουθεί αυτές τις προδιαγραφές.

1. Παρακολούθησε τα διαφορετικά νομίσματα

Έχεις δημιουργήσει ένα enum currency_t για να παρακολουθείς τα διαφορετικά νομίσματα που χρησιμοποιεί ο Chandler στα ταξίδια του:

enum currency_t {
    GBP,
    EUR,
    JPY,
    AUD,
    BRL,
    CNY,
    CAD,
    INR
};

Κάποιες φορές χρειάζεται να εκτυπώσεις αυτά τα νομίσματα στην οθόνη για να ελέγξεις αν είναι σωστά.

Δημιούργησε τη συνάρτηση stringify_currency:

void stringify_currency(char *buffer, enum currency_t currency);

Αυτή η συνάρτηση δεν έχει τιμή επιστροφής και πρέπει να αποθηκεύει μια συμβολοσειρά τύπου C στη θέση που δείχνει το buffer. Η συμβολοσειρά πρέπει να αναπαριστά οπτικά το είδος του νομίσματος που πέρασε στη συνάρτηση:

char buffer[4];
stringify_currency(buffer, CAD);
// => "CAD"

2. Έλεγξε την ισοτιμία

Δημιούργησε τη συνάρτηση exchange_rate:

double exchange_rate(enum currency_t domestic_currency, enum currency_t foreign_currency, const double *value_in_US_dollars);

Το όρισμα domestic_currency αντιπροσωπεύει το τοπικό νόμισμα του Chandler που θα ανταλλαχθεί με ένα foreign_currency.

Το value_in_US_dollars είναι ένας δείκτης σε έναν πίνακα από double που περιέχει την τιμή μιας μονάδας κάθε νομίσματος σε δολάρια ΗΠΑ. Τα νομίσματα είναι θέσεις σε αυτόν τον πίνακα. Έτσι, αν το EUR είναι έγκυρο στοιχείο για το enum currency_t, τότε το value_in_US_dollars[EUR] δίνει την τιμή του 1 EUR σε δολάρια ΗΠΑ.

Αυτή η συνάρτηση πρέπει να επιστρέφει την τιμή μιας μονάδας του ξένου νομίσματος στο εγχώριο νόμισμα.

const double value_in_US_dollars[8] = {
    1.33329, 1.16443, 0.00654374, 0.650888,
    0.185577, 0.140420, 0.714142, 0.0113863
};

exchange_rate(JPY, GBP, value_in_US_dollars);
// => 203.750454633

3. Υπολόγισε την αξία των χαρτονομισμάτων

Δημιούργησε τη συνάρτηση get_value_of_bills:

uint64_t get_value_of_bills(unsigned long long denomination, unsigned short number_of_bills);

Το όρισμα denomination είναι η ονομαστική αξία ενός μεμονωμένου χαρτονομίσματος, ενώ το number_of_bills είναι ο συνολικός αριθμός των χαρτονομισμάτων.

Αυτό το ανταλλακτήριο συναλλάσσει μόνο μετρητά σε συγκεκριμένες υποδιαιρέσεις. Το συνολικό ποσό που λαμβάνεις πρέπει να διαιρείται με την αξία ενός "χαρτονομίσματος" ή μιας μονάδας, κάτι που μπορεί να αφήσει κλάσμα ή υπόλοιπο. Η συνάρτηση σου πρέπει να επιστρέφει μόνο τη συνολική αξία των χαρτονομισμάτων (χωρίς τα κλασματικά ποσά) που θα σου έδινε το ανταλλακτήριο. Δυστυχώς, το ανταλλακτήριο κρατάει το υπόλοιπο/τα ρέστα ως επιπλέον μπόνους.

get_value_of_bills(5, 128);
// => 640

4. Υπολόγισε τον αριθμό των χαρτονομισμάτων

Δημιούργησε τη συνάρτηση get_number_of_bills:

unsigned int get_number_of_bills(float amount, unsigned long long denomination);

Αυτή η συνάρτηση πρέπει να επιστρέφει τον αριθμό των χαρτονομισμάτων που μπορείς να λάβεις μέσα στο δεδομένο ποσό. Με άλλα λόγια: πόσα ολόκληρα χαρτονομίσματα χωράνε στο αρχικό ποσό; Θυμήσου, μπορείς να λάβεις μόνο ολόκληρα χαρτονομίσματα, όχι κλάσματα χαρτονομισμάτων, οπότε θυμήσου να διαιρέσεις αναλόγως. Στην πράξη, στρογγυλοποιείς προς τα κάτω στο πλησιέστερο ολόκληρο χαρτονόμισμα/ονομαστική αξία.

get_number_of_bills(127.5, 5);
// => 25

5. Υπολόγισε την αξία μετά την ανταλλαγή

Δημιούργησε τη συνάρτηση exchangeable_value:

uint32_t exchangeable_value(float budget, double exchange_rate, uint8_t spread, unsigned long long denomination);

Η παράμετρος spread είναι το ποσοστό που κρατείται ως προμήθεια ανταλλαγής, γραμμένο ως ακέραιος. Πρέπει να μετατραπεί σε δεκαδικό διαιρώντας το με το 100. Σημείωσε ότι το spread προστίθεται στην ισοτιμία, έτσι ώστε ένα spread 10 να δίνει ισοτιμία 10% υψηλότερη.

Αυτή η συνάρτηση πρέπει να επιστρέφει τη μέγιστη αξία του νέου νομίσματος αφού υπολογίσεις την ισοτιμία προσαρμοσμένη με το spread. Θυμήσου ότι η ονομαστική αξία του νομίσματος είναι ακέραιος αριθμός και δεν μπορεί να διαιρεθεί περαιτέρω.

exchangeable_value(127.25, 0,873350884, 10, 20);
// => 120
exchangeable_value(127.25, 0,873350884, 10, 5);
// => 130
Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα
x86-64 Assembly Exercism

Έτοιμος να ξεκινήσεις την άσκηση Συνάλλαγμα;

Γράψου στο Exercism για να μάθεις και να κατακτήσεις x86-64 Assembly με 22 έννοιες130 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.