Rutas
/
x86-64 Assembly
x86-64 Assembly
/
Ejercicios
/
Cambio de divisas
Cambio de divisas

Cambio de divisas

Ejercicio de aprendizaje

Introducción

En conceptos anteriores se mencionó que la directiva global hace que una función o una variable definidas en un fichero fuente sean visibles para otros ficheros. De forma similar, la directiva extern informa al ensamblador de que una función o una variable que se usa en el fichero fuente actual está definida en otro.

Esas dos directivas son la forma principal en que el código ensamblador interactúa con otros ficheros fuente para lograr la modularización.

A efectos de estas directivas, es indiferente que el código externo esté definido o se use en ensamblador o en un lenguaje de alto nivel. Así, por ejemplo, incluso una función definida en un lenguaje de alto nivel puede usarse en código x86-64 si se declara extern.

Del mismo modo, la convención de llamada es compartida por todas esas funciones, definidas en ensamblador o en lenguajes de alto nivel, e incluso con el sistema operativo (SO).

Sin embargo, como en ensamblador los datos no son más que una secuencia de bytes, es importante saber cómo gestiona la memoria el lenguaje de alto nivel con el que se interactúa.

Tipos de C

El lenguaje C es una elección habitual como lenguaje de alto nivel para interactuar con código ensamblador. Facilita la tarea de interactuar con el sistema operativo, ya que ofrece envoltorios de alto nivel para muchas tareas importantes, como imprimir en pantalla, leer del teclado o reservar memoria dinámica.

En este track, los tests están escritos en C, así que es importante ofrecer una visión general del lenguaje.

Note

Cualquiera de los tipos indicados a continuación puede calificarse con const. Esto los convierte en tipos de solo lectura.

Tipos primitivos

En C hay muchos tipos primitivos y su tamaño, en bytes, puede variar. Algunos de ellos se resumen en la siguiente tabla, con su tamaño típico en un sistema x86-64:

tipo número de bytes entero/coma flotante
_Bool 1 entero
char 1 entero
short 2 entero
int 4 entero
long 4 u 8 entero
long long 8 entero
float 4 coma flotante
double 8 coma flotante

Cada uno de esos tipos enteros puede ser con signo (el comportamiento predeterminado) o sin signo. Cuando son sin signo, hay que indicarlo, por ejemplo: unsigned int.

Existen alias para los tipos enteros que se definen para ofrecer un control preciso sobre su tamaño:

alias número de bytes
int8_t 1
int16_t 2
int32_t 4
int64_t 8

Esos alias pueden llevar el prefijo u para indicar un tipo sin signo, por ejemplo: uint64_t.

Otros alias que merece la pena destacar son:

alias tipo
bool _Bool
size_t normalmente un entero sin signo de 8 bytes

Un _Bool, o su alias bool, es singular: aunque ocupa 1 byte entero, solo puede tomar uno de dos valores: true (1) y false (0).

Los valores de esos tipos primitivos se pasan a las funciones, y se devuelven desde ellas, según las reglas habituales para los enteros y los valores de coma flotante.

Enumeraciones

Un enum es un tipo que puede tomar uno de entre varios elementos indicados explícitamente:

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

En C, los enums se convierten implícitamente a un tipo entero, que suele ser int, es decir, un entero de 4 bytes.

De forma predeterminada, el primer elemento posible de un enum se convierte en 0 y todos los elementos siguientes se convierten en el entero siguiente. Así, en el enum example definido antes, example_1 tendría el valor 0, example_2 tendría el valor 1, y así sucesivamente.

Es posible indicar el valor de cualquier elemento de un enum y, a partir de ahí, de forma predeterminada, el elemento siguiente tendría como valor el entero siguiente:

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

Direcciones de memoria

En C, una dirección de memoria se referencia mediante un puntero a un tipo y se denota con el operador *. Así, por ejemplo, int64_t * hace referencia a una posición de memoria para uno o varios int64_t.

Las direcciones se tratan como enteros de 8 bytes, como es habitual.

Arrays

Los arrays se pasan a las funciones, y se devuelven desde ellas, como punteros a su primer elemento.

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

Cada elemento de un array tiene el tamaño del tipo de los elementos del array. El example_arr definido antes, por ejemplo, tiene 3 elementos de 8 bytes cada uno, lo que suma 24 bytes en total.

Como los arrays no almacenan su longitud, normalmente hace falta un valor aparte para indicar el número de elementos, a menos que un valor centinela marque el final.

Strings

Los strings en C son un array de char, que es un tipo de 1 byte. La mayoría de los strings constan de caracteres ASCII y terminan en NUL, así que acaban cuando se encuentra un byte con el valor 0. Esto significa que la longitud de un string no suele necesitarse como argumento aparte.

Instrucciones

Tu amigo Chandler tiene pensado visitar países exóticos de todo el mundo. Por desgracia, a Chandler no se le dan bien las matemáticas. Le preocupa bastante que le estafen en las casas de cambio durante el viaje, y quiere que le hagas una calculadora de divisas.

Estas son sus especificaciones para la aplicación.

Note

Las funciones de este ejercicio se declaran en un fichero fuente de C con las firmas proporcionadas. Tu código de ensamblador x86-64 debe seguir esas especificaciones.

1. Haz un seguimiento de las distintas divisas

Has creado un enum currency_t para hacer un seguimiento de las distintas divisas que Chandler usa en sus viajes:

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

A veces necesitas imprimir esas divisas en pantalla para poder comprobar si son correctas.

Crea la función stringify_currency:

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

Esta función no tiene valor devuelto y debe almacenar un string al estilo de C en la ubicación indicada por buffer. El string debe representar visualmente qué tipo de divisa se pasó a la función:

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

2. Comprueba el tipo de cambio

Crea la función exchange_rate:

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

El argumento domestic_currency representa la divisa local de Chandler que se va a cambiar por una foreign_currency.

value_in_US_dollars es un puntero a un array de double que contiene el valor de una unidad de cada divisa en dólares estadounidenses. Las divisas son índices en este array. Así, si EUR es un elemento válido para enum currency_t, entonces value_in_US_dollars[EUR] da el valor de 1 EUR en dólares estadounidenses.

Esta función debe devolver el valor de una unidad de la divisa extranjera en la divisa local.

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. Calcula el valor de los billetes

Crea la función get_value_of_bills:

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

El argumento denomination es el valor de un solo billete, mientras que number_of_bills es el número total de billetes.

Esta casa de cambio solo trabaja con efectivo en determinados incrementos. El total que recibes debe ser divisible por el valor de un «billete» o unidad, lo que puede dejar una fracción o un residuo. Tu función debe devolver solo el valor total de los billetes (excluyendo las cantidades fraccionarias) que te devolvería la casa de cambio. Por desgracia, la casa de cambio se queda con el residuo o el cambio como una bonificación adicional.

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

4. Calcula el número de billetes

Crea la función get_number_of_bills:

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

Esta función debe devolver el número de billetes que puedes recibir dentro de la cantidad dada. En otras palabras: ¿cuántos billetes enteros caben en la cantidad inicial? Recuerda que solo puedes recibir billetes enteros, no fracciones de billete, así que acuérdate de dividir en consecuencia. En efecto, estás redondeando hacia abajo al billete o denominación entera más cercana.

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

5. Calcula el valor tras el cambio

Crea la función exchangeable_value:

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

El parámetro spread es el porcentaje que se lleva la casa de cambio como comisión, escrito como un número entero. Hay que convertirlo a decimal dividiéndolo por 100. Ten en cuenta que el spread se suma al tipo de cambio, de modo que un spread de 10 da como resultado un tipo de cambio un 10 % más alto.

Esta función debe devolver el valor máximo de la nueva divisa tras calcular el tipo de cambio ajustado por el spread. Recuerda que la denominación de la divisa es un número entero y no se puede subdividir.

exchangeable_value(127.25, 0,873350884, 10, 20);
// => 120
exchangeable_value(127.25, 0,873350884, 10, 5);
// => 130
Editar en GitHub El enlace se abre en una ventana o pestaña nueva
x86-64 Assembly Exercism

¿Listo para empezar Cambio de divisas?

Regístrate en Exercism para aprender y dominar x86-64 Assembly con 22 conceptos130 ejercicios y mentoría humana real, todo gratis.