25 Jul 2026
На первый взгляд, это просто склеенные в одну операции сложения и умножения.
fma(a, b, c) = a * b + c
Но самая важная фишка - в точности вычисления. Результат вот этого промежуточного умножения считается с удвоенной точностью (для double будет мантисса аж за сотню бит), потом прибавляется c и только в конце один раз округляется результат.
Например, есть подход double-double арифметики, когда мы храним пару чисел (high, low), в верхнем лежит число и в нижнем - поправка. FMA позволяет это сделать в пару шагов:
val high = a * b
val low = fma(a, b, -high)
В high лежит обычный округлённый результат умножения, и в fma считается разница между точным результатом умножения и округлённым.
Примечание: этот приём может не работать на денормализованных числах и при переполнениях.
Раньше без fma использовали алгоритм “Veltkamp-Dekker splitting”. Статьи на википедии нет, но зато я нашёл саму оригинальную статью Деккера, можно посмотреть и порадоваться тому, насколько всё проще с FMA и насколько меньше операций с числами надо делать.
Ещё пример: хотим посчитать a * b - c * d. У нас теряется точность, потому что мы вычитаем два больших числа друг из друга.
Можно использовать что-то похожее на алгоритм Кэхэна:
def diffOfProducts(a: Double, b: Double, c: Double, d: Double): Double =
val cd = c * d
val err = fma(c, d, -cd) // ошибка округления cd
val dop = fma(a, b, -cd)
dop - err
В общем и целом - FMA используется в алгоритмах типа вычисления полиномов, перемножения матриц и т.п.
Из минусов - замена a * b + c на fma меняет поведение. Например, код sqrt(x * x - y * y) при использовании fma и x=y может вдруг вернуть NaN из-за корня отрицательного числа. Из-за этого JIT-компилятор в Java самостоятельно подставить fma не имеет права.
Историческая справка
В стандарте IEEE 754-2008 операция закреплена как обязательная.
Во всех современных процессорах и архитектурах (включая ARM и RISC-V) FMA есть. Но, как ни странно, в десктопах она появилась относительно недавно - в 2011 её добавили в AMD и в 2013 - Intel. Хотя, казалось бы, уже в 1990 она была в IBM POWER1 и в 2001 в Intel Itanium.
На видеокартах разработчики вообще преисполнились, понаделали блоков для fma, и умножение и сложение идут через них как fma(a, b, 0.0) и fma(a, 1.0, b). Заодно они считают, что один fma блок делает две floating point операции за раз, и рисуют красивые FLOPS в маркетинговых отчётах.