/* <decimal> for libdfp and redirect to system <decimal>

   Copyright (C) 2011, 2012 Free Software Foundation, Inc.

   This file is part of the Decimal Floating Point C Library.

   Author(s): Ryan S. Arnold <rsa@us.ibm.com>

   The Decimal Floating Point C Library is free software; you can
   redistribute it and/or modify it under the terms of the GNU Lesser
   General Public License version 2.1.

   The Decimal Floating Point C Library 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 Lesser General Public License version 2.1 for more details.

   You should have received a copy of the GNU Lesser General Public
   License version 2.1 along with the Decimal Floating Point C Library;
   if not, write to the Free Software Foundation, Inc., 59 Temple Place,
   Suite 330, Boston, MA 02111-1307 USA.

   Please see libdfp/COPYING.txt for more information.  */

#ifndef _LIBDFP_DECIMAL_H
#define _LIBDFP_DECIMAL_H 1

#ifdef _GLIBCXX_DECIMAL_IMPL
# error "dfp/<decimal/decimal> should be #included before the system <decimal/decimal> header."
#endif

#ifdef __cplusplus

/* This needs to be declared before #include_next <decimal/decimal> so that
 * _Decimal types are declared when the system <decimal/decimal> pulls in
 * other headers which are overloaded by libdfp, e.g., wchar.h.  */
#include <float.h> /* Pick up _Decimal[32|64|128] typedefs.  */

/* Pick up the system <decimal>. Since C++ DFP support is currently only a
 * technical report the decimal header is in the non-default search path so
 * <decimal/decimal> must be used.  */
#include_next <decimal/decimal>
using namespace std::decimal;

#pragma GCC system_header

#include <ostream>
using std::ostream;

#include <istream>
using std::istream;

#include <string>
using std::string;

#include <iostream>
using namespace std;

#include <stdlib.h> /* Pick up the strtod[32|64|128] prototypes.  */
#include <stdio.h>  /* Pick up the sprintf prototype.  */
#include <limits.h> /* For CHAR_MAX.  */
#include <string.h> /* For memset.  */

template<unsigned int size>
struct FIND_DEC_MANT_DIG
{
  enum {RESULT = 0};
  static inline std::string get_fmt(char conv)
  {
     std::string spec = "";
     return spec;
  }
};

template <>
struct FIND_DEC_MANT_DIG<4>
{
  enum {RESULT = (__DEC32_MANT_DIG__) };
  static inline std::string get_fmt(char conv)
  {
     std::string spec = "%.*H";
     return spec.append(1,conv);
  }
};
template <>
struct FIND_DEC_MANT_DIG<8>
{
  enum {RESULT = (__DEC64_MANT_DIG__) };
  static inline std::string get_fmt(char conv)
  {
     std::string spec = "%.*D";
     return spec.append(1,conv);
  }

};
template <>
struct FIND_DEC_MANT_DIG<16>
{
  enum {RESULT = (__DEC128_MANT_DIG__) };
  static inline std::string get_fmt(char conv)
  {
     std::string spec = "%.*DD";
     return spec.append(1,conv);
  }
};

/* Template meta-programming so we only have to write this code once for use
 * with each of the _Decimal[32|64|128] types.  */
template<class decimal_type>
class LIBDFP_META {
private:
public:
  static inline ostream & decimal_to_string(std::ostream &os, decimal_type &d)
    {
      /* If strbuf is big enough for a _Decimal128, it is big enough for the
       * other types as well, so just use the same size for all of them.
       *   1  (leading zero)
       * + 1  (.)
       * + 34 (__DEC128_MANT_DIG__)
       * + 1  (e)
       * + 1  (+/-)
       * + 4  (digits in __DEC128_MAX_EXP__)
       * + 1  "\n"
       * = 43 -> round up to a power of 2 = 64.  */

      char strbuf[64];

      ios_base::fmtflags flags = os.flags();
      unsigned int precision = os.precision();

      if (precision > DEC_MANT_DIG)
        precision = DEC_MANT_DIG;

      /* ISO/IEC TR 24733 (DFP C++ Technical Report) doesn't have a
       * specific ios:: flag that matches the 'a/A' conversion specifier
       * defined in ISO/IEC TR 24732 (DFP C Technical Report).  Nor does it
       * specify a default.  It only identifies ios::fixed and
       * ios::scientific.  There is no default ios_base::fmtflags set if
       * the user has not specified one or the other.  Presumably the 'g/G'
       * specifier would be chosen as the default since this implicitly
       * chooses f/F or e/E based on the number encountered.  */

      /* This ostream implementation prefers the behavior of the DFP C TR
       * 'a/A' conversion specifier as the default over 'g/G'.  The 'a/A'
       * conversion specifier is preferred because 'a/A' includes the
       * number of digits left of the decimal point in the specified
       * precision.  This is important because a decimal mantissa has a
       * fixed number of digits and there is no forced (approximate)
       * rounding, like in binary floating point.  */

      /* This implementation allows one of three approaches.  It can
       * either use 'a/A' by default or it can allow the user to select
       * 'f/F' (with ios::fixed) or 'e/E' (with ios::scientific).

      /* IF the user wants 'g/G' as the default he should compile with
       * #define _LIBDFP_G_CONV_SPEC. */

#ifdef _LIBDFP_G_CONV_SPEC
      char conv = 'g';
#else
      char conv = 'a';
#endif

      if (flags & ios::fixed)
        {
          /* Only used for "NAN" and "INF" rather than "nan" and "inf" for
           * "%.*Df".  */
          if (flags & ios::uppercase)
            conv = 'F';
          else
            conv = 'f';
        }
      else if (flags & ios::scientific)
       {
          if (flags & ios::uppercase)
            conv = 'E';
          else
            conv = 'e';
        }
      else if (flags & ios::uppercase)
#ifdef _LIBDFP_G_CONV_SPEC
        conv = 'G';
#else
        conv = 'A';
#endif

      std::string fmtstr = FIND_DEC_MANT_DIG<(sizeof(decimal_type))>::get_fmt(conv);
      sprintf (strbuf, fmtstr.c_str(), precision, d.__getval());
      os << strbuf;
      return os;
    }

  enum {DEC_MANT_DIG = FIND_DEC_MANT_DIG<(sizeof(decimal_type))>::RESULT};
};

/* Per ISO/IEC JTC1 SC22 WG21 N2732 - TR 24733: "Extension for the programming
 * language C++ to support decimal floating point arithmetic" define the
 * ostream and istream operators.  These are included in libdfp and NOT
 * libstdc++ because the ostream and istream operators rely upon libdfp
 * printf and strtod[32|64|128] support provided by libdfp.  */

namespace std
{
namespace decimal
{
  //ISO/IEC TR 24733 - 3.2.11 Formatted output:

  template <class charT, class traits>
  inline std::basic_ostream<charT, traits> &
    operator<<(std::basic_ostream<charT, traits> & os, decimal32 d)
    {
      LIBDFP_META<std::decimal::decimal32>::decimal_to_string(os, d);
      return os;
    }

  template <class charT, class traits>
  inline std::basic_ostream<charT, traits> &
    operator<<(std::basic_ostream<charT, traits> & os, decimal64 d)
    {
      LIBDFP_META<std::decimal::decimal64>::decimal_to_string(os, d);
      return os;
    }

  template <class charT, class traits>
  inline std::basic_ostream<charT, traits> &
    operator<<(std::basic_ostream<charT, traits> & os, decimal128 d)
    {
      LIBDFP_META<std::decimal::decimal128>::decimal_to_string(os, d);
      return os;
    }

//  ISO/IEC TR 27433 - 3.2.11 Formatted input:

  template <class charT, class traits>
    inline std::basic_istream<charT, traits> &
      operator>>(std::basic_istream<charT, traits> & is, decimal32 & d)
      {
	char buf[CHAR_MAX];
	memset(buf, '\0', CHAR_MAX);
	is.read(buf,CHAR_MAX);
	d.__setval(strtod32(buf, NULL));
	return is;
      }

  template <class charT, class traits>
    inline std::basic_istream<charT, traits> &
      operator>>(std::basic_istream<charT, traits> & is, decimal64 & d)
      {
	char buf[CHAR_MAX];
	memset(buf, '\0', CHAR_MAX);
	is.read(buf,CHAR_MAX);
	d.__setval(strtod64(buf, NULL));
	return is;
      }

  template <class charT, class traits>
    inline std::basic_istream<charT, traits> &
      operator>>(std::basic_istream<charT, traits> & is, decimal128 & d)
      {
	char buf[CHAR_MAX];
	memset(buf, '\0', CHAR_MAX);
	is.read(buf,CHAR_MAX);
	d.__setval(strtod128(buf, NULL));
	return is;
      }

} /* namespace decimal  */
} /* namespace std  */

#else
# warning "dfp/<decimal/decimal> should only be #included by C++ programs."
#endif /* __cplusplus  */

#endif /* _LIBDFP_DECIMAL_H  */
