Skip to the content.

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 в маркетинговых отчётах.