最終更新日時:
が更新

履歴 編集

function
<cmath>

std::fma(C++11)

namespace std {
  float fma(float x, float y, float z);
  double fma(double x, double y, double z);
  long double fma(long double x, long double y, long double z);

  Promoted fma(Arithmetic1 x, Arithmetic2 y, Arithmetic3 z);

  float fmaf(float x, float y, float z);                         // C++17 から
  long double fmal(long double x, long double y, long double z); // C++17 から
}

概要

x * y + z を計算する。

丸めは乗算と加算のあとに1回だけ行われる。

fma は fused multiply-add の略。

戻り値

x * y + z を無限精度で計算した後、現在の丸めモードで丸めた結果

備考

  • 本関数は、C99 の規格にある fma(より正確には math.h ヘッダの fmafmaffmal の 3 つ。それぞれ C++ の doublefloatlong double バージョンに相当)と同等である。
  • C99 では、処理系が ISO IEC 60559(IEEE 754 と同一)に準拠している場合、以下のように規定されている。

    • x、または y のいずれか一方が無限でもう一方がゼロで、かつ、z が NaN の場合、NaN を返す。この際、FE_INVALID が発生するか否かは処理系定義である。
    • x、または y のいずれか一方が無限でもう一方がゼロで、かつ、z が NaN 以外の場合、NaN を返す。この際、FE_INVALID が発生する。
    • x * yz がいずれも無限で、かつ、それらの符号が異なる場合、NaN を返す。この際、FE_INVALID が発生する。
  • 処理系が ISO IEC 60559 に準拠しているかどうかは、std::numeric_limits<T>::is_iec559 によって型毎に判別可能である。

  • 本関数が単純に x * y + z を計算するのと同等か、より速い場合には、引数の型に応じて FP_FAST_FMAdouble の場合)、FP_FAST_FMAFfloat の場合)、FP_FAST_FMALlong double の場合)と言ったマクロが定義される。
    これらのマクロは、一般的に本関数がハードウェアによる積和演算命令を使用している場合にのみ定義される。

#include <iostream>
#include <iomanip>
#include <cerrno>
#include <cstring>
#include <cfenv>
#include <cmath>

// エラー状態のクリア
void clearerr()
{
  if (math_errhandling & MATH_ERREXCEPT) {
    std::feclearexcept(FE_ALL_EXCEPT);
  } else {
    errno = 0;
  }
}

// エラー状態の出力
void printerr()
{
  if (math_errhandling & MATH_ERREXCEPT) {
    int excepts = std::fetestexcept(FE_ALL_EXCEPT);
    if (excepts & FE_INVALID) {
      std::cout << "FE_INVALID\n";
    }
    if (excepts & FE_DIVBYZERO) {
      std::cout << "FE_DIVBYZERO\n";
    }
    if (excepts & FE_OVERFLOW) {
      std::cout << "FE_OVERFLOW\n";
    }
    if (excepts & FE_UNDERFLOW) {
      std::cout << "FE_UNDERFLOW\n";
    }
    if (excepts & FE_INEXACT) {
      std::cout << "FE_INEXACT\n";
    }
  } else {
    int err = errno;
    if (err != 0) {
      std::cout << std::strerror(err) << '\n';
    }
  }
}

void test(float x, float y, float z)
{
  clearerr();
  float result1 = std::fma(x, y, z);
  printerr();
  std::cout << "fma(" << x << ", " << y << ", " << z << ") = " << result1 << "\n\n";

  clearerr();
  float result2 = x * y + z;
  printerr();
  std::cout << x << " * " << y << " + " << z << " = " << result2 << "\n\n\n";
}

int main()
{
  std::cout << std::fixed << std::setprecision(1);
  test(1.5F, 8388609.0F, -0.5F);
  test(INFINITY, 0.0F, NAN);
  test(INFINITY, 0.0F, 1.0F);
  test(INFINITY, 1.0F, -INFINITY);
}

出力例

fma(1.5, 8388609.0, -0.5) = 12582913.0

FE_INEXACT
1.5 * 8388609.0 + -0.5 = 12582914.0


FE_INVALID
fma(inf, 0.0, nan) = nan

FE_INVALID
inf * 0.0 + nan = -nan


FE_INVALID
fma(inf, 0.0, 1.0) = -nan

FE_INVALID
inf * 0.0 + 1.0 = -nan


FE_INVALID
fma(inf, 1.0, -inf) = -nan

FE_INVALID
inf * 1.0 + -inf = -nan