Trilhas
/
x86-64 Assembly
x86-64 Assembly
/
Exercícios
/
Tarifas de freelancer
Tarifas de freelancer

Tarifas de freelancer

Exercício de aprendizagem

Introdução

Números de ponto flutuante

Um número de ponto flutuante é um número que pode ter uma parte fracionária, como -1.23 ou 3.1415.

Para representar um número assim como uma sequência de bytes, o x86-64 usa o padrão IEEE-754. Os dois formatos mais comuns desse padrão são os números de ponto flutuante de precisão simples e de precisão dupla. O x86-64 dá suporte aos dois.

Um número de ponto flutuante de precisão simples tem 32 bits de largura, enquanto um de precisão dupla tem 64 bits.

Registradores XMM

No x86-64, há 16 registradores dedicados à aritmética de ponto flutuante, chamados de xmm0 a xmm15.

Esses registradores têm pelo menos 128 bits de largura, mas processadores recentes aumentam isso para 256 ou 512 bits. Na prática, porém, a maioria dos números de ponto flutuante usa apenas os 32 ou os 64 bits mais baixos, dependendo da precisão desejada.

Conversão entre números

Existe uma família de instruções para converter entre formatos de número: cvtxx2yy.

Tanto xx quanto yy não são literais, mas espaços reservados:

  • xx é um espaço reservado para o tipo do operando de origem.
  • yy é um espaço reservado para o tipo do operando de destino.

Esse espaço reservado pode assumir muitas formas, entre elas:

placeholder meaning
si inteiro com sinal
ss ponto flutuante de 32 bits
sd ponto flutuante de 64 bits

Por exemplo:

cvtsi2ss xmm0, eax ; converts a 32-bit signed integer to a single-precision floating-point
cvtsd2si rax, xmm1 ; converts a double-precision floating-point to a 64-bit signed integer

Repare que é possível converter um inteiro de 32 bits em um ponto flutuante de 64 bits ou um inteiro de 64 bits em um ponto flutuante de 32 bits. Da mesma forma, também é possível converter um ponto flutuante de 32 bits em um inteiro de 64 bits ou um ponto flutuante de 64 bits em um inteiro de 32 bits. Em outras palavras, o tamanho dos dois operandos em uma cvtxx2yy não precisa ser o mesmo:

cvtsi2sd xmm0, eax ; converts a 32-bit signed integer to a double-precision floating-point
cvtsi2ss xmm0, rax ; converts a 64-bit signed integer to a single-precision floating-point
cvtss2si rax, xmm0 ; converts a single-precision floating-point to a 64-bit signed integer
cvtsd2si eax, xmm0 ; converts a double-precision floating-point to a 32-bit signed integer

Quando o número de ponto flutuante não puder ser expresso exatamente como um inteiro, cvtxx2si arredonda o número de acordo com o controle de arredondamento em um registrador especial chamado mxcsr. Isso pode ser um arredondamento para o mais próximo, para baixo, para cima ou um truncamento. Não há um padrão garantido na entrada da função.

Repare que um resultado exato não é arredondado e, portanto, não é afetado pelo controle de arredondamento. Por exemplo, 42.0 seria sempre convertido para 42.

Há uma família semelhante de instruções com o formato cvttxx2si (com um t a mais) que sempre trunca o resultado.

Arredondamento

A família de instruções roundxx converte um ponto flutuante no operando de origem em um ponto flutuante arredondado, armazenado no operando de destino.

Aqui, xx é um espaço reservado para o tipo dos operandos, como de costume: ss para precisão simples e sd para precisão dupla.

Essas instruções recebem um número constante como terceiro operando. Esse número indica o modo de arredondamento:

Number Mode
0 Mais próximo
1 Piso (para baixo)
2 Teto (para cima)
3 Truncar

Por exemplo:

roundss xmm0, xmm1, 1 ; this rounds down (takes the floor) a 32-bit floating-point in xmm1 and stores it in xmm0
roundsd xmm2, xmm2, 2 ; this rounds up (takes the ceil) a 64-bit floating-point in xmm2 and stores it in xmm2

Como essas instruções produzem um resultado exato, elas podem ser usadas como preparação para uma cvtxx2si.

Instruções aritméticas

Muitas operações com números de ponto flutuante usam instruções com um nome semelhante ao equivalente para inteiros, mas acrescentando o espaço reservado necessário (ss ou sd) no final. Essas instruções têm dois operandos, com a semântica de sempre, inclusive as que fazem multiplicação ou divisão:

addss xmm1, xmm2 ; xmm1 = xmm1 + xmm2 (all 32-bit floating-point)
mulsd xmm4, xmm5 ; xmm4 = xmm4 * xmm5 (all 64-bit floating-point)
divss xmm0, xmm3 ; xmm0 = xmm0 / xmm3 (all 32-bit floating-point)

Movimentação

As instruções para mover de um registrador XMM para outro são movss ou movsd:

movss xmm1, xmm0 ; this copies a 32-bit floating-point from xmm0 to xmm1
movsd xmm6, xmm4 ; this copies a 64-bit floating-point from xmm4 to xmm6

Não é possível mover um imediato (um inteiro constante com sinal) diretamente para um registrador xmm. Esse número primeiro precisa ser movido para um registrador de uso geral e depois convertido usando cvtsi2xx:

mov rax, 10        ; rax is a 64-bit integer with value 10
cvtsi2sd xmm0, rax ; xmm0 is a 64-bit floating-point with value 10.0

Comparações

Há dois grupos de instruções para comparar dois números de ponto flutuante. Elas têm a forma comixx e ucomixx, em que xx é ss ou sd. Ambas definem flags em rflags como de costume, seguindo a mesma semântica do cmp.

A diferença entre elas é que ucomixx define a parity flag (PF) se um dos dois números de ponto flutuante tiver um valor NaN, enquanto comixx gera uma exceção nesse caso.

A comparação entre valores de ponto flutuante é sem sinal:

ucomisd xmm0, xmm1
ja .greater         ; 'ja' must be used for "greater", not 'jg'
jb .lesser          ; 'jb' must be used for "lesser", not 'jl'

Convenção de chamada

Na ABI System V, os oito primeiros argumentos de ponto flutuante são passados para funções em ordem, de xmm0 a xmm7. Valores de ponto flutuante são retornados pelas funções em xmm0 e, se necessário, em xmm1.

Todos os registradores xmm estão livres para uso, nenhum deles é reservado para quem chama.

Memória

Valores de ponto flutuante podem ser definidos na memória seguindo a mesma semântica dos inteiros do mesmo tamanho. O NASM (The Netwide Assembler, o montador usado por esta trilha) também permite declarar valores de ponto flutuante em notação científica.

section .data
    pi dq 3.141592653589793 ; this is a double-precision floating-point value
    exp_num dd 1.e5 ; this is a single precision floating-point value equal to 1 * 10⁵
    exp_num2 dd 1.e+5 ; this is a single precision floating-point value equal to 1 * 10⁵
    frac dq 1.e-10 ; this is a double precision floating-point value equal to 1 * 10⁻¹⁰, i.e., 0.0000000001

Instruções

Neste exercício, você vai escrever código para ajudar uma pessoa freelancer a se comunicar com um gerente de projeto. Sua tarefa é fornecer alguns utilitários para calcular rapidamente as taxas diária e mensal, opcionalmente com um desconto dado.

Primeiro, estabelecemos algumas regras entre a pessoa freelancer e o gerente de projeto:

  • A taxa diária é 8 vezes a taxa horária.
  • Um mês tem 22 dias faturáveis.

Às vezes, a pessoa freelancer oferece aplicar um desconto sobre sua taxa diária (por exemplo, para clientes mais fiéis ou clientes sem fins lucrativos).

Os descontos são modelados como números fracionários que representam porcentagens, por exemplo, 25.0 (25%).

Note

Estes são os sufixos das instruções de ponto flutuante:

Sufixo Tamanho do operando
ss 32 bits
sd 64 bits

Estas são algumas das instruções mencionadas neste conceito:

Instrução Descrição
movss/movsd a, b copia b para a
addss/addsd a, b a = a + b
subss/subsd a, b a = a - b
mulss/mulsd a, b a = a * b
divss/divsd a, b a = a / b
cvtsi2ss/cvtsi2sd a, b converte o inteiro b para ponto flutuante a
roundss/roundsd a, b, c arredonda o ponto flutuante b de acordo com c e armazena em a
cvtss2si/cvtsd2si a, b converte o ponto flutuante b para o inteiro a
comiss/comisd a, b faz uma comparação entre a e b, exceção se NaN
ucomiss/ucomisd a, b faz uma comparação entre a e b, define PF se NaN

Estes são os números de controle de arredondamento em roundss e roundsd:

Número Modo
0 Mais próximo
1 Para baixo
2 Para cima
3 Truncar

1. Calcule a taxa diária a partir de uma taxa horária

Implemente uma função chamada daily_rate para calcular a taxa diária a partir de uma taxa horária recebida como parâmetro, como um número de ponto flutuante de 64 bits. O contrato define que um dia tem 8 horas faturáveis.

daily_rate(60)
// => 480.0

A taxa diária retornada deve ser um número de ponto flutuante de 64 bits.

2. Calcule um preço com desconto

Implemente uma função apply_discount para calcular o preço após um desconto. Ela deve aceitar dois parâmetros: o preço original e a taxa de desconto em porcentagem, ambos como um número de ponto flutuante de 64 bits.

apply_discount(150.0, 10.0)
// => 135.0

O valor retornado deve ser sempre um número de ponto flutuante de 64 bits.

3. Calcule a taxa mensal, a partir de uma taxa horária e de um desconto

Implemente uma função monthly_rate para calcular a taxa mensal com desconto. Ela recebe dois parâmetros: uma taxa horária e o desconto em porcentagem, ambos como um número de ponto flutuante de 64 bits.

monthly_rate(77.0, 10.5)
// => 12130

A taxa mensal retornada deve ser arredondada para cima (teto) para o inteiro de 64 bits mais próximo.

4. Calcule o número de dias úteis completos a partir de um orçamento, uma taxa horária e um desconto

Implemente uma função days_in_budget que recebe como parâmetros:

  1. Um orçamento, como um inteiro sem sinal de 64 bits.
  2. Uma taxa horária, como um número de ponto flutuante de 64 bits.
  3. Um desconto em porcentagem, como um número de ponto flutuante de 64 bits.

Essa função deve retornar o número de dias completos de trabalho que o orçamento cobre.

days_in_budget(20000, 80, 11.0)
// => 35

O número de dias retornado deve ser arredondado para baixo (piso) para o inteiro de 32 bits mais próximo.

Editar via GitHub O link abre em uma nova janela ou aba
x86-64 Assembly Exercism

Tudo pronto para começar Tarifas de freelancer?

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.