Треки
/
x86-64 Assembly
x86-64 Assembly
/
Вправи
/
Ставки фрилансера
Ставки фрилансера

Ставки фрилансера

Навчальна вправа

Вступ

Числа з плаваючою комою

Число з плаваючою комою - це число, яке може мати дробову частину, наприклад -1.23 або 3.1415.

Щоб подати таке число як послідовність байтів, x86-64 використовує стандарт IEEE-754. Два найпоширеніші формати в цьому стандарті - числа з плаваючою комою одинарної та подвійної точності. x86-64 підтримує обидва.

Число з плаваючою комою одинарної точності має ширину 32 біти, тоді як подвійної - 64 біти.

Регістри XMM

У x86-64 є 16 окремих регістрів для арифметики з плаваючою комою, названих від xmm0 до xmm15.

Ці регістри мають ширину щонайменше 128 бітів, але в сучасніших процесорах вона зростає до 256 або 512 бітів. Утім, на практиці більшість чисел з плаваючою комою використовують лише наймолодші 32 або 64 біти, залежно від потрібної точності.

Перетворення між числами

Існує сімейство інструкцій для перетворення між форматами чисел: cvtxx2yy.

І xx, і yy - не літерали, а заповнювачі:

  • xx - заповнювач для типу операнда-джерела.
  • yy - заповнювач для типу операнда-призначення.

Цей заповнювач може набувати багатьох форм, зокрема:

заповнювач значення
si ціле число зі знаком
ss 32-бітне число з плаваючою комою
sd 64-бітне число з плаваючою комою

Наприклад:

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

Зауважмо, що можна перетворити 32-бітне ціле число на 64-бітне число з плаваючою комою або 64-бітне ціле число на 32-бітне число з плаваючою комою. Так само можна перетворити 32-бітне число з плаваючою комою на 64-бітне ціле число або 64-бітне число з плаваючою комою на 32-бітне ціле число. Інакше кажучи, розміри обох операндів у cvtxx2yy не обовʼязково мають бути однаковими:

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

Коли число з плаваючою комою не можна точно подати як ціле, cvtxx2si округлює його відповідно до керування округленням у спеціальному регістрі mxcsr. Це може бути округлення до найближчого, округлення вниз, округлення вгору або відкидання дробової частини. Гарантованого типового значення на вході у функцію немає.

Зауважмо, що точний результат не округлюється і, отже, не залежить від керування округленням. Наприклад, 42.0 завжди буде перетворено на 42.

Є подібне сімейство інструкцій із форматом cvttxx2si (з додатковою t), які завжди відкидають дробову частину результату.

Округлення

Сімейство інструкцій roundxx перетворює число з плаваючою комою в операнді-джерелі на округлене число з плаваючою комою, збережене в операнді-призначенні.

Тут xx, як завжди, є заповнювачем для типу операндів: ss для чисел одинарної точності, а sd для чисел подвійної точності.

Ці інструкції приймають стале число як третій операнд. Це число вказує режим округлення:

Число Режим
0 До найближчого
1 Униз (floor)
2 Угору (ceil)
3 Відкидання

Наприклад:

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

Оскільки ці інструкції дають точний результат, їх можна використати як підготовку до cvtxx2si.

Арифметичні інструкції

Багато операцій із числами з плаваючою комою виконуються інструкціями, назви яких подібні до відповідних інструкцій для цілих чисел, але з додаванням потрібного заповнювача (ss або sd) у кінці. Ці інструкції дводоперандні, зі звичною семантикою, навіть ті, що виконують множення чи ділення:

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)

Пересилання

Інструкції для пересилання з одного регістра XMM до іншого - це movss або 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

Не можна переслати безпосереднє значення (стале ціле число зі знаком) прямо в регістр xmm. Спершу це число потрібно переслати в регістр загального призначення, а потім перетворити за допомогою 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

Порівняння

Є дві групи інструкцій для порівняння двох чисел з плаваючою комою. Вони мають вигляд comixx і ucomixx, де xx - це або ss, або sd. Обидві встановлюють прапорці в rflags, як звично, з тією ж семантикою, що й cmp.

Різниця між ними в тому, що ucomixx встановлює parity flag (PF), якщо одне з двох чисел з плаваючою комою має значення NaN, тоді як comixx у цьому випадку зумовлює виняток.

Порівняння між значеннями з плаваючою комою є беззнаковим:

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

Угода про виклики

У System V ABI перші вісім аргументів з плаваючою комою передаються у функції по порядку, від xmm0 до xmm7. Значення з плаваючою комою повертаються з функцій у xmm0 і, за потреби, у xmm1.

Усі регістри xmm доступні для вільного використання, жоден із них не зарезервовано для того, хто викликає.

Памʼять

Значення з плаваючою комою можна оголосити в памʼяті за тією ж семантикою, що й цілі числа того самого розміру. NASM (The Netwide Assembler - асемблер, який використовує цей трек) також дозволяє оголошувати значення з плаваючою комою в науковому записі.

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

Вказівки

У цій вправі ми напишемо код, який допоможе фрилансеру спілкуватися з менеджером проєкту. Наше завдання - надати кілька утиліт для швидкого обчислення денної та місячної ставок, за потреби з урахуванням заданої знижки.

Спершу встановімо кілька правил між фрилансером і менеджером проєкту:

  • Денна ставка у 8 разів більша за погодинну.
  • У місяці 22 оплачувані дні.

Іноді фрилансер пропонує застосувати знижку до своєї денної ставки (наприклад, для найвідданіших клієнтів або для неприбуткових організацій).

Знижки подаються як дробові числа, що позначають відсотки, наприклад 25.0 (25%).

Note

Ось суфікси для інструкцій з плаваючою комою:

Суфікс Розмір операнда
ss 32-бітний
sd 64-бітний

Ось деякі з інструкцій, згаданих у цій концепції:

Інструкція Опис
movss/movsd a, b копіює b в 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 перетворює ціле число b на число a з плаваючою комою
roundss/roundsd a, b, c округлює число b з плаваючою комою відповідно до c і зберігає результат у a
cvtss2si/cvtsd2si a, b перетворює число b з плаваючою комою на ціле число a
comiss/comisd a, b порівнює a та b, виняток, якщо NaN
ucomiss/ucomisd a, b порівнює a та b, встановлює PF, якщо NaN

Ось числа керування округленням у roundss і roundsd:

Число Режим
0 До найближчого
1 Униз (до меншого)
2 Угору (до більшого)
3 Відкинути дробову частину

1. Обчислити денну ставку за погодинною ставкою

Реалізуйте функцію daily_rate, яка обчислює денну ставку, отримуючи як параметр погодинну ставку у вигляді 64-бітного числа з плаваючою комою. Умова визначає, що робочий день має 8 оплачуваних годин.

daily_rate(60)
// => 480.0

Повернена денна ставка має бути 64-бітним числом з плаваючою комою.

2. Обчислити ціну зі знижкою

Реалізуйте функцію apply_discount, яка обчислює ціну після застосування знижки. Вона має приймати два параметри: початкову ціну та розмір знижки у відсотках, обидва у вигляді 64-бітного числа з плаваючою комою.

apply_discount(150.0, 10.0)
// => 135.0

Повернене значення завжди має бути 64-бітним числом з плаваючою комою.

3. Обчислити місячну ставку за погодинною ставкою та знижкою

Реалізуйте функцію monthly_rate, яка обчислює місячну ставку зі знижкою. Вона приймає два параметри: погодинну ставку та знижку у відсотках, обидва у вигляді 64-бітного числа з плаваючою комою.

monthly_rate(77.0, 10.5)
// => 12130

Повернену місячну ставку потрібно округлити вгору (взяти верхню межу) до найближчого 64-бітного цілого числа.

4. Обчислити кількість повних робочих днів за бюджетом, погодинною ставкою та знижкою

Реалізуйте функцію days_in_budget, яка приймає такі параметри:

  1. Бюджет у вигляді 64-бітного беззнакового цілого числа.
  2. Погодинну ставку у вигляді 64-бітного числа з плаваючою комою.
  3. Знижку у відсотках у вигляді 64-бітного числа з плаваючою комою.

Ця функція має повернути кількість повних днів роботи, які покриває бюджет.

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

Повернену кількість днів потрібно округлити вниз (взяти нижню межу) до найближчого 32-бітного цілого числа.

Редагувати через GitHub Посилання відкривається в новому вікні або вкладці
x86-64 Assembly Exercism

Час розпочати Ставки фрилансера?

Зареєструйтеся на Exercism, щоб вивчати й опановувати x86-64 Assembly, а також 22 концепції130 вправ та справжнє наставництво від людей, і все це безкоштовно.