Число з плаваючою комою - це число, яке може мати дробову частину, наприклад -1.23 або 3.1415.
Щоб подати таке число як послідовність байтів, x86-64 використовує стандарт IEEE-754. Два найпоширеніші формати в цьому стандарті - числа з плаваючою комою одинарної та подвійної точності. x86-64 підтримує обидва.
Число з плаваючою комою одинарної точності має ширину 32 біти, тоді як подвійної - 64 біти.
У 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
У цій вправі ми напишемо код, який допоможе фрилансеру спілкуватися з менеджером проєкту. Наше завдання - надати кілька утиліт для швидкого обчислення денної та місячної ставок, за потреби з урахуванням заданої знижки.
Спершу встановімо кілька правил між фрилансером і менеджером проєкту:
Іноді фрилансер пропонує застосувати знижку до своєї денної ставки (наприклад, для найвідданіших клієнтів або для неприбуткових організацій).
Знижки подаються як дробові числа, що позначають відсотки, наприклад 25.0 (25%).
Ось суфікси для інструкцій з плаваючою комою:
| Суфікс | Розмір операнда |
|---|---|
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 | Відкинути дробову частину |
Реалізуйте функцію daily_rate, яка обчислює денну ставку, отримуючи як параметр погодинну ставку у вигляді 64-бітного числа з плаваючою комою.
Умова визначає, що робочий день має 8 оплачуваних годин.
daily_rate(60)
// => 480.0
Повернена денна ставка має бути 64-бітним числом з плаваючою комою.
Реалізуйте функцію apply_discount, яка обчислює ціну після застосування знижки.
Вона має приймати два параметри: початкову ціну та розмір знижки у відсотках, обидва у вигляді 64-бітного числа з плаваючою комою.
apply_discount(150.0, 10.0)
// => 135.0
Повернене значення завжди має бути 64-бітним числом з плаваючою комою.
Реалізуйте функцію monthly_rate, яка обчислює місячну ставку зі знижкою.
Вона приймає два параметри: погодинну ставку та знижку у відсотках, обидва у вигляді 64-бітного числа з плаваючою комою.
monthly_rate(77.0, 10.5)
// => 12130
Повернену місячну ставку потрібно округлити вгору (взяти верхню межу) до найближчого 64-бітного цілого числа.
Реалізуйте функцію days_in_budget, яка приймає такі параметри:
Ця функція має повернути кількість повних днів роботи, які покриває бюджет.
days_in_budget(20000, 80, 11.0)
// => 35
Повернену кількість днів потрібно округлити вниз (взяти нижню межу) до найближчого 32-бітного цілого числа.
Зареєструйтеся на Exercism, щоб вивчати й опановувати x86-64 Assembly, а також 22 концепції130 вправ та справжнє наставництво від людей, і все це безкоштовно.