🤔 1. Что такое FPU простыми словами?
Представьте калькулятор внутри процессора. Обычный процессор умеет работать только с целыми числами:
- ✅ Сложить:
5 + 3 = 8 - ✅ Умножить:
10 × 2 = 20 - ❌ Но разделить
10 ÷ 3 = 3.333...— не может! Получится3(дробная часть потеряна)
FPU (Floating Point Unit) — это специальный “калькулятор для дробных чисел” внутри процессора, который умеет:
- Работать с числами типа
3.14159,0.0001,1.5e-10 - Вычислять синусы, косинусы, логарифмы
- Сохранять очень большие ($10^{308}$) и очень маленькие ($10^{-308}$) числа
Словарь: основные термины
| Термин | Расшифровка | Что означает |
|---|---|---|
| FPU | Floating Point Unit | Блок вещественных вычислений |
| float | — | Вещественное число (дробное) |
| ST(0) | Stack Top | Вершина стека FPU (самый верхний элемент) |
fld |
Floating Load | Загрузить число в FPU |
fstp |
Floating Store and Pop | Сохранить число и выгрузить из FPU |
dword |
Double Word | 4 байта = 32 бита (тип float в C) |
qword |
Quad Word | 8 байт = 64 бита (тип double в C) |
tword |
Ten bytes | 10 байт = 80 бит (максимальная точность FPU) |
🚀 2. Быстрый старт
Вот минимальный пример, который умножает число 3.14 на 2:
section .data
pi dq 3.14159265358979 ; dq = qword = 8 байт = double
two dq 2.0
result dq 0.0
section .text
global _start
_start:
fld qword [pi] ; Шаг 1: загрузить π в FPU
fld qword [two] ; Шаг 2: загрузить 2 в FPU
fmulp ; Шаг 3: умножить и выгрузить одно число
fstp qword [result] ; Шаг 4: сохранить результат в память
; Выход из программы
mov rax, 60
xor rdi, rdi
syscall
Разбор по шагам
Шаг 1: fld qword [pi] — что происходит?
fld = Floating LoaD (загрузить вещественное число)
qwordговорит: “это 8-байтовое число типаdouble”[pi]— адрес в памяти, где лежит число 3.14159…- Результат: число помещается на вершину стека FPU
Стек FPU после fld [pi]: ┌────────────┐ │ 3.14159... │ ← ST(0) (вершина) └────────────┘
Шаг 2: fld qword [two] — почему снова fld?
Потому что умножение требует ДВА числа! Загружаем второе:
Стек FPU после fld [two]:
┌────────────┐
│ 2.0 │ ← ST(0) (новая вершина!)
├────────────┤
│ 3.14159... │ ← ST(1) (π "провалилось" вниз)
└────────────┘☝️ Важно: Каждый новый fld кладёт число СВЕРХУ, старые элементы сдвигаются вниз.
Шаг 3: fmulp — что такое суффикс "p"?
fmulp = Floating MULtiply and Pop
- Умножает два верхних числа:
ST(0) × ST(1) - Суффикс
pозначает pop (выгрузить) — убирает один элементДо fmulp: После fmulp: ┌────────────┐ ┌────────────┐ │ 2.0 │ ST(0) │ 6.28318... │ ← ST(0) (результат 3.14×2) ├────────────┤ └────────────┘ │ 3.14159... │ ST(1) └────────────┘ (два элемента) (один элемент)
Шаг 4: fstp qword [result] — сохранение результата
fstp = Floating STore and Pop
st= сохранить (store) в памятьp= выгрузить (pop) из стекаqword= сохранить как 8-байтовое числоДо fstp: После fstp: ┌────────────┐ ┌────────────┐ │ 6.28318... │ │ (пусто) │ └────────────┘ └────────────┘ Память [result] = 6.28318...
✅ Теперь стек FPU пуст и готов к новым вычислениям!
Словарь суффиксов инструкций
В FPU суффиксы после команды меняют её поведение:
| Суффикс | Что означает | Пример | Действие |
|---|---|---|---|
p |
Pop (выгрузить) | faddp |
Выполнить операцию + убрать один элемент |
r |
Reverse (обратный порядок) | fsubr |
Поменять местами операнды |
i |
Integer (целое число) | fild |
Загрузить/сохранить как целое |
| Нет суффикса | Базовая операция | fadd |
Операция без дополнительных действий |
Примеры:
fadd— сложить два числа, оставить оба на стекеfaddp— сложить два числа, убрать одно (с pop)fiadd— сложить с целым числом (с integer)
💡 3. Зачем это нужно?
Проблема целочисленной арифметики
Работая только с целыми числами (int, long), вы сталкиваетесь с ограничениями:
Что можно:
- Точные вычисления с целыми:
42 + 17 = 59✅ - Быстрые операции (сложение за 1 такт)
- Деление с остатком:
10 ÷ 3 = 3 (остаток 1)
Чего НЕЛЬЗЯ:
- Представить $\frac{1}{3}$ точно (получится 0!)
- Вычислить $\sqrt{2}$ (нет инструкции)
- Работать с числами больше $2^{63}$
- Тригонометрия, логарифмы — невозможны
Пример проблемы:
; Попытка разделить 10 на 3
mov rax, 10
mov rbx, 3
xor rdx, rdx
div rbx
; ❌ rax = 3 (дробь .333... потеряна!)
; rdx = 1 (остаток)
Вывод:
Для научных расчётов, 3D графики, физики — целых чисел недостаточно.
Что появляется:
- Дробные числа:
3.14159,0.001,1.5e-10✅ - Огромный диапазон: от $10^{-308}$ до $10^{308}$
- Встроенная математика: sin, cos, log, sqrt
- Стандарт IEEE 754 (как в C/C++/Python)
Те же задачи решаются:
- $\frac{1}{3} = 0.333333…$ (точное представление)
- $\sqrt{2} = 1.414213…$ (есть инструкция
fsqrt) - Можно работать с $10^{100}$ и $10^{-100}$
fsin,fcos— встроены в процессор!
Тот же пример:
; Деление 10 на 3 с FPU
fild dword [ten] ; 10.0
fild dword [three] ; 3.0
fdivp ; 10.0 / 3.0
; ✅ ST(0) = 3.333333... (точно!)
Вывод:
FPU открывает мир научных вычислений.
Реальные сценарии использования
🔬 Научные вычисления
Физика, химия, биология — везде нужны вещественные числа:
Пример: вычисление кинетической энергии
$$E_k = \frac{mv^2}{2}$$
section .data
mass dq 5.0 ; кг
velocity dq 10.0 ; м/с
two dq 2.0
energy dq 0.0
section .text
fld qword [mass] ; m
fld qword [velocity] ; v
fmul st0, st0 ; v²
fmulp ; m × v²
fld qword [two] ; 2
fdivp ; (m × v²) / 2
fstp qword [energy] ; E = 250 Дж🎮 3D графика и игры
Повороты, масштабирование, перспектива — всё на вещественных числах:
Пример: нормализация вектора
Нормализация = приведение длины вектора к 1, сохраняя направление.
$$\vec{v}_{norm} = \frac{\vec{v}}{|\vec{v}|} = \frac{\vec{v}}{\sqrt{x^2 + y^2 + z^2}}$$
; Вычислить длину вектора (x, y, z)
fld qword [x]
fmul st0, st0 ; x²
fld qword [y]
fmul st0, st0 ; y²
faddp ; x² + y²
fld qword [z]
fmul st0, st0 ; z²
faddp ; x² + y² + z²
fsqrt ; √(x² + y² + z²) = длина
; Теперь можно разделить каждую компоненту на длину💰 Финансовые расчёты
Пример: сложный процент
$$A = P \times (1 + r)^n$$
Мы преобразуем формулу для FPU: $$A = P \times 2^{(n \cdot \log_2(1+r))}$$
section .data
principal dq 1000.0 ; Начальная сумма 1000$
rate dq 0.05 ; 5% годовых
years dq 10.0 ; 10 лет
result dq 0.0
section .text
; 1. Подготовим основание (1 + r)
fld1 ; ST0 = 1.0
fld qword [rate] ; ST0 = 0.05, ST1 = 1.0
faddp ; ST0 = 1.05 (Base)
; 2. Загрузим степень (years)
fld qword [years] ; ST0 = 10.0, ST1 = 1.05
; 3. Вычислим показатель степени для двойки: years * log2(Base)
; fyl2x вычисляет: ST1 * log2(ST0) и выталкивает ST0
; Нам нужно: 10.0 * log2(1.05). Сейчас 10.0 в ST0.
fxch ; Меняем местами: ST0 = 1.05, ST1 = 10.0
fyl2x ; ST0 = 10.0 * log2(1.05) ≈ 0.701
; 4. Возведение 2 в степень ST0 (Power = 2^ST0)
; f2xm1 работает только если -1.0 < ST0 < 1.0.
; Поэтому разбиваем число на целую и дробную части: 2^(I+F) = 2^I * 2^F
fld st0 ; Дублируем значение: ST0=0.701, ST1=0.701
frndint ; Округляем до целого: ST0=1.0 (или 0.0)
fsubr st1, st0 ; Вычитаем целое из исх: ST0 = дробная часть (F)
; ST1 = целая часть (I)
f2xm1 ; ST0 = (2^F) - 1
fld1 ; ST0 = 1.0
faddp ; ST0 = 2^F
fscale ; ST0 = ST0 * 2^(ST1(целое)) = 2^F * 2^I
fstp st1 ; Убираем целую часть (I) из стека, она больше не нужна
; Теперь ST0 = (1.05)^10 ≈ 1.62889
; 5. Умножаем на начальную сумму
fld qword [principal] ; ST0 = 1000.0, ST1 = 1.62889
fmulp ; ST0 = 1628.89
fstp qword [result] ; Сохраняем итог в память📚 4. Стек FPU: как это устроено?
Критическое ограничение: только 8 регистров!
FPU имеет всего 8 регистров стека: ST(0) до ST(7)
Это означает:
- ✅ Можете загрузить максимум 8 чисел
- ❌ 9-я операция
fldвызовет переполнение стека и ошибку - 🔑 Поэтому обязательно используйте команды с
p(pop) — они освобождают место!
fld qword [a] ; ST(0) занят (1/8)
fld qword [b] ; ST(0-1) заняты (2/8)
fadd ; ST(0-1) заняты (2/8)
; ❌ Результат остался в стеке!
fld qword [c] ; ST(0-2) заняты (3/8)
fld qword [d] ; ST(0-3) заняты (4/8)
fmul ; ST(0-3) заняты (4/8)
; ❌ Ещё результат!
; После 6-7 таких операций:
; ❌ ПЕРЕПОЛНЕНИЕ! Программа упадёт
Проблема:
Без p результаты накапливаются в стеке, и через 8 операций места не останется.
fld qword [a] ; ST(0) занят (1/8)
fld qword [b] ; ST(0-1) заняты (2/8)
faddp ; ST(0) занят (1/8) ← pop освободил!
fstp qword [r1] ; Стек пуст (0/8) ← сохранили
fld qword [c] ; ST(0) занят (1/8)
fld qword [d] ; ST(0-1) заняты (2/8)
fmulp ; ST(0) занят (1/8) ← pop освободил!
fstp qword [r2] ; Стек пуст (0/8) ← сохранили
; ✅ Можем повторять бесконечно!
Решение:
Команды с p (faddp, fmulp, fstp) выталкивают элементы, освобождая место.
Почему именно p (pop)?
p = Pop = “Вытолкнуть из стека”
Когда вы делаете faddp:
- Складываются ST(0) и ST(1)
- Результат помещается в ST(0)
- Один элемент удаляется (pop!) — стек уменьшается на 1
Без p (команда fadd):
- Складываются ST(0) и ST(1)
- Результат помещается в ST(0)
- ST(1) остаётся! — стек не уменьшается
Пример: faddp (с pop) Пример: fadd (без pop)
До операции: До операции:
┌───────┐ ┌───────┐
│ 3.0 │ ST(0) │ 3.0 │ ST(0)
├───────┤ ├───────┤
│ 5.0 │ ST(1) │ 5.0 │ ST(1)
├───────┤ ├───────┤
│ 7.0 │ ST(2) │ 7.0 │ ST(2)
└───────┘ └───────┘
После faddp: После fadd:
┌───────┐ ┌───────┐
│ 8.0 │ ST(0) ← 3+5 │ 8.0 │ ST(0) ← 3+5
├───────┤ ├───────┤
│ 7.0 │ ST(1) ← было ST(2) │ 5.0 │ ST(1) ← осталось!
└───────┘ ├───────┤
│ 7.0 │ ST(2)
Стек: 2 элемента └───────┘
Стек: 3 элементаВывод: Используйте p после операций, чтобы стек не рос!
Главное отличие от обычных регистров
RAX, RBX, RCX, RDX…
┌──────┐ ┌──────┐ ┌──────┐
│ RAX │ │ RBX │ │ RCX │
│ 42 │ │ 17 │ │ 100 │
└──────┘ └──────┘ └──────┘Принцип работы:
- Как полка с ящиками
- Каждый ящик имеет имя
- Можете взять любой в любой момент
- Нет ограничения на количество операций
Доступ:
mov rax, 42 ; Записать в RAX
mov rbx, rax ; Скопировать из RAX в RBX
add rcx, rax ; Сложить RAX и RCX
; Можем делать бесконечно!
ST(0), ST(1), ST(2)… ST(7)
┌──────────┐
│ ST(0) │ ← Вершина
├──────────┤
│ ST(1) │
├──────────┤
│ ST(2) │
├──────────┤
│ ... │
├──────────┤
│ ST(7) │ ← Дно (последний!)
└──────────┘Принцип работы:
- Как стопка из 8 тарелок
- Можете взять только ВЕРХНЮЮ
- Максимум 8 элементов одновременно!
- Нужно освобождать место через
p
Доступ:
fld qword [x] ; +1 элемент в стеке
fadd ; Операция (элементов столько же)
fstp qword [y] ; -1 элемент (освободили!)
⚙️ 5. Базовые инструкции FPU
1. Загрузка и Сохранение
Загрузка вещественных чисел (fld)
; fld = Floating Load (загрузить)
fld dword [float32] ; 32-бит (float)
fld qword [float64] ; 64-бит (double)
fld tword [float80] ; 80-бит (extended)
; Все они КЛАДУТ число на вершину стека; Специальные команды для констант
fld1 ; Загрузить 1.0
fldz ; Загрузить 0.0
fldpi ; Загрузить π (3.14159...)
fldl2e ; Загрузить log₂(e) ≈ 1.442695
fldl2t ; Загрузить log₂(10) ≈ 3.321928Загрузка целых чисел (fild)
Обратите внимание на букву i — она означает Integer!
; fild = Floating Integer Load
; Загружает целое и конвертирует в float
section .data
age dd 42 ; Целое 32-бит
section .text
fild dword [age] ; Загрузит как 42.0
; ST(0) = 42.0 (теперь это float!)section .data
x_float dd 42.0 ; Уже float
x_int dd 42 ; Целое
section .text
; ПРАВИЛЬНО:
fld dword [x_float] ; ✅ Загрузить float
fild dword [x_int] ; ✅ Загрузить int → float
; НЕПРАВИЛЬНО:
fld dword [x_int] ; ❌ Попытка прочитать
; int как float!Сохранение в память
; fstp = Floating STore and Pop
; Сохраняет ST(0) в память и УБИРАЕТ из стека
fstp dword [result32] ; → float (32 бит)
fstp qword [result64] ; → double (64 бит)
fstp tword [result80] ; → extended (80 бит)
; ⚠️ После fstp элемент ИСЧЕЗАЕТ из стека!; fst = Floating STore (без Pop!)
; Сохраняет ST(0), но ОСТАВЛЯЕТ в стеке
fst qword [backup] ; Скопировать ST(0)
; ST(0) всё ещё на месте!
; Полезно для промежуточных результатов:
fld qword [x]
fmul st0, st0 ; x²
fst qword [x_squared] ; Сохранить x²
fsqrt ; √(x²) = x
; Теперь есть и x², и xСохранение целых чисел (fistp)
; fistp = Floating Integer STore and Pop
; Конвертирует float → int и сохраняет
section .data
result dd 0
section .text
fld qword [pi] ; ST(0) = 3.14159...
fistp dword [result] ; [result] = 3
; ⚠️ Дробная часть отбрасывается!
; (с округлением, см. режимы FPU); По умолчанию: округление к ближайшему
fld qword [value] ; 42.7
fistp dword [res] ; res = 43 (ближайшее)
fld qword [value] ; 42.3
fistp dword [res] ; res = 42 (ближайшее)
fld qword [value] ; 42.5
fistp dword [res] ; res = 42 (к чётному!)
; Режим можно изменить (см. раздел про
; Control Word)2. Арифметические операции
Сложение и вычитание
; fadd — сложить два верхних элемента
fld qword [a] ; ST(0) = 10
fld qword [b] ; ST(0) = 20, ST(1) = 10
fadd ; ST(0) = 30 (20+10)
; ST(1) всё ещё 10!
; Или с выгрузкой:
fld qword [a]
fld qword [b]
faddp ; ST(0) = 30, ST(1) убран!
; Или сразу из памяти:
fld qword [a]
fadd qword [b] ; ST(0) = a + b; fsub — вычесть из нижнего верхнее
fld qword [a] ; ST(0) = 10
fld qword [b] ; ST(0) = 3, ST(1) = 10
fsubp ; ST(0) = ST(1) - ST(0)
; ST(0) = 10 - 3 = 7 ✅
; ⚠️ ПОРЯДОК ВАЖЕН!
; fsub вычитает ST(0) из ST(1)
; Обратный порядок:
fld qword [a] ; 10
fld qword [b] ; 3
fsubrp ; ST(0) = ST(0) - ST(1)
; ST(0) = 3 - 10 = -7Порядок операндов — главная ловушка!
╔══════════════════════════════════════════════════════════════════════╗
║ ПРАВИЛО ЗАПОМИНАНИЯ ПОРЯДКА В ВЫЧИТАНИИ/ДЕЛЕНИИ ║
╚══════════════════════════════════════════════════════════════════════╝
БЕЗ 'r': нижнее МИНУС верхнее (ST(1) - ST(0))
С 'r': верхнее МИНУС нижнее (ST(0) - ST(1))
Пример: хотим вычислить 10 - 3 = 7
+----------------------------------+----------------------------------+
| Вариант 1: fsub (без 'r') | Вариант 2: fsubr (с 'r') |
+----------------------------------+----------------------------------+
| fld qword [ten] ; ST(0)=10 | fld qword [three] ; ST(0)=3 |
| fld qword [three] ; ST(0)=3 | fld qword [ten] ; ST(0)=10 |
| ST(1)=10 | ST(1)=3 |
| fsubp ; ST(1)-ST(0)=10-3=7 | fsubrp ; ST(0)-ST(1)=10-3=7 |
| ✅ Правильно! | ✅ Правильно! |
+----------------------------------+----------------------------------+
💡 Совет: загружайте числа в том порядке, который нужен для fsub,
тогда не понадобится fsubr!Умножение и деление
; fmul — умножить два верхних элемента
fld qword [a] ; ST(0) = 5
fld qword [b] ; ST(0) = 3, ST(1) = 5
fmulp ; ST(0) = 15 (5×3)
; Умножение коммутативно, порядок не важен!
; 5×3 = 3×5 ✅
; На самого себя (возведение в квадрат):
fld qword [x]
fmul st0, st0 ; ST(0) = x²; fdiv — разделить нижнее на верхнее
fld qword [a] ; ST(0) = 10
fld qword [b] ; ST(0) = 2, ST(1) = 10
fdivp ; ST(0) = ST(1) / ST(0)
; ST(0) = 10 / 2 = 5 ✅
; ⚠️ Порядок важен! (как в вычитании)
; fdiv делит ST(1) на ST(0)
; Обратное деление:
fdivrp ; ST(0) = ST(0) / ST(1)3. Специальные математические операции
; Квадратный корень
fld qword [x]
fsqrt ; ST(0) = √x
; Квадрат (умножить на себя)
fld qword [x]
fmul st0, st0 ; ST(0) = x²
; Куб (x³)
fld qword [x]
fld st0 ; Копия
fmul st0, st0 ; x²
fmulp ; x² × x = x³
; Четвёртая степень (x⁴)
fld qword [x]
fmul st0, st0 ; x²
fmul st0, st0 ; (x²)² = x⁴; Абсолютное значение
fld qword [x]
fabs ; ST(0) = |x|
; Смена знака
fld qword [x]
fchs ; ST(0) = -x
; Округление до целого
fld qword [x] ; x = 3.7
frndint ; ST(0) = 4.0
; Извлечение целой и дробной части
fld qword [x] ; x = 3.7
fld st0 ; Копия
frndint ; ST(0) = 4.0 (целое)
fsub st1, st0 ; ST(1) = 0.7 (дробное)4. Трансцендентные функции
Это главное преимущество FPU перед SSE/AVX — встроенные сложные функции:
; ⚠️ Все углы в РАДИАНАХ!
; Синус
fld qword [angle] ; angle в радианах
fsin ; ST(0) = sin(angle)
; Косинус
fld qword [angle]
fcos ; ST(0) = cos(angle)
; Синус И косинус одновременно
fld qword [angle]
fsincos ; ST(0) = cos, ST(1) = sin
; Быстрее, чем два вызова!
; Тангенс (частичный)
fld qword [angle]
fptan ; ST(0) = tan(angle)
; ST(1) = 1.0 (доп. значение); Логарифм по основанию 2
fld qword [x]
fld1
fxch
fyl2x ; ST(0) = log₂(x)
; Натуральный логарифм ln(x)
fld qword [x]
fldln2 ; log₂(e)
fxch
fyl2x ; log₂(x)
; Теперь нужно разделить...
; (сложно, см. примеры ниже)
; Экспонента 2^x - 1
fld qword [x]
f2xm1 ; ST(0) = 2^x - 1
fld1
faddp ; ST(0) = 2^x
; ⚠️ f2xm1 точна только для |x| ≤ 1🤔 Почему fptan возвращает два значения?
Из-за исторических причин fptan помещает на стек ДВА числа:
- ST(0) = tan(x) — собственно тангенс
- ST(1) = 1.0 — дополнительное значение для совместимости
Обычно единицу просто удаляют:
fld qword [angle]
fptan ; ST(0) = tan, ST(1) = 1.0
fstp st1 ; Убрать единицу
; ST(0) = tan (нужный результат)💡 Как перевести градусы в радианы?
Формула: $\text{radians} = \text{degrees} \times \frac{\pi}{180}$
section .data
degrees dq 45.0 ; 45 градусов
pi_over_180 dq 0.017453292519943295 ; π/180
radians dq 0.0
section .text
fld qword [degrees]
fld qword [pi_over_180]
fmulp
fstp qword [radians] ; radians ≈ 0.785 (π/4)
; Теперь можно использовать в fsin/fcosИли с использованием константы fldpi:
fld qword [degrees] ; 45
fldpi ; π
fmulp ; 45π
fld qword [c_180] ; 180
fdivp ; 45π/180 = π/4
fsin ; sin(π/4) ≈ 0.707📝 6. Практические задачи (от простых к сложным)
Задача 1: Вычислить среднее арифметическое
Формула: $\text{average} = \frac{a + b + c}{3}$
section .data
a dq 10.0
b dq 20.0
c dq 30.0
three dq 3.0
average dq 0.0
section .text
global _start
_start:
; Сложить три числа
fld qword [a] ; ST(0) = 10
fld qword [b] ; ST(0) = 20, ST(1) = 10
faddp ; ST(0) = 30
fld qword [c] ; ST(0) = 30, ST(1) = 30
faddp ; ST(0) = 60
; Разделить на 3
fld qword [three] ; ST(0) = 3, ST(1) = 60
fdivp ; ST(0) = 60/3 = 20
fstp qword [average]
; Выход
mov rax, 60
xor rdi, rdi
syscallsection .data
a dq 10.0
b dq 20.0
c dq 30.0
three dq 3.0
average dq 0.0
section .text
global _start
_start:
fld qword [a]
fadd qword [b] ; Сразу из памяти
fadd qword [c]
fld qword [three]
fdivp
fstp qword [average]
mov rax, 60
xor rdi, rdi
syscallСостояние стека на каждом шаге
После fld [a]: │ 10 │
└─────┘
После fadd [b]: │ 30 │ (10 + 20)
└─────┘
После fadd [c]: │ 60 │ (30 + 30)
└─────┘
После fld [three]: │ 3 │
│ 60 │
└─────┘
После fdivp: │ 20 │ (60 / 3)
└─────┘Задача 2: Формула расстояния между точками
Формула: $d = \sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2}$
section .data
x1 dq 1.0
y1 dq 2.0
x2 dq 4.0
y2 dq 6.0
distance dq 0.0
section .text
global _start
_start:
; Вычислить (x2 - x1)²
fld qword [x2] ; ST(0) = 4
fld qword [x1] ; ST(0) = 1, ST(1) = 4
fsubp ; ST(0) = 3 (4-1)
fmul st0, st0 ; ST(0) = 9 (3²)
; Вычислить (y2 - y1)²
fld qword [y2] ; ST(0) = 6, ST(1) = 9
fld qword [y1] ; ST(0) = 2, ST(1) = 6, ST(2) = 9
fsubp ; ST(0) = 4, ST(1) = 9
fmul st0, st0 ; ST(0) = 16 (4²)
; Сложить и извлечь корень
faddp ; ST(0) = 25 (9+16)
fsqrt ; ST(0) = 5
fstp qword [distance]
mov rax, 60
xor rdi, rdi
syscall
Результат: расстояние между (1,2) и (4,6) равно 5.0
Задача 3: Конвертация температуры (Цельсий → Фаренгейт)
Формула: $F = C \times \frac{9}{5} + 32$
section .data
celsius dq 25.0
nine dq 9.0
five dq 5.0
thirtytwo dq 32.0
fahrenheit dq 0.0
section .text
global _start
_start:
; C × 9/5
fld qword [celsius] ; 25
fld qword [nine] ; 9
fmulp ; 225
fld qword [five] ; 5
fdivp ; 45
; + 32
fld qword [thirtytwo] ; 32
faddp ; 77
fstp qword [fahrenheit]
mov rax, 60
xor rdi, rdi
syscallsection .data
celsius dq 25.0
coeff dq 1.8 ; 9/5 = 1.8
thirtytwo dq 32.0
fahrenheit dq 0.0
section .text
global _start
_start:
fld qword [celsius] ; 25
fld qword [coeff] ; 1.8
fmulp ; 45
fld qword [thirtytwo] ; 32
faddp ; 77
fstp qword [fahrenheit]
mov rax, 60
xor rdi, rdi
syscallРезультат: 25°C = 77°F ✅
Задача 4: Площадь треугольника по формуле Герона
Формула: $$p = \frac{a + b + c}{2}$$ $$S = \sqrt{p(p-a)(p-b)(p-c)}$$
section .data
a dq 3.0 ; Сторона a
b dq 4.0 ; Сторона b
c dq 5.0 ; Сторона c (прямоугольный треугольник!)
two dq 2.0
area dq 0.0
section .text
global _start
_start:
; Вычислить полупериметр p = (a+b+c)/2
fld qword [a]
fadd qword [b]
fadd qword [c] ; ST(0) = 12 (a+b+c)
fld qword [two]
fdivp ; ST(0) = 6 (полупериметр p)
; Теперь нужно вычислить p(p-a)(p-b)(p-c)
; Сохраним s для дальнейшего использования
fld st0 ; ST(0) = s, ST(1) = p (дубликат)
; s - a
fld qword [a] ; ST(0) = 3, ST(1) = 6, ST(2) = 6
fsubp ; ST(0) = 3 (6-3), ST(1) = 6
; Умножить на s
fmul st0, st1 ; ST(0) = 18 (6×3), ST(1) = 6
; s - b
fld st1 ; ST(0) = 6, ST(1) = 18, ST(2) = 6
fld qword [b] ; ST(0) = 4, ST(1) = 6, ST(2) = 18
fsubp ; ST(0) = 2, ST(1) = 18, ST(2) = 6
fmulp ; ST(0) = 36 (18×2), ST(1) = 6
; s - c
fld st1 ; ST(0) = 6, ST(1) = 36
fld qword [c] ; ST(0) = 5, ST(1) = 6, ST(2) = 36
fsubp ; ST(0) = 1, ST(1) = 36
fmulp ; ST(0) = 36 (36×1)
; Извлечь корень
fsqrt ; ST(0) = 6.0
fstp qword [area]
mov rax, 60
xor rdi, rdi
syscall
Результат: площадь треугольника 3-4-5 равна 6.0 (для проверки: $\frac{3 \times 4}{2} = 6$ ✅)
Задача 5: Вычислить синус угла 30°
Важно: FPU работает только с радианами! $30° = \frac{\pi}{6}$ радиан.
section .data
degrees dq 30.0
pi_over_180 dq 0.017453292519943295
result dq 0.0
section .text
global _start
_start:
; Градусы → Радианы
fld qword [degrees] ; 30
fld qword [pi_over_180] ; π/180
fmulp ; 30π/180 = π/6
; Вычислить синус
fsin ; sin(π/6)
fstp qword [result] ; ≈ 0.5
mov rax, 60
xor rdi, rdi
syscallsection .data
degrees dq 30.0
c_180 dq 180.0
result dq 0.0
section .text
global _start
_start:
; Вычислить радианы: deg×π/180
fld qword [degrees] ; 30
fldpi ; π
fmulp ; 30π
fld qword [c_180] ; 180
fdivp ; 30π/180 = π/6
fsin ; sin(π/6) ≈ 0.5
fstp qword [result]
mov rax, 60
xor rdi, rdi
syscallРезультат: sin(30°) = 0.5 ✅
📐 Таблица известных значений для проверки
| Угол | Радианы | sin | cos | tan |
|---|---|---|---|---|
| 0° | 0 | 0 | 1 | 0 |
| 30° | π/6 | 0.5 | √3/2 ≈ 0.866 | √3/3 ≈ 0.577 |
| 45° | π/4 | √2/2 ≈ 0.707 | √2/2 ≈ 0.707 | 1 |
| 60° | π/3 | √3/2 ≈ 0.866 | 0.5 | √3 ≈ 1.732 |
| 90° | π/2 | 1 | 0 | ∞ |
Задача 6: Работа с массивами — сумма элементов
section .data
array dq 1.5, 2.7, 3.2, 4.8, 5.1 ; 5 элементов
count equ 5
sum dq 0.0
section .text
global _start
_start:
fldz ; ST(0) = 0.0 (аккумулятор)
mov rcx, count ; Счётчик цикла
lea rsi, [rel array] ; Указатель на массив
loop_start:
fadd qword [rsi] ; ST(0) += текущий элемент
add rsi, 8 ; Следующий элемент (+8 байт)
loop loop_start ; Декремент RCX, повтор если != 0
fstp qword [sum] ; Сохранить сумму
mov rax, 60
xor rdi, rdi
syscall
Результат: 1.5 + 2.7 + 3.2 + 4.8 + 5.1 = 17.3
💡 Оптимизация: раскрутка цикла
Для небольших массивов быстрее развернуть цикл:
_start:
fld qword [array+0] ; Первый элемент
fadd qword [array+8] ; +второй
fadd qword [array+16] ; +третий
fadd qword [array+24] ; +четвёртый
fadd qword [array+32] ; +пятый
fstp qword [sum]
Это быстрее, потому что:
- Нет накладных расходов на цикл
- Процессор может выполнять инструкции параллельно
- Нет зависимости от счётчика RCX
Задача 7: Квадратное уравнение (дискриминант и корни)
Формула: $ax^2 + bx + c = 0$
$$D = b^2 - 4ac$$ $$x_{1,2} = \frac{-b \pm \sqrt{D}}{2a}$$
section .data
a dq 1.0 ; Коэффициент a
b dq -5.0 ; Коэффициент b
c dq 6.0 ; Коэффициент c
two dq 2.0
four dq 4.0
discriminant dq 0.0
x1 dq 0.0
x2 dq 0.0
section .text
global _start
_start:
; Вычислить дискриминант D = b² - 4ac
fld qword [b] ; b
fmul st0, st0 ; b²
fld qword [four] ; 4
fld qword [a] ; a
fmulp ; 4a
fld qword [c] ; c
fmulp ; 4ac
fsubp ; b² - 4ac = D
fld st0 ; Копия D для x2
fstp qword [discriminant]
; Вычислить √D
fsqrt ; √D
; Вычислить x1 = (-b + √D) / 2a
fld qword [b] ; b
fchs ; -b
fld st1 ; √D
faddp ; -b + √D
fld qword [two] ; 2
fld qword [a] ; a
fmulp ; 2a
fdivp ; (-b + √D) / 2a
fstp qword [x1]
; Вычислить x2 = (-b - √D) / 2a
fld qword [b] ; b
fchs ; -b
fxch ; Поменять с √D
fsubp ; -b - √D
fld qword [two] ; 2
fld qword [a] ; a
fmulp ; 2a
fdivp ; (-b - √D) / 2a
fstp qword [x2]
mov rax, 60
xor rdi, rdi
syscall
Для уравнения $x^2 - 5x + 6 = 0$:
- D = 25 - 24 = 1
- x₁ = (5 + 1) / 2 = 3
- x₂ = (5 - 1) / 2 = 2
Проверка: (x-2)(x-3) = x² - 5x + 6 ✅
Задача 8: Вычисление факториала (через цикл)
Задача: вычислить $5! = 1 \times 2 \times 3 \times 4 \times 5 = 120$
section .data
n dd 5 ; Вычислить 5!
result dq 0.0
section .bss
temp resd 1 ; Временная переменная
section .text
global _start
_start:
mov ecx, [n] ; Счётчик = 5
fld1 ; ST(0) = 1.0 (аккумулятор)
factorial_loop:
; Умножить на текущий счётчик
push rcx ; Сохранить RCX
mov dword [temp], ecx ; Конвертировать в память
fild dword [temp] ; Загрузить как float
fmulp ; Умножить
pop rcx ; Восстановить RCX
loop factorial_loop ; Декремент, повтор если != 0
fstp qword [result] ; Сохранить результат
mov rax, 60
xor rdi, rdi
syscall
Результат: 5! = 120.0
Задача 9: Приближённое вычисление π (формула Лейбница)
Формула: $\frac{\pi}{4} = 1 - \frac{1}{3} + \frac{1}{5} - \frac{1}{7} + \frac{1}{9} - …$
section .data
iterations dd 1000 ; Количество итераций
four dq 4.0
pi_approx dq 0.0
section .bss
temp_denom resd 1
section .text
global _start
_start:
fldz ; Сумма = 0
mov ecx, [iterations]
mov eax, 1 ; Знаменатель (1, 3, 5, 7...)
mov ebx, 1 ; Знак (1, -1, 1, -1...)
leibniz_loop:
; Вычислить текущий член: sign / denominator
push rax
mov dword [temp_denom], eax
fild dword [temp_denom] ; Знаменатель
fld1 ; 1
fdivrp ; 1 / знаменатель
pop rax
; Применить знак
test ebx, ebx
jns add_term
fchs ; Сменить знак
add_term:
faddp ; Добавить к сумме
; Следующая итерация
add eax, 2 ; 1→3→5→7...
neg ebx ; Сменить знак
loop leibniz_loop
; Умножить на 4
fld qword [four]
fmulp
fstp qword [pi_approx]
mov rax, 60
xor rdi, rdi
syscall
Результат: После 1000 итераций π ≈ 3.140… (точность улучшается с увеличением итераций)
📊 Сравнение точности
| Итераций | Результат | Ошибка |
|---|---|---|
| 10 | 3.04183… | ~0.1 |
| 100 | 3.13159… | ~0.01 |
| 1000 | 3.14059… | ~0.001 |
| 10000 | 3.14149… | ~0.0001 |
Реальный π = 3.14159265358979…
⚠️ Формула Лейбница сходится медленно! Для быстрого вычисления π используйте другие методы (например, Мачина или Чудновского).
🛠️ 7. Распространенные ошибки и решения
Ошибки в FPU-коде часто связаны с неверным управлением стеком. Здесь мы рассмотрим специфичные для FPU проблемы, а более общие ловушки описаны в статье «Топ ошибок в NASM: Почему падает Segfault и неверные расчёты». Для отладки таких проблем полезно использовать GDB, как описано в руководстве по отладке в VS Code.
Ошибка 1: Переполнение стека
; Забыли выгрузить результаты!
fld qword [a]
fld qword [b]
faddp ; Результат в ST(0)
; ❌ Не сохранили!
fld qword [c] ; Ещё одно число
fld qword [d]
fmulp
; ❌ Снова не сохранили!
; После 8 таких операций:
; ❌ ПЕРЕПОЛНЕНИЕ СТЕКА!
Проблема:
Стек FPU имеет только 8 регистров (ST0-ST7). Если не выгружать результаты, стек переполнится.
; Всегда выгружайте результаты!
fld qword [a]
fld qword [b]
faddp
fstp qword [result1] ; ✅ Сохранили!
fld qword [c]
fld qword [d]
fmulp
fstp qword [result2] ; ✅ Сохранили!
; Стек чист, можно продолжать
Правило:
После каждого вычисления сохраняйте результат через fstp, если он больше не нужен в стеке.
Ошибка 2: Путаница с порядком операндов
; Хотим: 10 - 3 = 7
fld qword [ten] ; ST(0) = 10
fld qword [three] ; ST(0) = 3, ST(1) = 10
fsubp ; ST(0) = ???
; Результат: ST(0) = 7 ✅
; НО! Порядок загрузки сбивает с толку
Путаница:
fsubp делает ST(1) - ST(0), что правильно, но не интуитивно.
; Способ 1: Загружать в нужном порядке
fld qword [ten] ; ST(0) = 10
fld qword [three] ; ST(0) = 3, ST(1) = 10
fsubp ; ST(0) = 10-3 = 7 ✅
; Способ 2: Использовать fsub напрямую
fld qword [ten] ; ST(0) = 10
fsub qword [three] ; ST(0) = 10-3 = 7 ✅
; (вычитает из ST(0))
; Способ 3: Обратная операция
fld qword [three] ; ST(0) = 3
fld qword [ten] ; ST(0) = 10, ST(1) = 3
fsubrp ; ST(0) = 10-3 = 7 ✅
╔═══════════════════════════════════════════════════════════════╗
║ ШПАРГАЛКА: ПОРЯДОК ОПЕРАНДОВ ║
╚═══════════════════════════════════════════════════════════════╝
Операция Что делает Результат
─────────────────────────────────────────────────────────────────
faddp ST(1) + ST(0) Порядок не важен
fmulp ST(1) × ST(0) Порядок не важен
fsubp ST(1) - ST(0) ⚠️ Нижнее МИНУС верхнее
fsubrp ST(0) - ST(1) ⚠️ Верхнее МИНУС нижнее
fdivp ST(1) / ST(0) ⚠️ Нижнее ДЕЛИТЬ НА верхнее
fdivrp ST(0) / ST(1) ⚠️ Верхнее ДЕЛИТЬ НА нижнее
💡 Мнемоника 'r' = reverse (обратный порядок)Ошибка 3: Смешивание типов данных
section .data
x dd 42 ; Это целое int!
section .text
fld dword [x] ; ❌ ОШИБКА!
; Попытка загрузить int как float
; Получится мусор!
Проблема:
fld ожидает вещественное число, но получает целое. Биты интерпретируются неправильно.
section .data
x_int dd 42 ; Целое
x_float dd 42.0 ; Вещественное
section .text
; Для целых используйте fild
fild dword [x_int] ; ✅ Загрузка int
; ST(0) = 42.0 (конвертировано)
; Для вещественных используйте fld
fld dword [x_float] ; ✅ Загрузка float
; ST(0) = 42.0
Правило:
fld/fst/fstp— для вещественных чиселfild/fist/fistp— для целых чисел (с автоматической конвертацией)
Ошибка 4: Забыли инициализировать FPU
global _start
_start:
; ❌ Не инициализировали FPU!
fld qword [x]
; Стек может содержать мусор
fadd qword [y]
; Непредсказуемый результат!
Проблема:
После запуска программы состояние FPU неопределено. В стеке может быть мусор.
global _start
_start:
finit ; ✅ Инициализация FPU
; Теперь:
; - Стек пуст
; - Режимы по умолчанию
; - Флаги исключений очищены
fld qword [x]
fadd qword [y]
; Гарантированно правильный результат
Правило:
Всегда начинайте с finit для очистки состояния FPU.
Ошибка 5: Неправильная работа с углами
section .data
angle dq 90.0 ; 90 градусов
section .text
fld qword [angle]
fsin ; ❌ ОШИБКА!
; sin(90 радиан) ≈ 0.894
; Ожидали: sin(90°) = 1.0
Проблема:
FPU принимает углы только в радианах. 90 градусов ≠ 90 радиан!
section .data
angle_deg dq 90.0
pi_over_180 dq 0.017453292519943295
section .text
; Градусы → Радианы
fld qword [angle_deg] ; 90
fld qword [pi_over_180] ; π/180
fmulp ; 90π/180 = π/2
fsin ; ✅ sin(π/2) = 1.0
Формула конвертации:
$$\text{radians} = \text{degrees} \times \frac{\pi}{180}$$
🐞 8. Отладка FPU-кода
Полезные техники
Проверка статуса FPU
; Сохранить Status Word в AX
fstsw ax
; Проверить флаги исключений (биты 0-5)
test ah, 0x01 ; Invalid Operation
test ah, 0x02 ; Denormalized Operand
test ah, 0x04 ; Division by Zero
test ah, 0x08 ; Overflow
test ah, 0x10 ; Underflow
test ah, 0x20 ; Precision
; Пример: проверка деления на ноль
fld qword [a]
fld qword [b]
fdivp
fstsw ax
test ah, 0x04
jnz division_by_zero_handlerИспользование GDB для отладки
# Скомпилировать с отладочной информацией
nasm -f elf64 -g -F dwarf program.asm
ld -o program program.o
# Запустить в GDB
gdb ./program
# Полезные команды в GDB:
(gdb) info float # Показать все регистры FPU
(gdb) print $st0 # Значение ST(0)
(gdb) print $st1 # Значение ST(1)
(gdb) info registers fctrl # Control Word
(gdb) info registers fstat # Status Word
(gdb) x/8xb &value # Посмотреть байты float в памяти
# Установить breakpoint перед FPU операцией
(gdb) break *_start+10
(gdb) run
(gdb) info float # Проверить состояниеМакрос для печати стека (отладочный)
; ВНИМАНИЕ: Это изменяет стек! Только для отладки!
%macro DEBUG_PRINT_STACK 0
; Сохранить все 8 регистров в память
sub rsp, 80
mov rbx, rsp
; Выгрузить все элементы
fstp qword [rbx+0]
fstp qword [rbx+8]
fstp qword [rbx+16]
fstp qword [rbx+24]
fstp qword [rbx+32]
fstp qword [rbx+40]
fstp qword [rbx+48]
fstp qword [rbx+56]
; Здесь можно вывести значения через syscall
; (для простоты опущено)
; Восстановить стек в обратном порядке
fld qword [rbx+56]
fld qword [rbx+48]
fld qword [rbx+40]
fld qword [rbx+32]
fld qword [rbx+24]
fld qword [rbx+16]
fld qword [rbx+8]
fld qword [rbx+0]
add rsp, 80
%endmacro
⚠️ Этот макрос разрушает и восстанавливает стек. Используйте только в режиме отладки!
Интерпретация Status Word
Регистр Status Word (16 бит) содержит информацию о состоянии FPU:
Биты Status Word (после fstsw ax):
15 14 13-11 10-8 7 6 5 4 3 2 1 0
B C3 TOP C2-C0 ES SF PE UE OE ZE DE IE
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │ └─ Invalid Operation
│ │ │ │ │ │ │ │ │ │ └───── Denormal
│ │ │ │ │ │ │ │ │ └───────── Zero Divide
│ │ │ │ │ │ │ │ └───────────── Overflow
│ │ │ │ │ │ │ └───────────────── Underflow
│ │ │ │ │ │ └───────────────────── Precision
│ │ │ │ │ └───────────────────────── Stack Fault
│ │ │ │ └───────────────────────────── Exception Summary
│ │ │ └──────────────────────────────────── Condition codes
│ │ └─────────────────────────────────────────── Top of stack pointer
│ └─────────────────────────────────────────────── Condition code C3
└─────────────────────────────────────────────────── Busy flagПример проверки:
fstsw ax ; Загрузить Status Word
sahf ; Скопировать AH в флаги процессора
jz result_zero ; Переход если результат = 0
jc result_less ; Переход если результат < 0⚡ 9. Оптимизация производительности
Таблица латентностей инструкций
| Инструкция | Латентность (циклы) | Throughput | Примечание |
|---|---|---|---|
fld |
1-3 | 1/цикл | Зависит от кэша |
fst / fstp |
1-3 | 1/цикл | |
fadd / fsub |
3-5 | 1/цикл | |
fmul |
5-7 | 2/цикл | |
fdiv |
20-45 | 20/инстр | ⚠️ Очень медленно! |
fsqrt |
15-30 | 15/инстр | |
fsin / fcos |
50-120 | 50/инстр | ⚠️ Избегайте в циклах |
fpatan |
30-80 | 30/инстр | |
fyl2x |
30-60 | 30/инстр |
Латентность = сколько тактов проходит от начала до завершения
Throughput = как часто можно запускать новую инструкцию
Советы по оптимизации
; Избыточные загрузки/выгрузки
loop_start:
fld qword [array + rsi]
fstp qword [temp]
fld qword [temp]
fadd qword [sum]
fstp qword [sum]
add rsi, 8
loop loop_start
; Много обращений к памяти!
Проблемы:
- Ненужные
fstp/fldпары - Каждая итерация обращается к памяти 4 раза
- Стек постоянно опустошается
; Держим аккумулятор в стеке
fldz ; sum = 0 (остаётся в ST0)
loop_start:
fadd qword [array + rsi] ; Прямо в ST(0)
add rsi, 8
loop loop_start
fstp qword [sum] ; Один раз в конце
; Минимум обращений к памяти!
Улучшения:
- Аккумулятор живёт в ST(0)
- Одно обращение к памяти на итерацию
- Нет лишних операций со стеком
Избегайте деления — используйте умножение
; Деление в цикле (медленно!)
loop_start:
fld qword [array + rsi]
fld qword [divisor] ; 5.0
fdivp ; 20-45 тактов!
fstp qword [result + rsi]
add rsi, 8
loop loop_start
Проблема:
fdiv выполняется 20-45 тактов. В цикле на 1000 элементов = 20000-45000 тактов!
; Предвычислить обратное значение
fld1
fld qword [divisor] ; 5.0
fdivp ; 1/5 = 0.2 (один раз!)
fstp qword [inv_divisor]
; Теперь умножаем в цикле
loop_start:
fld qword [array + rsi]
fmul qword [inv_divisor] ; 5-7 тактов
fstp qword [result + rsi]
add rsi, 8
loop loop_start
Выигрыш:
5-7 тактов вместо 20-45. В 4-6 раз быстрее!
Раскрутка циклов
; 5 итераций с накладными расходами
mov rcx, 5
loop_start:
fld qword [array + rsi]
fadd st0, st1
fstp qword [array + rsi]
add rsi, 8
loop loop_start
Накладные расходы на каждой итерации:
- Декремент RCX
- Проверка условия
- Переход (может быть предсказан неверно)
; Развернули цикл
fld qword [array+0]
fadd st0, st1
fstp qword [array+0]
fld qword [array+8]
fadd st0, st1
fstp qword [array+8]
fld qword [array+16]
fadd st0, st1
fstp qword [array+16]
fld qword [array+24]
fadd st0, st1
fstp qword [array+24]
fld qword [array+32]
fadd st0, st1
fstp qword [array+32]
Преимущества:
- Нет накладных расходов цикла
- Процессор может выполнять параллельно
- Предсказание переходов не нужно
⚠️ Внимание: Раскрутка увеличивает размер кода. Используйте для малых фиксированных размеров.
🔭 10. Взгляд в будущее: SSE и AVX
Хотя FPU (x87) отлично подходит для обучения и сложных тригонометрических вычислений, в современном программировании под x86-64 стандартом де-факто являются SSE (Streaming SIMD Extensions) и AVX.
Почему FPU считается устаревшим?
- Стековая модель: Неудобно управлять регистрами (нужно постоянно делать
fxch,fstp). - Скорость: FPU работает с одним числом за раз.
- Совместимость: 64-битные компиляторы C/C++ (GCC, Clang) по умолчанию используют SSE регистры (
xmm0-xmm15) даже для одиночных вычисленийfloatиdouble.
Сравнение подходов
; Сложение двух чисел
fld qword [a] ; Загрузить a в стек
fadd qword [b] ; Добавить b
fstp qword [res] ; Сохранить и очистить стек; Сложение двух чисел
movsd xmm0, [a] ; Загрузить a в регистр xmm0
addsd xmm0, [b] ; Добавить b в xmm0
movsd [res], xmm0 ; Сохранить результатКогда переходить на SSE?
Как только вы освоите работу с памятью и базовую логику ассемблера. SSE позволяет складывать по 4 (float) или 2 (double) числа одной инструкцией, что критически важно для обработки аудио, видео и игровой физики.
📦 Готовое решение: Чтобы не писать сложный код ввода/вывода
floatвручную, используйте готовый модульio_float.asm, описанный в статье «Ввод-вывод на чистом NASM: Готовые модули (без libc)».
✅ Заключение
В этой статье вы изучили FPU от основ стековой архитектуры до практического применения. Теперь вы умеете работать с вещественными числами, использовать трансцендентные функции и оптимизировать код.
FPU используйте для: трансцендентной математики (sin, cos, log), максимальной точности (80 бит), одиночных сложных вычислений.
SSE/AVX лучше для: массовой обработки данных, простой арифметики, современных приложений с параллелизмом.