/* d-longdouble.cc -- Software floating-point emulation for the frontend.
   Copyright (C) 2006-2019 Free Software Foundation, Inc.

GCC 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, or (at your option)
any later version.

GCC 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 GCC; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */

#include "config.h"
#include "system.h"
#include "coretypes.h"

#include "dmd/mtype.h"

#include "tree.h"
#include "fold-const.h"
#include "diagnostic.h"
#include "stor-layout.h"

#include "d-tree.h"
#include "longdouble.h"


/* Constant real values 0, 1, -1 and 0.5.  */
real_t CTFloat::zero;
real_t CTFloat::one;
real_t CTFloat::minusone;
real_t CTFloat::half;

/* Truncate longdouble to the highest precision supported by target.  */

longdouble
longdouble::normalize (void)
{
  const machine_mode mode = TYPE_MODE (long_double_type_node);
  real_convert (&this->rv (), mode, &this->rv ());
  return *this;
}

/* Assign a real_value to a longdouble type.  */

void
longdouble::set (real_value& d)
{
  real_convert (&this->rv (), TYPE_MODE (long_double_type_node), &d);
}

/* Conversion routines between longdouble and integer types.  */

void
longdouble::set (int32_t d)
{
  real_from_integer (&this->rv (), TYPE_MODE (double_type_node), d, SIGNED);
}

void
longdouble::set (int64_t d)
{
  real_from_integer (&this->rv (), TYPE_MODE (long_double_type_node), d,
		     SIGNED);
}

int64_t
longdouble::to_int (void) const
{
  bool overflow;
  wide_int wi = real_to_integer (&this->rv (), &overflow, 64);
  return wi.to_shwi ();
}

/* Unsigned variants of the same conversion routines.  */

void
longdouble::set (uint32_t d)
{
  real_from_integer (&this->rv (), TYPE_MODE (double_type_node), d, UNSIGNED);
}

void
longdouble::set (uint64_t d)
{
  real_from_integer (&this->rv (), TYPE_MODE (long_double_type_node), d,
		     UNSIGNED);
}

uint64_t
longdouble::to_uint (void) const
{
  bool overflow;
  wide_int wi = real_to_integer (&this->rv (), &overflow, 64);
  return wi.to_uhwi ();
}

/* For conversion between boolean, only need to check if is zero.  */

void
longdouble::set (bool d)
{
  this->rv () = (d == false) ? dconst0 : dconst1;
}

bool
longdouble::to_bool (void) const
{
  return this->rv ().cl != rvc_zero;
}

/* Overload numeric operators for longdouble types.  */

longdouble
longdouble::add (const longdouble& r) const
{
  longdouble x;
  real_arithmetic (&x.rv (), PLUS_EXPR, &this->rv (), &r.rv ());
  return x.normalize ();
}

longdouble
longdouble::sub (const longdouble& r) const
{
  longdouble x;
  real_arithmetic (&x.rv (), MINUS_EXPR, &this->rv (), &r.rv ());
  return x.normalize ();
}

longdouble
longdouble::mul (const longdouble& r) const
{
  longdouble x;
  real_arithmetic (&x.rv (), MULT_EXPR, &this->rv (), &r.rv ());
  return x.normalize ();
}

longdouble
longdouble::div (const longdouble& r) const
{
  longdouble x;
  real_arithmetic (&x.rv (), RDIV_EXPR, &this->rv (), &r.rv ());
  return x.normalize ();
}

longdouble
longdouble::mod (const longdouble& r) const
{
  longdouble x;
  real_value q;

  if (r.rv ().cl == rvc_zero || REAL_VALUE_ISINF (this->rv ()))
    {
      real_nan (&x.rv (), "", 1, TYPE_MODE (long_double_type_node));
      return x;
    }

  if (this->rv ().cl == rvc_zero)
    return *this;

  if (REAL_VALUE_ISINF (r.rv ()))
    return *this;

  /* Need to check for NaN?  */
  real_arithmetic (&q, RDIV_EXPR, &this->rv (), &r.rv ());
  real_arithmetic (&q, FIX_TRUNC_EXPR, &q, NULL);
  real_arithmetic (&q, MULT_EXPR, &q, &r.rv ());
  real_arithmetic (&x.rv (), MINUS_EXPR, &this->rv (), &q);

  return x.normalize ();
}

longdouble
longdouble::neg (void) const
{
  longdouble x;
  real_arithmetic (&x.rv (), NEGATE_EXPR, &this->rv (), NULL);
  return x.normalize ();
}

/* Overload equality operators for longdouble types.  */

int
longdouble::cmp (const longdouble& r) const
{
  if (real_compare (LT_EXPR, &this->rv (), &r.rv ()))
    return -1;

  if (real_compare (GT_EXPR, &this->rv (), &r.rv ()))
    return 1;

  return 0;
}

int
longdouble::equals (const longdouble& r) const
{
  return real_compare (EQ_EXPR, &this->rv (), &r.rv ());
}
