/*
 * Copyright (c) 2013 The Native Client Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

/*
 * Test rounding behavior, checking and setting the rounding mode.
 */

#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

volatile double two = 2.0;
volatile double one = 1.0;
volatile double zero = 0.0;
volatile double neg_one = -1.0;
volatile double neg_two = -2.0;

#define ASSERT_TRUE(pred)                                     \
  if (!pred) {                                                \
    fprintf(stderr, "file %s, line %d, !(" #pred ")\n",       \
            __FILE__, __LINE__);                              \
  }

#define ASSERT_EQ(f1, f2)                                     \
  do {                                                        \
    double x = f1;                                            \
    double y = f2;                                            \
    if (x != y) {                                             \
      fprintf(stderr, "file %s, line %d, %f != %f\n",         \
              __FILE__, __LINE__, x, y);                      \
      abort();                                                \
    }                                                         \
  } while(0)

void __attribute__((noinline)) test_flt_rounds(int);
void test_flt_rounds(int expected_round) {
  ASSERT_EQ(FLT_ROUNDS, expected_round);
}

extern void set_round_toward_nearest(void);
extern void set_round_toward_plus_infinity(void);
extern void set_round_toward_minus_infinity(void);
extern void set_round_toward_zero(void);

/* Test that nearbyint() really does round to nearest. */
void __attribute__((noinline)) test_round_to_nearest(void);
void test_round_to_nearest(void) {
  ASSERT_EQ(nearbyint(one + 0.5), two);
  ASSERT_EQ(nearbyint(zero + 0.5), zero);
  ASSERT_EQ(nearbyint(zero), zero);
  ASSERT_EQ(nearbyint(-zero), -zero);
  ASSERT_EQ(nearbyint(neg_one + 0.5), -zero);
  ASSERT_TRUE(isnan(nearbyint(NAN)));
  ASSERT_EQ(nearbyint(INFINITY), INFINITY);
}

/* Test that nearbyint() really does round to plus infinity. */
void __attribute__((noinline)) test_round_to_plus_infinity(void);
void test_round_to_plus_infinity(void) {
  ASSERT_EQ(nearbyint(one + 0.5), two);
  ASSERT_EQ(nearbyint(zero + 0.5), one);
  ASSERT_EQ(nearbyint(zero), zero);
  ASSERT_EQ(nearbyint(-zero), -zero);
  ASSERT_EQ(nearbyint(neg_one + 0.5), -zero);
  ASSERT_TRUE(isnan(nearbyint(NAN)));
  ASSERT_EQ(nearbyint(INFINITY), INFINITY);
}

/* Test that nearbyint() really does round to minus infinity. */
void __attribute__((noinline)) test_round_to_minus_infinity(void);
void test_round_to_minus_infinity(void) {
  ASSERT_EQ(nearbyint(one + 0.5), one);
  ASSERT_EQ(nearbyint(zero + 0.5), zero);
  ASSERT_EQ(nearbyint(zero), zero);
  ASSERT_EQ(nearbyint(-zero), -zero);
  ASSERT_EQ(nearbyint(neg_one + 0.5), neg_one);
  ASSERT_TRUE(isnan(nearbyint(NAN)));
  ASSERT_EQ(nearbyint(INFINITY), INFINITY);
}

/* Test that nearbyint() really does round to zero. */
void __attribute__((noinline)) test_round_to_zero(void);
void test_round_to_zero(void) {
  ASSERT_EQ(nearbyint(one + 0.5), one);
  ASSERT_EQ(nearbyint(zero + 0.5), zero);
  ASSERT_EQ(nearbyint(zero), zero);
  ASSERT_EQ(nearbyint(-zero), -zero);
  ASSERT_EQ(nearbyint(neg_one + 0.5), -zero);
  ASSERT_TRUE(isnan(nearbyint(NAN)));
  ASSERT_EQ(nearbyint(INFINITY), INFINITY);
}

int main(int ac, char* av[]) {
#if defined(__MIPS__)
  /* For MIPS, the llvm.flt.rounds intrinsic is stuck at (1), so we cannot
   * test the other cases.  Furthermore, the llvm MIPS asm parser doesn't
   * parse the cfc1 $reg, $31 and ctc1 instructions for setting
   * the rounding mode.
   *
   * At least test that initial value is always round towards nearest.
   */
  test_flt_rounds(1);
  test_round_to_nearest();
#else
  /* Test that initial value is always round towards nearest. */
  test_flt_rounds(1);
  test_round_to_nearest();

  set_round_toward_plus_infinity();
  /*
   * GCC + Newlib and Glibc always return 1 for FLT_ROUNDS,
   * so we can't really test FLT_ROUNDS.  However, we can observe the
   * difference through nearbyint() behavior:
   * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=30569
   */
#if defined(__clang__)
  test_flt_rounds(2);
#endif
  test_round_to_plus_infinity();

  set_round_toward_minus_infinity();
#if defined(__clang__)
  test_flt_rounds(3);
#endif
  test_round_to_minus_infinity();

  set_round_toward_zero();
#if defined(__clang__)
  test_flt_rounds(0);
#endif
  test_round_to_zero();

  set_round_toward_nearest();

#endif  /* __MIPS__ */
  test_flt_rounds(1);
  test_round_to_nearest();
  return 0;
}
