/*
 * Check that denormalised values are respected when they appear as input to
 * floating point operations copy, add, sub, abs, neg and convert s<->d
 *
 * The Cirrus Logic MaverickCrunch FPU truncates all denorms to 0 when they are
 * present as inputs to these operations.
 *
 * We check by doing math on the tiniest values.
 */

#if defined(__mips__) && defined(__sgi__)
#include <sys/fpu.h>
#endif /* defined(__mips__) && defined(__sgi__) */

#if __INT_MAX__ != 2147483647 || (__LONG_LONG_MAX__ != 9223372036854775807ll && __LONG_MAX__ != 9223372036854775807ll)
int main(void) { exit (0); }
#else

#if __LONG_MAX__ != 9223372036854775807ll
typedef unsigned long long ull;
#else
typedef unsigned long ull;
#endif
typedef unsigned ul;

union fl {
  float	f;
  ul l;
} uf;
union dl {
  double d;
  ull ll;
} ud;

int failed = 0;

/* Two floats should be equal */
#define feq(a, b) if ((a) != (b)) failed++;

/* Two floats should be different */
#define fneq(a, b) if ((a) == (b)) failed++;

/* Convert a bit pattern into a float */
#define ultof(ul) ( uf.l = (ul), uf.f )

check_float()
{
  register float one asm ("mvf3"), minus_one asm ("mvf4");
  register float two asm ("mvf5");
  register float TWO asm ("mvf7"); /* 2.0 */

/* bit 31: sign; bits 30-23 exponent; bits 22-0 mantissa.
 * Denormalized values: exponent is 0, mantissa is non-zero */
  one = ultof(0x00000001UL);
  two = ultof(0x00000002UL);
  minus_one = ultof(0x80000001UL);
  TWO = ultof(0x40000000UL); /* 2.0f */

  printf("FLOAT\n", one);
  printf("one = %e\n", one);
  printf("two = %e\n", two);
  printf("-one = %e\n", minus_one);
  printf("TWO = %e\n", TWO);

  /* first, check that comparisons recognize denorms */
  if (one == two) { failed++; puts("one == two"); }
  
  if (one + one != two) { failed++; puts("one + one != two"); }
  if (two - one != one) { failed++; puts("two - one != one"); }
  if (one * TWO != two) { failed++; puts("one * 2.0 != two"); }
  if (-one != minus_one) { failed++; puts("-one != minus_one"); }
  if (-minus_one != one) { failed++; puts("-minus_one != one"); }
  if (__builtin_fabsf(one) != one) { failed++; puts("fabsf(one) != one"); }
  if (__builtin_fabsf(minus_one) != one) { failed++; puts("fabsf(minus_one) != one"); }
}

#define ulltod(ull) ( ud.ll = (ull), ud.d )

check_double()
{
  register double one asm ("mvd3"), minus_one asm ("mvd4");
  register double two asm ("mvd5");
  register double TWO asm ("mvf7"); /* 2.0 */

/* bit 31: sign; bits 30-23 exponent; bits 22-0 mantissa.
 * Denormalized values: exponent is 0, mantissa is non-zero */
  one = ulltod(0x0000000000000001ULL);
  two = ulltod(0x0000000000000002ULL);
  minus_one = ulltod(0x8000000000000001ULL);
  TWO = ulltod(0x4000000000000000ULL);

  printf("DOUBLE\n", one);
  printf("one = %e\n", one);
  printf("two = %e\n", two);
  printf("-one = %e\n", minus_one);
  printf("TWO = %e\n", TWO);

  /* first, check that comparisons recognize denorms */
  if (one == two) { failed++; puts("one == two"); }
  
  if (one + one != two) { failed++; puts("one + one != two"); }
  if (two - one != one) { failed++; puts("two - one != one"); }
  if (one * TWO != two) { failed++; puts("one * 2.0 != two"); }
  if (-one != minus_one) { failed++; puts("-one != minus_one"); }
  if (-minus_one != one) { failed++; puts("-minus_one != one"); }
  if (__builtin_fabs(one) != one) { failed++; puts("fabs(one) != one"); }
  if (__builtin_fabs(minus_one) != one) { failed++; puts("fabs(minus_one) != one"); }
}

int main()
{
#if defined(__mips__) && defined(__sgi__)
  /* Many MIPS chips round denormalized floating point numbers to zero
     rather than follow the IEEE standard.  Change the rounding mode
     to correspond to the IEEE rounding mode that rounds numbers to
     the nearest representable mode, the most common IEEE rounding
     mode.  */
  set_fpc_csr(0);
#endif /* defined(__mips__) && defined(__sgi__) */

  if (sizeof (float) != sizeof (ul)
      || sizeof (double) != sizeof (ull))
    exit (0);
  
#if (defined __arm__ || defined __thumb__) && ! (defined __ARMEB__ || defined __VFP_FP__) && ! (defined __MAVERICK__)
  /* The ARM always stores FP numbers in big-wordian format,
     even when running in little-byteian mode.  */
#endif

  check_float(1.0);
  check_double(1.0);

  if (failed)
    abort ();
  else
    exit (0);
}
#endif
