Câmbio de moedas

Câmbio de moedas

Exercício de aprendizagem

Introdução

Nos conceitos anteriores, foi mencionado que a diretiva global torna uma função ou variável definida em um arquivo fonte visível para outros arquivos. Da mesma forma, a diretiva extern informa ao montador que uma função ou variável usada no arquivo fonte atual está definida em outro.

Essas duas diretivas são a principal forma de o código assembly interagir com outros arquivos fonte para alcançar a modularização.

Para os propósitos dessas diretivas, é indiferente se o código externo é definido ou usado em assembly ou em uma linguagem de alto nível. Assim, por exemplo, até uma função definida em uma linguagem de alto nível pode ser usada em código x86-64 se for declarada extern.

Da mesma forma, a convenção de chamada é compartilhada por todas essas funções, definidas em assembly ou em linguagens de alto nível, e até mesmo com o Sistema Operacional (SO).

No entanto, como em assembly os dados são apenas uma sequência de bytes, é importante entender como a linguagem de alto nível envolvida gerencia a memória.

Tipos em C

A linguagem C é uma escolha comum quando uma linguagem de alto nível precisa interagir com código assembly. Ela facilita a tarefa de interagir com o SO, oferecendo abstrações de alto nível para muitas tarefas importantes, como imprimir na tela, ler do teclado ou alocar memória dinâmica.

Nesta trilha, os testes são escritos em C, então é importante apresentar uma visão geral da linguagem.

Note

Qualquer um dos tipos indicados abaixo pode ser qualificado com const. Isso os torna somente leitura.

Tipos primitivos

Existem muitos tipos primitivos em C, e o tamanho deles, em bytes, pode variar. Alguns deles estão resumidos na tabela a seguir, com o tamanho típico em um sistema x86-64:

tipo número de bytes inteiro/ponto flutuante
_Bool 1 inteiro
char 1 inteiro
short 2 inteiro
int 4 inteiro
long 4 ou 8 inteiro
long long 8 inteiro
float 4 ponto flutuante
double 8 ponto flutuante

Cada um desses tipos integrais pode ser com sinal (o padrão) ou sem sinal. Caso sejam sem sinal, isso precisa ser especificado, por exemplo: unsigned int.

Existem aliases para os tipos integrais, definidos para dar controle preciso sobre o tamanho deles:

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

Esses aliases podem receber o prefixo u para indicar um tipo sem sinal, por exemplo: uint64_t.

Outros aliases que vale a pena mencionar:

alias tipo
bool _Bool
size_t geralmente um inteiro sem sinal de 8 bytes

Um _Bool, ou seu alias bool, é especial porque, embora ocupe 1 byte inteiro, só pode assumir um de dois valores: true (1) e false (0).

Os valores desses tipos primitivos são passados para funções, e retornados por elas, de acordo com as regras usuais para inteiros e valores de ponto flutuante.

Enums

Um enum é um tipo que pode assumir um entre vários elementos indicados explicitamente:

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

Em C, enums são convertidos implicitamente para um tipo inteiro, que normalmente é um int, ou seja, um inteiro de 4 bytes.

Por padrão, o primeiro elemento possível de um enum é convertido para 0, e todos os elementos seguintes são convertidos para o próximo inteiro. Então, no enum example definido acima, example_1 teria o valor 0, example_2 teria o valor 1, e assim por diante.

É possível indicar o valor de qualquer elemento de um enum e, a partir daí, por padrão, o próximo elemento teria o próximo inteiro como valor:

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

Endereços de memória

Em C, um endereço de memória é referenciado por um ponteiro para um tipo e denotado com o operador *. Assim, por exemplo, int64_t * se refere a um local de memória para um ou mais int64_t.

Os endereços são tratados como inteiros de 8 bytes, como de costume.

Arrays

Arrays são passados para funções, e retornados por elas, como ponteiros para o primeiro 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 um array tem o tamanho do tipo de elemento do array. O example_arr definido acima, por exemplo, tem 3 elementos de 8 bytes cada, o que dá 24 bytes no total.

Como os arrays não armazenam seu comprimento, normalmente é necessário um valor separado para indicar o número de elementos, a menos que um valor sentinela marque o fim.

Strings

Strings em C são um array de char, que é um tipo de 1 byte. A maioria das strings é composta de caracteres ASCII e termina com NUL, então elas acabam quando um byte com o valor 0 é encontrado. Isso significa que o comprimento de uma string geralmente não precisa ser passado como um argumento separado.

Instruções

Seu amigo Chandler planeja visitar países exóticos pelo mundo todo. Infelizmente, Chandler não é bom em matemática. Ele está bastante preocupado em ser enganado por casas de câmbio durante a viagem, e quer que você faça uma calculadora de câmbio para ele.

Aqui estão as especificações dele para o aplicativo.

Note

As funções deste exercício são declaradas em um arquivo-fonte C com as assinaturas fornecidas. Seu código em assembly x86-64 deve seguir essas especificações.

1. Acompanhe as diferentes moedas

Você criou um enum currency_t para acompanhar as diferentes moedas que Chandler usa em suas viagens:

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

Às vezes, você precisa imprimir essas moedas na tela para conferir se estão certas.

Crie a função stringify_currency:

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

Esta função não tem valor de retorno e deve armazenar uma string no estilo C no local indicado por buffer. A string deve representar visualmente qual tipo de moeda foi passado para a função:

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

2. Verifique a taxa de câmbio

Crie a função exchange_rate:

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

O argumento domestic_currency representa a moeda local de Chandler a ser trocada por uma foreign_currency.

value_in_US_dollars é um ponteiro para um array de double que contém o valor de uma unidade de cada moeda em dólares americanos. As moedas são índices nesse array. Assim, se EUR é um elemento válido para enum currency_t, então value_in_US_dollars[EUR] fornece o valor de 1 EUR em dólares americanos.

Esta função deve retornar o valor de uma unidade da moeda estrangeira na moeda 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. Calcule o valor das cédulas

Crie a função get_value_of_bills:

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

O argumento denomination é o valor de uma única cédula, enquanto number_of_bills é o número total de cédulas.

Esta casa de câmbio só trabalha com dinheiro em certos incrementos. O total que você recebe deve ser divisível pelo valor de uma "cédula", ou unidade, o que pode deixar uma fração ou um resto. Sua função deve retornar apenas o valor total das cédulas (excluindo valores fracionários) que a casa de câmbio devolveria. Infelizmente, a casa de câmbio fica com o resto/troco como bônus.

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

4. Calcule o número de cédulas

Crie a função get_number_of_bills:

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

Esta função deve retornar o número de cédulas que você pode receber dentro da quantia informada. Em outras palavras: quantas cédulas inteiras cabem na quantia inicial? Lembre-se: você só pode receber cédulas inteiras, não frações de cédulas, então lembre-se de dividir de acordo. Na prática, você está arredondando para baixo até a cédula/denominação inteira mais próxima.

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

5. Calcule o valor após o câmbio

Crie a função exchangeable_value:

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

O parâmetro spread é a porcentagem cobrada como taxa de câmbio, escrita como um número inteiro. Ela precisa ser convertida para decimal dividindo-a por 100. Note que o spread é somado à taxa de câmbio, de modo que um spread de 10 resulta em uma taxa de câmbio 10% maior.

Esta função deve retornar o valor máximo da nova moeda após calcular a taxa de câmbio ajustada pelo spread. Lembre-se de que a denominação da moeda é um número inteiro e não pode ser subdividida.

exchangeable_value(127.25, 0,873350884, 10, 20);
// => 120
exchangeable_value(127.25, 0,873350884, 10, 5);
// => 130
Editar via GitHub O link abre em uma nova janela ou aba
x86-64 Assembly Exercism

Tudo pronto para começar Câmbio de moedas?

Crie sua conta no Exercism para aprender e dominar x86-64 Assembly com 22 conceitos130 exercícios e mentoria humana de verdade, tudo de graça.