/* Test of fused multiply-add.
   Copyright (C) 2011-2023 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

/* Written by Bruno Haible <bruno@clisp.org>, 2011.  */

/* Returns 2^e as a DOUBLE.  */
#define POW2(e) \
  LDEXP (L_(1.0), e)

/* One could define XE_RANGE and YE_RANGE to 5 or 60, but this would slow down
   the test without the expectation of catching more bugs.  */
#define XE_RANGE 0
#define YE_RANGE 0

/* Define to 1 if you want to allow the behaviour of the 'double-double'
   implementation of 'long double' (seen on IRIX 6.5 and Linux/PowerPC).
   This floating-point type does not follow IEEE 754.  */
#if MANT_BIT == LDBL_MANT_BIT && LDBL_MANT_BIT == 2 * DBL_MANT_BIT
# define FORGIVE_DOUBLEDOUBLE_BUG 1
#else
# define FORGIVE_DOUBLEDOUBLE_BUG 0
#endif

/* Subnormal numbers appear to not work as expected on IRIX 6.5.  */
#ifdef __sgi
# define MIN_SUBNORMAL_EXP (MIN_EXP - 1)
#else
# define MIN_SUBNORMAL_EXP (MIN_EXP - MANT_BIT)
#endif

/* Check rounding behaviour.  */

static void
test_function (DOUBLE (*my_fma) (DOUBLE, DOUBLE, DOUBLE))
{
  /* Array mapping n to (-1)^n.  */
  static const DOUBLE pow_m1[] =
    {
      L_(1.0), - L_(1.0), L_(1.0), - L_(1.0),
      L_(1.0), - L_(1.0), L_(1.0), - L_(1.0)
    };

  /* A product x * y that consists of two bits.  */
  {
    volatile DOUBLE x;
    volatile DOUBLE y;
    volatile DOUBLE z;
    volatile DOUBLE result;
    volatile DOUBLE expected;
    int xs;
    int xe;
    int ys;
    int ye;
    int ze;
    DOUBLE sign;

    for (xs = 0; xs < 2; xs++)
      for (xe = -XE_RANGE; xe <= XE_RANGE; xe++)
        {
          x = pow_m1[xs] * POW2 (xe); /* (-1)^xs * 2^xe */

          for (ys = 0; ys < 2; ys++)
            for (ye = -YE_RANGE; ye <= YE_RANGE; ye++)
              {
                y = pow_m1[ys] * POW2 (ye); /* (-1)^ys * 2^ye */

                sign = pow_m1[xs + ys];

                /* Test addition (same signs).  */
                for (ze = MIN_EXP - MANT_BIT; ze <= MAX_EXP - 1;)
                  {
                    z = sign * POW2 (ze); /* (-1)^(xs+ys) * 2^ze */
                    result = my_fma (x, y, z);
                    if (xe + ye >= ze + MANT_BIT)
                      expected = sign * POW2 (xe + ye);
                    else if (xe + ye > ze - MANT_BIT)
                      expected = sign * (POW2 (xe + ye) + POW2 (ze));
                    else
                      expected = z;
                    ASSERT (result == expected);

                    ze++;
                    /* Shortcut some values of ze, to speed up the test.  */
                    if (ze == MIN_EXP + MANT_BIT)
                      ze = - 2 * MANT_BIT - 1;
                    else if (ze == 2 * MANT_BIT)
                      ze = MAX_EXP - MANT_BIT - 1;
                  }

                /* Test subtraction (opposite signs).  */
                for (ze = MIN_EXP - MANT_BIT; ze <= MAX_EXP - 1;)
                  {
                    z = - sign * POW2 (ze); /* (-1)^(xs+ys+1) * 2^ze */
                    result = my_fma (x, y, z);
                    if (xe + ye > ze + MANT_BIT)
                      expected = sign * POW2 (xe + ye);
                    else if (xe + ye >= ze)
                      expected = sign * (POW2 (xe + ye) - POW2 (ze));
                    else if (xe + ye > ze - 1 - MANT_BIT)
                      expected = - sign * (POW2 (ze) - POW2 (xe + ye));
                    else
                      expected = z;
                    ASSERT (result == expected);

                    ze++;
                    /* Shortcut some values of ze, to speed up the test.  */
                    if (ze == MIN_EXP + MANT_BIT)
                      ze = - 2 * MANT_BIT - 1;
                    else if (ze == 2 * MANT_BIT)
                      ze = MAX_EXP - MANT_BIT - 1;
                  }
              }
        }
  }
  /* A product x * y that consists of three bits.  */
  {
    volatile DOUBLE x;
    volatile DOUBLE y;
    volatile DOUBLE z;
    volatile DOUBLE result;
    volatile DOUBLE expected;
    int i;
    int xs;
    int xe;
    int ys;
    int ye;
    int ze;
    DOUBLE sign;

    for (i = 1; i <= MANT_BIT - 1; i++)
      for (xs = 0; xs < 2; xs++)
        for (xe = -XE_RANGE; xe <= XE_RANGE; xe++)
          {
            x = /* (-1)^xs * (2^xe + 2^(xe-i)) */
              pow_m1[xs] * (POW2 (xe) + POW2 (xe - i));

            for (ys = 0; ys < 2; ys++)
              for (ye = -YE_RANGE; ye <= YE_RANGE; ye++)
                {
                  y = /* (-1)^ys * (2^ye + 2^(ye-i)) */
                    pow_m1[ys] * (POW2 (ye) + POW2 (ye - i));

                  sign = pow_m1[xs + ys];

                  /* The exact value of x * y is
                     (-1)^(xs+ys) * (2^(xe+ye) + 2^(xe+ye-i+1) + 2^(xe+ye-2*i)) */

                  /* Test addition (same signs).  */
                  for (ze = MIN_SUBNORMAL_EXP; ze <= MAX_EXP - 1;)
                    {
                      z = sign * POW2 (ze); /* (-1)^(xs+ys) * 2^ze */
                      result = my_fma (x, y, z);
                      if (FORGIVE_DOUBLEDOUBLE_BUG)
                        if ((xe + ye > ze
                             && xe + ye < ze + MANT_BIT
                             && i == DBL_MANT_BIT)
                            || (xe + ye == ze + DBL_MANT_BIT && i == DBL_MANT_BIT + 1)
                            || (xe + ye == ze + MANT_BIT - 1 && i == 1))
                          goto skip1;
                      if (xe + ye > ze + MANT_BIT)
                        {
                          if (2 * i > MANT_BIT)
                            expected =
                              sign * (POW2 (xe + ye)
                                      + POW2 (xe + ye - i + 1));
                          else if (2 * i == MANT_BIT)
                            expected =
                              sign * (POW2 (xe + ye)
                                      + POW2 (xe + ye - i + 1)
                                      + POW2 (xe + ye - MANT_BIT + 1));
                          else
                            expected =
                              sign * (POW2 (xe + ye)
                                      + POW2 (xe + ye - i + 1)
                                      + POW2 (xe + ye - 2 * i));
                        }
                      else if (xe + ye == ze + MANT_BIT)
                        {
                          if (2 * i >= MANT_BIT)
                            expected =
                              sign * (POW2 (xe + ye)
                                      + POW2 (xe + ye - i + 1)
                                      + POW2 (xe + ye - MANT_BIT + 1));
                          else if (2 * i == MANT_BIT - 1)
                            /* round-to-even rounds up */
                            expected =
                              sign * (POW2 (xe + ye)
                                      + POW2 (xe + ye - i + 1)
                                      + POW2 (xe + ye - 2 * i + 1));
                          else
                            expected =
                              sign * (POW2 (xe + ye)
                                      + POW2 (xe + ye - i + 1)
                                      + POW2 (xe + ye - 2 * i));
                        }
                      else if (xe + ye > ze - MANT_BIT + 2 * i)
                        expected =
                          sign * (POW2 (ze)
                                  + POW2 (xe + ye)
                                  + POW2 (xe + ye - i + 1)
                                  + POW2 (xe + ye - 2 * i));
                      else if (xe + ye >= ze - MANT_BIT + i)
                        expected =
                          sign * (POW2 (ze)
                                  + POW2 (xe + ye)
                                  + POW2 (xe + ye - i + 1));
                      else if (xe + ye == ze - MANT_BIT + i - 1)
                        {
                          if (i == 1)
                            expected =
                              sign * (POW2 (ze) + POW2 (ze - MANT_BIT + 1));
                          else
                            expected =
                              sign * (POW2 (ze)
                                      + POW2 (xe + ye)
                                      + POW2 (ze - MANT_BIT + 1));
                        }
                      else if (xe + ye >= ze - MANT_BIT + 1)
                        expected = sign * (POW2 (ze) + POW2 (xe + ye));
                      else if (xe + ye == ze - MANT_BIT)
                        expected =
                          sign * (POW2 (ze) + POW2 (ze - MANT_BIT + 1));
                      else if (xe + ye == ze - MANT_BIT - 1)
                        {
                          if (i == 1)
                            expected =
                              sign * (POW2 (ze) + POW2 (ze - MANT_BIT + 1));
                          else
                            expected = z;
                        }
                      else
                        expected = z;
                      ASSERT (result == expected);

                     skip1:
                      ze++;
                      /* Shortcut some values of ze, to speed up the test.  */
                      if (ze == MIN_EXP + MANT_BIT)
                        ze = - 2 * MANT_BIT - 1;
                      else if (ze == 2 * MANT_BIT)
                        ze = MAX_EXP - MANT_BIT - 1;
                    }

                  /* Test subtraction (opposite signs).  */
                  if (i > 1)
                    for (ze = MIN_SUBNORMAL_EXP; ze <= MAX_EXP - 1;)
                      {
                        z = - sign * POW2 (ze); /* (-1)^(xs+ys+1) * 2^ze */
                        result = my_fma (x, y, z);
                        if (FORGIVE_DOUBLEDOUBLE_BUG)
                          if ((xe + ye == ze && i == MANT_BIT - 1)
                              || (xe + ye > ze
                                  && xe + ye <= ze + DBL_MANT_BIT - 1
                                  && i == DBL_MANT_BIT + 1)
                              || (xe + ye >= ze + DBL_MANT_BIT - 1
                                  && xe + ye < ze + MANT_BIT
                                  && xe + ye == ze + i - 1)
                              || (xe + ye > ze + DBL_MANT_BIT
                                  && xe + ye < ze + MANT_BIT
                                  && i == DBL_MANT_BIT))
                            goto skip2;
                        if (xe + ye == ze)
                          {
                            /* maximal extinction */
                            expected =
                              sign * (POW2 (xe + ye - i + 1)
                                      + POW2 (xe + ye - 2 * i));
                          }
                        else if (xe + ye == ze - 1)
                          {
                            /* significant extinction */
                            if (2 * i > MANT_BIT)
                              expected =
                                sign * (- POW2 (xe + ye)
                                        + POW2 (xe + ye - i + 1));
                            else
                              expected =
                                sign * (- POW2 (xe + ye)
                                        + POW2 (xe + ye - i + 1)
                                        + POW2 (xe + ye - 2 * i));
                          }
                        else if (xe + ye > ze + MANT_BIT)
                          {
                            if (2 * i >= MANT_BIT)
                              expected =
                                sign * (POW2 (xe + ye)
                                        + POW2 (xe + ye - i + 1));
                            else
                              expected =
                                sign * (POW2 (xe + ye)
                                        + POW2 (xe + ye - i + 1)
                                        + POW2 (xe + ye - 2 * i));
                          }
                        else if (xe + ye == ze + MANT_BIT)
                          {
                            if (2 * i >= MANT_BIT)
                              expected =
                                sign * (POW2 (xe + ye)
                                        + POW2 (xe + ye - i + 1));
                            else if (2 * i == MANT_BIT - 1)
                              /* round-to-even rounds down */
                              expected =
                                sign * (POW2 (xe + ye)
                                        + POW2 (xe + ye - i + 1));
                            else
                              /* round-to-even rounds up */
                              expected =
                                sign * (POW2 (xe + ye)
                                        + POW2 (xe + ye - i + 1)
                                        + POW2 (xe + ye - 2 * i));
                          }
                        else if (xe + ye >= ze - MANT_BIT + 2 * i)
                          expected =
                            sign * (- POW2 (ze)
                                    + POW2 (xe + ye)
                                    + POW2 (xe + ye - i + 1)
                                    + POW2 (xe + ye - 2 * i));
                        else if (xe + ye >= ze - MANT_BIT + i - 1)
                          expected =
                            sign * (- POW2 (ze)
                                    + POW2 (xe + ye)
                                    + POW2 (xe + ye - i + 1));
                        else if (xe + ye == ze - MANT_BIT + i - 2)
                          expected =
                            sign * (- POW2 (ze)
                                    + POW2 (xe + ye)
                                    + POW2 (ze - MANT_BIT));
                        else if (xe + ye >= ze - MANT_BIT)
                          expected =
                            sign * (- POW2 (ze)
                                    + POW2 (xe + ye));
                        else if (xe + ye == ze - MANT_BIT - 1)
                          expected =
                            sign * (- POW2 (ze)
                                    + POW2 (ze - MANT_BIT));
                        else
                          expected = z;
                        ASSERT (result == expected);

                       skip2:
                        ze++;
                        /* Shortcut some values of ze, to speed up the test.  */
                        if (ze == MIN_EXP + MANT_BIT)
                          ze = - 2 * MANT_BIT - 1;
                        else if (ze == 2 * MANT_BIT)
                          ze = MAX_EXP - MANT_BIT - 1;
                      }
                }
          }
  }
  /* TODO: Similar tests with
     x = (-1)^xs * (2^xe - 2^(xe-i)), y = (-1)^ys * (2^ye - 2^(ye-i))  */
  /* A product x * y that consists of one segment of bits (or, if you prefer,
     two bits, one with positive weight and one with negative weight).  */
  {
    volatile DOUBLE x;
    volatile DOUBLE y;
    volatile DOUBLE z;
    volatile DOUBLE result;
    volatile DOUBLE expected;
    int i;
    int xs;
    int xe;
    int ys;
    int ye;
    int ze;
    DOUBLE sign;

    for (i = 1; i <= MANT_BIT - 1; i++)
      for (xs = 0; xs < 2; xs++)
        for (xe = -XE_RANGE; xe <= XE_RANGE; xe++)
          {
            x = /* (-1)^xs * (2^xe + 2^(xe-i)) */
              pow_m1[xs] * (POW2 (xe) + POW2 (xe - i));

            for (ys = 0; ys < 2; ys++)
              for (ye = -YE_RANGE; ye <= YE_RANGE; ye++)
                {
                  y = /* (-1)^ys * (2^ye - 2^(ye-i)) */
                    pow_m1[ys] * (POW2 (ye) - POW2 (ye - i));

                  sign = pow_m1[xs + ys];

                  /* The exact value of x * y is
                     (-1)^(xs+ys) * (2^(xe+ye) - 2^(xe+ye-2*i)) */

                  /* Test addition (same signs).  */
                  for (ze = MIN_EXP - MANT_BIT; ze <= MAX_EXP - 1;)
                    {
                      z = sign * POW2 (ze); /* (-1)^(xs+ys) * 2^ze */
                      result = my_fma (x, y, z);
                      if (FORGIVE_DOUBLEDOUBLE_BUG)
                        if ((xe + ye == ze + MANT_BIT && i > DBL_MANT_BIT)
                            || (xe + ye < ze + MANT_BIT
                                && xe + ye >= ze
                                && i == DBL_MANT_BIT)
                            || (xe + ye < ze
                                && xe + ye == ze - MANT_BIT + 2 * i))
                          goto skip3;
                      if (xe + ye > ze + MANT_BIT + 1)
                        {
                          if (2 * i > MANT_BIT)
                            expected = sign * POW2 (xe + ye);
                          else
                            expected =
                              sign * (POW2 (xe + ye)
                                      - POW2 (xe + ye - 2 * i));
                        }
                      else if (xe + ye == ze + MANT_BIT + 1)
                        {
                          if (2 * i >= MANT_BIT)
                            expected = sign * POW2 (xe + ye);
                          else
                            expected =
                              sign * (POW2 (xe + ye)
                                      - POW2 (xe + ye - 2 * i));
                        }
                      else if (xe + ye >= ze - MANT_BIT + 2 * i)
                        {
                          if (2 * i > MANT_BIT)
                            expected =
                              sign * (POW2 (xe + ye) + POW2 (ze));
                          else
                            expected =
                              sign * (POW2 (xe + ye)
                                      - POW2 (xe + ye - 2 * i)
                                      + POW2 (ze));
                        }
                      else if (xe + ye >= ze - MANT_BIT + 1)
                        expected =
                          sign * (POW2 (ze) + POW2 (xe + ye));
                      else
                        expected = z;
                      ASSERT (result == expected);

                     skip3:
                      ze++;
                      /* Shortcut some values of ze, to speed up the test.  */
                      if (ze == MIN_EXP + MANT_BIT)
                        ze = - 2 * MANT_BIT - 1;
                      else if (ze == 2 * MANT_BIT)
                        ze = MAX_EXP - MANT_BIT - 1;
                    }

                  /* Test subtraction (opposite signs).  */
                  if (i > 1)
                    for (ze = MIN_SUBNORMAL_EXP; ze <= MAX_EXP - 1;)
                      {
                        z = - sign * POW2 (ze); /* (-1)^(xs+ys+1) * 2^ze */
                        result = my_fma (x, y, z);
                        if (FORGIVE_DOUBLEDOUBLE_BUG)
                          if (xe + ye > ze
                              && xe + ye < ze + DBL_MANT_BIT
                              && xe + ye == ze + 2 * i - LDBL_MANT_BIT)
                            goto skip4;
                        if (xe + ye == ze)
                          {
                            /* maximal extinction */
                            expected = sign * - POW2 (xe + ye - 2 * i);
                          }
                        else if (xe + ye > ze + MANT_BIT + 1)
                          {
                            if (2 * i > MANT_BIT + 1)
                              expected = sign * POW2 (xe + ye);
                            else if (2 * i == MANT_BIT + 1)
                              expected =
                                sign * (POW2 (xe + ye)
                                        - POW2 (xe + ye - MANT_BIT));
                            else
                              expected =
                                sign * (POW2 (xe + ye)
                                        - POW2 (xe + ye - 2 * i));
                          }
                        else if (xe + ye == ze + MANT_BIT + 1)
                          {
                            if (2 * i > MANT_BIT)
                              expected =
                                sign * (POW2 (xe + ye)
                                        - POW2 (xe + ye - MANT_BIT));
                            else if (2 * i == MANT_BIT)
                              expected =
                                sign * (POW2 (xe + ye)
                                        - POW2 (xe + ye - MANT_BIT + 1));
                            else
                              expected =
                                sign * (POW2 (xe + ye)
                                        - POW2 (xe + ye - 2 * i));
                          }
                        else if (xe + ye == ze + MANT_BIT)
                          {
                            if (2 * i > MANT_BIT + 1)
                              expected =
                                sign * (POW2 (xe + ye)
                                        - POW2 (xe + ye - MANT_BIT));
                            else if (2 * i == MANT_BIT + 1)
                              expected =
                                sign * (POW2 (xe + ye)
                                        - POW2 (xe + ye - MANT_BIT + 1));
                            else
                              expected =
                                sign * (POW2 (xe + ye)
                                        - POW2 (ze)
                                        - POW2 (xe + ye - 2 * i));
                          }
                        else if (xe + ye > ze - MANT_BIT + 2 * i)
                          {
                            if (2 * i > MANT_BIT)
                              expected =
                                sign * (POW2 (xe + ye) - POW2 (ze));
                            else
                              expected =
                                sign * (POW2 (xe + ye)
                                        - POW2 (ze)
                                        - POW2 (xe + ye - 2 * i));
                          }
                        else if (xe + ye == ze - MANT_BIT + 2 * i)
                          expected =
                            sign * (- POW2 (ze)
                                    + POW2 (xe + ye)
                                    - POW2 (xe + ye - 2 * i));
                        else if (xe + ye >= ze - MANT_BIT)
                          expected = sign * (- POW2 (ze) + POW2 (xe + ye));
                        else
                          expected = z;
                        ASSERT (result == expected);

                       skip4:
                        ze++;
                        /* Shortcut some values of ze, to speed up the test.  */
                        if (ze == MIN_EXP + MANT_BIT)
                          ze = - 2 * MANT_BIT - 1;
                        else if (ze == 2 * MANT_BIT)
                          ze = MAX_EXP - MANT_BIT - 1;
                      }
                }
          }
  }
  /* TODO: Tests with denormalized results.  */
  /* Tests with temporary overflow.  */
  {
    volatile DOUBLE x = POW2 (MAX_EXP - 1);
    volatile DOUBLE y = POW2 (MAX_EXP - 1);
    volatile DOUBLE z = - INFINITY;
    volatile DOUBLE result = my_fma (x, y, z);
    ASSERT (result == - INFINITY);
  }
  {
    volatile DOUBLE x = POW2 (MAX_EXP - 1); /* 2^(MAX_EXP-1) */
    volatile DOUBLE y = L_(2.0);            /* 2^1 */
    volatile DOUBLE z =               /* -(2^MAX_EXP - 2^(MAX_EXP-MANT_BIT)) */
      - LDEXP (POW2 (MAX_EXP - 1) - POW2 (MAX_EXP - MANT_BIT - 1), 1);
    volatile DOUBLE result = my_fma (x, y, z);
    if (!FORGIVE_DOUBLEDOUBLE_BUG)
      ASSERT (result == POW2 (MAX_EXP - MANT_BIT));
  }
  {
    volatile DOUBLE x = POW2 (MAX_EXP - 1);             /* 2^(MAX_EXP-1) */
    volatile DOUBLE y = L_(3.0);                        /* 3 */
    volatile DOUBLE z = - LDEXP (L_(5.0), MAX_EXP - 3); /* -5*2^(MAX_EXP-3) */
    volatile DOUBLE result = my_fma (x, y, z);
    ASSERT (result == LDEXP (L_(7.0), MAX_EXP - 3));
  }
}
