/* 
Copyright (C) 1990 Free Software Foundation
    written by Doug Lea (dl@rocky.oswego.edu)

This file is part of the GNU C++ Library.  This library is free
software; you can redistribute it and/or modify it under the terms of
the GNU Library General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your
option) any later version.  This 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 Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free Software
Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#ifdef __GNUG__
#pragma implementation
#endif
#include <builtin.h>
#include <math.h>
#include <values.h>
#include <AllocRing.h>

extern AllocRing _libgxx_fmtq;

char* dtoa(double fpnum,  char cvt, int width, int prec)
{
  // set up workspace

  // max possible digits <= those need to show all of prec + exp
  // <= ceil(log10(HUGE)) plus space for null, etc.

  const int worksiz = int((M_LN2 / M_LN10) * DMAXEXP) + 8; 

  // for fractional part
  char  fwork[worksiz];
  char* fw = fwork;

  // for integer part
  char  iwork[worksiz];
  char* iworkend = &iwork[sizeof(iwork) - 1];
  char* iw = iworkend;
  *iw = 0;

  // for exponent part

  const int eworksiz = int(M_LN2 * _DEXPLEN) + 8;
  char  ework[eworksiz];
  char* eworkend = &ework[sizeof(ework) - 1];
  char* ew = eworkend;
  *ew = 0;

#if (_IEEE != 0)
  if (isinf(fpnum))
  {
    char* inffmt = (char *) _libgxx_fmtq.alloc(5);
    char* inffmtp = inffmt;
    if (fpnum < 0)
      *inffmtp++ = '-';
    strcpy(inffmtp, "Inf");
    return inffmt;
  }

  if (isnan(fpnum))
  {
    char* nanfmt = (char *) _libgxx_fmtq.alloc(4);
    strcpy(nanfmt, "NaN");
    return nanfmt;
  }
#endif

  // grab sign & make non-negative
  int is_neg = fpnum < 0;
  if (is_neg) fpnum = -fpnum;

  // precision matters

  if (prec > worksiz - 2) // can't have more prec than supported
    prec = worksiz - 2;
  
  double powprec;
  if (prec == 6)
    powprec = 1.0e6;
  else
    powprec = pow(10.0, (long) prec);

  double rounder = 0.5 / powprec;

  int f_fmt = cvt == 'f' ||
    ((cvt == 'g') && (fpnum == 0.0 || (fpnum >= 1e-4 && fpnum < powprec)));

  int iwidth = 0;
  int fwidth = 0;
  int ewidth = 0;

  if (f_fmt)  // fixed format
  {
    double ipart;
    double fpart = modf(fpnum, &ipart);

    // convert fractional part

    if (fpart >= rounder || cvt != 'g')
    {
      fpart += rounder;
      if (fpart >= 1.0)
      {
        ipart += 1.0;
        fpart -= 1.0;
      }
      double ffpart = fpart;
      double ifpart;
      for (int i = 0; i < prec; ++i)
      {
        ffpart = modf(ffpart * 10.0, &ifpart);
        *fw++ = '0' + int(ifpart);
        ++fwidth;
      }
      if (cvt == 'g')  // inhibit trailing zeroes if g-fmt
      {
        for (char* p = fw - 1; p >= fwork && *p == '0'; --p)
        {
          *p = 0;
          --fwidth;
        }
      }
    }

    // convert integer part
    if (ipart == 0.0)
    {
      if (cvt != 'g' || fwidth < prec || fwidth < width)
      {
        *--iw = '0'; ++iwidth;
      }
    }
    else if (ipart <= double(MAXLONG)) // a useful speedup
    {
      long li = long(ipart);
      while (li != 0)
      {
        *--iw = '0' + (li % 10);
        li = li / 10;
        ++iwidth;
      }
    }
    else // the slow way
    {
      while (ipart > 0.5)
      {
        double ff = modf(ipart / 10.0, &ipart);
        ff = (ff + 0.05) * 10.0;
        *--iw = '0' + int(ff);
        ++iwidth;
      }
    }

    // g-fmt: kill part of frac if prec/width exceeded
    if (cvt == 'g')
    {
      int m = prec;
      if (m < width)
        m = width;
      int adj = iwidth + fwidth - m;
      if (adj > fwidth)
        adj = fwidth;
      if (adj > 0)
      {
        for (char* f = &fwork[fwidth-1]; f >= fwork && adj > 0; --adj, --f)
        {
          --fwidth;
          char ch = *f;
          *f = 0;
          if (ch > '5') // properly round: unavoidable propagation
          {
            int carry = 1;
            for (char* p = f - 1; p >= fwork && carry; --p)
            {
              ++*p;
              if (*p > '9')
                *p = '0';
              else
                carry = 0;
            }
            if (carry)
            {
              for (p = iworkend - 1; p >= iw && carry; --p)
              {
                ++*p;
                if (*p > '9')
                  *p = '0';
                else
                  carry = 0;
              }
              if (carry)
              {
                *--iw = '1';
                ++iwidth;
                --adj;
              }
            }
          }
        }
      }
    }
              
  }
  else  // e-fmt
  {
    
    // normalize
    int exp = 0;
    while (fpnum >= 10.0)
    {
      fpnum *= 0.1;
      ++exp;
    }
    double almost_one = 1.0 - rounder;
    while (fpnum > 0.0 && fpnum < almost_one)
    {
      fpnum *= 10.0;
      --exp;
    }
    
    double ipart;
    double fpart = modf(fpnum, &ipart);


    if (cvt == 'g')     // used up one digit for int part...
    {
      --prec;
      powprec /= 10.0;
      rounder = 0.5 / powprec;
    }

    // convert fractional part -- almost same as above
    if (fpart >= rounder || cvt != 'g')
    {
      fpart += rounder;
      if (fpart >= 1.0)
      {
        fpart -= 1.0;
        ipart += 1.0;
        if (ipart >= 10.0)
        {
          ++exp;
          ipart /= 10.0;
          fpart /= 10.0;
        }
      }
      double ffpart = fpart;
      double ifpart;
      for (int i = 0; i < prec; ++i)
      {
        ffpart = modf(ffpart * 10.0, &ifpart);
        *fw++ = '0' + int(ifpart);
        ++fwidth;
      }
      if (cvt == 'g')  // inhibit trailing zeroes if g-fmt
      {
        for (char* p = fw - 1; p >= fwork && *p == '0'; --p)
        {
          *p = 0;
          --fwidth;
        }
      }
    }

    
    // convert exponent

    char eneg = exp < 0;
    if (eneg) exp = - exp;

    while (exp > 0)
    {
      *--ew = '0' + (exp % 10);
      exp /= 10;
      ++ewidth;
    }

    while (ewidth < 2)  // ensure at least 2 zeroes
    {
      *--ew = '0';
      ++ewidth;
    }

    *--ew = eneg ? '-' : '+';
    *--ew = 'e';

    ewidth += 2;

    // convert the one-digit integer part
    *--iw = '0' + int(ipart);
    ++iwidth;
    
  }

  // arrange everything in returned string

  int showdot = cvt != 'g' || fwidth > 0;

  int fmtwidth = is_neg + iwidth + showdot + fwidth + ewidth;
  
  int pad = width - fmtwidth;
  if (pad < 0) pad = 0;
  
  char* fmtbase = (char *) _libgxx_fmtq.alloc(fmtwidth + pad + 1);
  char* fmt = fmtbase;
  
  for (int i = 0; i < pad; ++i) *fmt++ = ' ';
  
  if (is_neg) *fmt++ = '-';
  
  for (i = 0; i < iwidth; ++i) *fmt++ = *iw++;
  
  if (showdot)
  {
    *fmt++ = '.';
    fw = fwork;
    for (i = 0; i < fwidth; ++i) *fmt++ = *fw++;
  }
  
  for (i = 0; i < ewidth; ++i) *fmt++ = *ew++;
  
  *fmt = 0;
  
  return fmtbase;
}

