Parcours
/
x86-64 Assembly
x86-64 Assembly
/
Exercices
/
Tarifs d'un freelance
Tarifs d'un freelance

Tarifs d'un freelance

Exercice d'apprentissage

Introduction

Nombres à virgule flottante

Un nombre à virgule flottante est un nombre qui peut comporter une partie fractionnaire, comme -1.23 ou 3.1415.

Pour représenter un tel nombre sous la forme d'une séquence d'octets, x86-64 utilise la norme IEEE-754. Les deux formats les plus courants de cette norme sont les nombres à virgule flottante en simple précision et en double précision. x86-64 prend en charge les deux.

Un nombre à virgule flottante en simple précision occupe 32 bits, tandis qu'un nombre en double précision en occupe 64.

Registres XMM

En x86-64, il existe 16 registres dédiés à l'arithmétique en virgule flottante, nommés xmm0 à xmm15.

Ces registres font au moins 128 bits de large, mais les processeurs récents portent cette taille à 256 ou 512 bits. Cependant, en pratique, la plupart des nombres à virgule flottante n'utilisent que les 32 ou 64 bits de poids faible, selon la précision souhaitée.

Conversion entre nombres

Il existe une famille d'instructions pour convertir entre les formats de nombres : cvtxx2yy.

xx et yy ne sont pas des littéraux, mais des espaces réservés :

  • xx est un espace réservé pour le type de l'opérande source.
  • yy est un espace réservé pour le type de l'opérande destination.

Cet espace réservé peut prendre de nombreuses formes, entre autres :

espace réservé signification
si entier signé
ss virgule flottante 32 bits
sd virgule flottante 64 bits

Par exemple :

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

Note qu'il est possible de convertir un entier 32 bits en un nombre à virgule flottante 64 bits, ou un entier 64 bits en un nombre à virgule flottante 32 bits. De même, il est aussi possible de convertir un nombre à virgule flottante 32 bits en un entier 64 bits, ou un nombre à virgule flottante 64 bits en un entier 32 bits. Autrement dit, la taille des deux opérandes d'une instruction cvtxx2yy n'a pas besoin d'être la même :

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

Lorsqu'un nombre à virgule flottante ne peut pas être exprimé exactement comme un entier, cvtxx2si arrondit le nombre selon le contrôle d'arrondi d'un registre spécial appelé mxcsr. Il peut s'agir d'un arrondi au plus proche, d'un arrondi vers le bas, d'un arrondi vers le haut ou d'une troncature. Il n'y a pas de valeur par défaut garantie à l'entrée d'une fonction.

Note qu'un résultat exact n'est pas arrondi et n'est donc pas affecté par le contrôle d'arrondi. Par exemple, 42.0 est toujours converti en 42.

Il existe une famille d'instructions similaire, de la forme cvttxx2si (avec un t supplémentaire), qui tronque toujours le résultat.

Arrondi

La famille d'instructions roundxx convertit un nombre à virgule flottante de l'opérande source en un nombre à virgule flottante arrondi, stocké dans l'opérande destination.

Ici, comme d'habitude, xx est un espace réservé pour le type des opérandes : ss pour la simple précision et sd pour la double précision.

Ces instructions prennent un nombre constant comme troisième opérande. Ce nombre indique le mode d'arrondi :

Nombre Mode
0 Au plus proche
1 Plancher (vers le bas)
2 Plafond (vers le haut)
3 Troncature

Par exemple :

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

Comme ces instructions produisent un résultat exact, on peut les utiliser en préparation d'une instruction cvtxx2si.

Instructions arithmétiques

De nombreuses opérations sur les nombres à virgule flottante disposent d'instructions au nom similaire à celui de leur équivalent pour les entiers, auquel on ajoute le suffixe de type nécessaire (ss ou sd). Ces instructions prennent deux opérandes, avec la sémantique habituelle, y compris celles qui effectuent une multiplication ou une division :

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)

Déplacement

Les instructions pour déplacer un nombre d'un registre XMM vers un autre sont 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

Il n'est pas possible de déplacer directement une valeur immédiate (un entier signé constant) vers un registre xmm. Ce nombre doit d'abord être placé dans un registre à usage général, puis converti avec 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

Comparaisons

Il existe deux groupes d'instructions pour comparer deux nombres à virgule flottante. Elles ont la forme comixx et ucomixx, où xx vaut ss ou sd. Toutes deux positionnent des drapeaux dans rflags, comme d'habitude, en suivant la même sémantique que cmp.

La différence entre elles est que ucomixx positionne le parity flag (PF) si l'un des deux nombres à virgule flottante a une valeur NaN, tandis que comixx lève une exception dans ce cas.

La comparaison entre valeurs à virgule flottante est non signée :

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

Convention d'appel

Dans l'ABI System V, les huit premiers arguments en virgule flottante sont passés aux fonctions dans l'ordre, de xmm0 à xmm7. Les valeurs à virgule flottante sont renvoyées par les fonctions dans xmm0 et, si nécessaire, dans xmm1.

Tous les registres xmm sont librement utilisables : aucun d'entre eux n'est réservé à l'appelant.

Mémoire

Les valeurs à virgule flottante peuvent être définies en mémoire en suivant la même sémantique que les entiers de même taille. NASM (The Netwide Assembler, l'assembleur utilisé par ce parcours) permet aussi de déclarer des valeurs à virgule flottante en notation scientifique.

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

Instructions

Dans cet exercice, tu vas écrire du code pour aider un freelance à communiquer avec un chef de projet. Ton objectif est de fournir quelques outils pour calculer rapidement des tarifs journaliers et mensuels, avec éventuellement une remise donnée.

On commence par établir quelques règles entre le freelance et le chef de projet :

  • Le tarif journalier correspond à 8 fois le tarif horaire.
  • Un mois compte 22 jours facturables.

Il arrive que le freelance propose d'appliquer une remise sur son tarif journalier (par exemple pour ses clients les plus fidèles ou pour des clients à but non lucratif).

Les remises sont modélisées par des nombres décimaux représentant des pourcentages, par exemple 25.0 (25 %).

Note

Voici les suffixes des instructions en virgule flottante :

Suffixe Taille de l'opérande
ss 32 bits
sd 64 bits

Voici quelques-unes des instructions abordées dans ce concept :

Instruction Description
movss/movsd a, b copie b dans 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 convertit l'entier b en un nombre à virgule flottante a
roundss/roundsd a, b, c arrondit le nombre à virgule flottante b selon c et stocke le résultat dans a
cvtss2si/cvtsd2si a, b convertit le nombre à virgule flottante b en l'entier a
comiss/comisd a, b effectue une comparaison entre a et b, déclenche une exception si NaN
ucomiss/ucomisd a, b effectue une comparaison entre a et b, positionne PF si NaN

Voici les nombres de contrôle d'arrondi de roundss et roundsd :

Nombre Mode
0 Au plus proche
1 Vers le bas
2 Vers le haut
3 Troncature

1. Calculer le tarif journalier à partir d'un tarif horaire

Implémente une fonction daily_rate qui calcule le tarif journalier à partir d'un tarif horaire passé en paramètre, sous forme de nombre à virgule flottante 64 bits. Le contrat stipule qu'une journée compte 8 heures facturables.

daily_rate(60)
// => 480.0

Le tarif journalier renvoyé doit être un nombre à virgule flottante 64 bits.

2. Calculer un prix après remise

Implémente une fonction apply_discount qui calcule le prix après remise. Elle doit accepter deux paramètres : le prix d'origine et le taux de remise en pourcentage, tous deux sous forme de nombres à virgule flottante 64 bits.

apply_discount(150.0, 10.0)
// => 135.0

La valeur renvoyée doit toujours être un nombre à virgule flottante 64 bits.

3. Calculer le tarif mensuel à partir d'un tarif horaire et d'une remise

Implémente une fonction monthly_rate qui calcule le tarif mensuel après remise. Elle prend deux paramètres, un tarif horaire et la remise en pourcentage, tous deux sous forme de nombres à virgule flottante 64 bits.

monthly_rate(77.0, 10.5)
// => 12130

Le tarif mensuel renvoyé doit être arrondi vers le haut (arrondi au plafond) en un entier 64 bits.

4. Calculer le nombre de jours travaillés complets à partir d'un budget, d'un tarif horaire et d'une remise

Implémente une fonction days_in_budget qui prend les paramètres suivants :

  1. Un budget, sous forme d'entier non signé 64 bits.
  2. Un tarif horaire, sous forme de nombre à virgule flottante 64 bits.
  3. Une remise en pourcentage, sous forme de nombre à virgule flottante 64 bits.

Cette fonction doit renvoyer le nombre de jours de travail complets que le budget couvre.

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

Le nombre de jours renvoyé doit être arrondi vers le bas (arrondi au plancher) en un entier 32 bits.

Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet
x86-64 Assembly Exercism

Prêt à commencer Tarifs d'un freelance ?

Inscris-toi sur Exercism pour apprendre et maîtriser x86-64 Assembly avec 22 concepts130 exercices, et un vrai mentorat humain, le tout gratuitement.