Tracks
/
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 definida en un archivo fuente sea visible para los demás archivos. De manera similar, la directiva extern le indica al ensamblador que una función o una variable que se usa en el archivo fuente actual está definida en otro.

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

Para los fines de estas directivas, es indiferente si el código externo está definido o se usa 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.

De la misma manera, la convención de llamada se comparte entre todas esas funciones, ya estén definidas en ensamblador o en lenguajes de alto nivel, e incluso con el sistema operativo (SO).

Sin embargo, como en ensamblador los datos son solo una secuencia de bytes, es importante tener en cuenta cómo administra la memoria el lenguaje de alto nivel con el que se interactúa.

Tipos de C

El lenguaje C es una elección común como lenguaje de alto nivel para interactuar con código ensamblador. Facilita la tarea de interactuar con el sistema operativo y ofrece envoltorios de alto nivel para muchas tareas importantes, como imprimir en pantalla, leer desde un teclado o asignar memoria dinámica.

En este track, las pruebas están escritas 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 hace 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/punto flotante
_Bool 1 entero
char 1 entero
short 2 entero
int 4 entero
long 4 u 8 entero
long long 8 entero
float 4 punto flotante
double 8 punto flotante

Cada uno de esos tipos enteros puede ser con signo (el valor predeterminado) o sin signo. En caso de que no tengan 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 vale la pena mencionar son:

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

Un _Bool, o su alias bool, es particular porque, aunque ocupa 1 byte completo, 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 punto 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 enum se convierten implícitamente en un tipo entero, que normalmente es un 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 siguiente entero. Así, en el enum example definido arriba, 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, entonces, de forma predeterminada, el siguiente elemento tendría como valor el siguiente entero:

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

Direcciones de memoria

En C, se hace referencia a una dirección de memoria mediante un puntero a un tipo, y se denota con el operador *. Así, por ejemplo, int64_t * se refiere a una ubicación de memoria para uno o más 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 elemento del array. El example_arr definido arriba, por ejemplo, tiene 3 elementos de 8 bytes cada uno, es decir, 24 bytes en total.

Como los arrays no almacenan su longitud, normalmente se necesita 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 son de caracteres ASCII y terminan en NUL, así que acaban cuando se encuentra un byte con el valor 0. Esto significa que, por lo general, no hace falta pasar la longitud de un string como un argumento aparte.

Instrucciones

Tu amigo Chandler planea visitar países exóticos de todo el mundo. Lamentablemente, a Chandler no se le dan bien las matemáticas. Le preocupa bastante que lo estafen en los cambios de moneda durante su 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 archivo fuente de C con las firmas proporcionadas. Tu código de ensamblador x86-64 debe seguir esas especificaciones.

1. Lleva un registro de las diferentes monedas

Has creado un enum currency_t para llevar un registro de las diferentes monedas que Chandler usa en sus viajes:

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

A veces necesitas imprimir esas monedas en la pantalla para 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 de retorno y debe almacenar un string de estilo C en la ubicación indicada por buffer. El string debe representar visualmente qué tipo de moneda se pasó a la función:

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

2. Verifica 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 moneda local de Chandler que se 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 moneda en dólares estadounidenses. Las monedas 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 moneda extranjera en la moneda 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.

Este puesto de cambio solo opera con efectivo en ciertos 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 (sin incluir las cantidades fraccionarias) que el puesto te devolvería. Lamentablemente, el puesto se queda con el residuo/vuelto como bonificación adicional.

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

4. Calcula la cantidad 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 del monto dado. En otras palabras: ¿cuántos billetes completos de la moneda caben en el monto inicial? Recuerda: solo puedes recibir billetes completos, no fracciones de billetes, así que recuerda dividir en consecuencia. En efecto, estás redondeando hacia abajo al billete/denominación entero más cercano.

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

5. Calcula el valor después del 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 toma como comisión de cambio, escrito como un entero. Se debe convertir 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 10% más alto.

Esta función debe devolver el valor máximo de la nueva moneda después de calcular el tipo de cambio ajustado por el spread. Recuerda que la denominación de la moneda 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 una pestaña nuevas
x86-64 Assembly Exercism

¿Todo 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.