/***
*cvt.c - C floating-point output conversions
*
*   Copyright (c) 1987-89, Microsoft Corporation
*
*Purpose:
*   contains routines for performing %e, %f, and %g output conversions
*   for printf, etc.
*
*   routines include _cfltcvt(), _cftoe(), _cftof(), _cftog(), _fassign(),
*                    _positive(), _cropzeros(), _forcdecpt()
*
*Revision History:
*   04-18-84  RN  author
*   01-15-87  BCM corrected processing of %g formats (to handle precision
*                   as the maximum number of signifcant digits displayed)
*   03-24-87  BCM Evaluation Issues: (fccvt.obj version for ?LIBFA)
*             ------------------
*                 SDS - no problem
*                 GD/TS :
*                       char g_fmt = 0;                 (local,   initialized)
*                       int g_magnitude =0;             (local,   initialized)
*                       char g_round_expansion = 0;     (local,   initialized)
*                       STRFLT g_pflt;                          (local, uninitialized)
*                 other INIT :
*               ALTMATH __fpmath() initialization (perhaps)
*                 TERM - nothing
*   10-22-87  BCM   changes for OS/2 Support Library -
*                               including elimination of g_... static variables
*                               in favor of stack-based variables & function arguments
*                               under MTHREAD switch;  changed interfaces to _cfto? routines
*   01-15-88  BCM   remove IBMC20 switches; use only memmove, not memcpy;
*                               use just MTHREAD switch, not SS_NEQ_DGROUP
*   06-13-88  WAJ   Fixed %.1g processing for small x
*   08-02-88  WAJ   Made changes to _fassign() for new input().
*   03-09-89  WAJ   Added some long double support.
*   06-05-89  WAJ   Made changes for C6. LDOUBLE => long double
*   06-12-89  WAJ   Renamed this file from cvtn.c to cvt.c
*   11-02-89  WAJ   Removed register.h
*   06-28-90  WAJ   Removed fars.
*   11-15-90  WAJ   Added _cdecl where needed. Also "pascal" => "_pascal".
*   09-12-91  GDP   _cdecl=>_CALLTYPE2 _pascal=>_CALLTYPE5 near=>_NEAR
*   04-30-92  GDP   Removed floating point code. Instead used S/W routines
*                   (_atodbl, _atoflt _atoldbl), so that to avoid generation
*                   of IEEE exceptions from the lib code.
*   03-11-93  JWM   Added minimal support for _INTL decimal point - one byte only!
*   07-16-93  SRW   ALPHA Merge
*
*******************************************************************************/

#include <ctype.h>
#include <string.h>
#include <math.h>
#include <cv.h>
#include <nlsint.h>

#ifdef i386
// Uncomment this for enabling 10-byte long double string conversions
// #define LONG_DOUBLE
#endif


/* this routine resides in the crt32 tree */
extern void _fptostr(char *buf, int digits, STRFLT pflt);


static void _CALLTYPE5 _shift( char *s, int dist );

#ifdef  MTHREAD
    static char * _cftoe2( char * buf, int ndec, int caps, STRFLT pflt, char g_fmt );
    static char * _cftof2( char * buf, int ndec, STRFLT pflt, char g_fmt );

#else   /* not MTHREAD */
    static char * _cftoe_g( double * pvalue, char * buf, int ndec, int caps );
    static char * _cftof_g( double * pvalue, char * buf, int ndec );
#endif  /* not MTHREAD */

/***
*_forcdecpt(buffer) - force a decimal point in floating-point output
*Purpose:
*       force a decimal point in floating point output. we are only called if '#'
*       flag is given and precision is 0; so we know the number has no '.'. insert
*       the '.' and move everybody else back one position, until '\0' seen
*
*       side effects: futzes around with the buffer, trying to insert a '.'
*       after the initial string of digits. the first char can usually be
*   skipped since it will be a digit or a '-'.  but in the 0-precision case,
*       the number could start with 'e' or 'E', so we'd want the '.' before the
*       exponent in that case.
*
*Entry:
*       buffer = (char *) pointer to buffer to modify
*
*Exit:
*       returns : (void)
*
*Exceptions:
*******************************************************************************/

void _CALLTYPE2 _forcdecpt( char * buffer )
{
char    holdchar;
char    nextchar;

    if (tolower(*buffer) != 'e'){
        do {
            buffer++;
            }
        while (isdigit(*buffer));
        }

    holdchar = *buffer;

#ifdef _INTL
        *buffer++ = *_decimal_point;
#else
        *buffer++ = '.';
#endif

    do  {
        nextchar = *buffer;
        *buffer = holdchar;
        holdchar = nextchar;
        }

    while(*buffer++);
}


/***
*_cropzeros(buffer) - removes trailing zeros from floating-point output
*Purpose:
*       removes trailing zeros (after the '.') from floating-point output;
*       called only when we're doing %g format, there's no '#' flag, and
*       precision is non-zero.  plays around with the buffer, looking for
*       trailing zeros.  when we find them, then we move everbody else forward
*       so they overlay the zeros.  if we eliminate the entire fraction part,
*       then we overlay the decimal point ('.'), too.   
*
*       side effects: changes the buffer from
*       [-] digit [digit...] [ . [digits...] [0...] ] [(exponent part)]
*       to
*               [-] digit [digit...] [ . digit [digits...] ] [(exponent part)]
*       or
*       [-] digit [digit...] [(exponent part)]
*
*Entry:
*       buffer = (char *) pointer to buffer to modify
*
*Exit:
*       returns : (void)
*
*Exceptions:
*******************************************************************************/

void _CALLTYPE2 _cropzeros( char * buf )
{
char    *stop;

#ifdef _INTL
    while (*buf && *buf != *_decimal_point)
#else
    while (*buf && *buf != '.')
#endif
        buf++;

    if (*buf++) {
        while (*buf && *buf != 'e' && *buf != 'E')
            buf++;

        stop = buf--;

        while (*buf == '0')
            buf--;

#ifdef _INTL
        if (*buf == *_decimal_point)
#else
        if (*buf == '.')
#endif
            buf--;

        while( (*++buf = *stop++) != '\0' );
        }
}


int _CALLTYPE2 _positive( double * arg )
{
    return( (*arg >= 0.0) );
}


void  _CALLTYPE2 _fassign( int flag, char * argument, char * number )
{

    FLOAT floattemp;
    DOUBLE doubletemp;

#ifdef  LONG_DOUBLE
    _LDOUBLE longtemp;

    switch( flag ){
        case 2:
            _atoldbl(&longtemp, number );
            *(_LDOUBLE UNALIGNED *)argument = longtemp;
            break;

        case 1:
            _atodbl( &doubletemp, number );
            *(DOUBLE UNALIGNED *)argument = doubletemp;
            break;

        default:
            _atoflt( &floattemp, number );
            *(FLOAT UNALIGNED *)argument = floattemp;
        }

#else   /* not LONG_DOUBLE */
    if (flag) {

        _atodbl( &doubletemp, number );
#if defined(_ALPHA_) && !defined(_MSC_VER)
        /*
         * Alpha acc does not support unaligned double yet.
         */
        ((long UNALIGNED *)argument)[0] =  ((long *)&doubletemp)[0];
        ((long UNALIGNED *)argument)[1] =  ((long *)&doubletemp)[1];
#else
        *(DOUBLE UNALIGNED *)argument = doubletemp;
#endif
    } else {
        _atoflt( &floattemp, number );
#if defined(_ALPHA_) && !defined(_MSC_VER)
        /*
         * Alpha acc does not support unaligned float yet.
         */
        *((long UNALIGNED *)argument) =  *((long *)&floattemp);
#else
        *(FLOAT UNALIGNED *)argument = floattemp;
#endif
    }
#endif  /* not LONG_DOUBLE */
}


#ifndef MTHREAD
    static char   g_fmt = 0;
    static int    g_magnitude = 0;
    static char   g_round_expansion = 0;
    static STRFLT g_pflt;
#endif


/*
 *  Function name:  _cftoe
 *
 *  Arguments:      pvalue -  double * pointer
 *                  buf    -  char * pointer
 *                  ndec   -  int
 *                  caps   -  int
 *
 *  Description:    _cftoe converts the double pointed to by pvalue to a null
 *                  terminated string of ASCII digits in the c language
 *                  printf %e format, nad returns a pointer to the result.
 *                  This format has the form [-]d.ddde(+/-)ddd, where there
 *                  will be ndec digits following the decimal point.  If
 *                  ndec <= 0, no decimal point will appear.  The low order
 *                  digit is rounded.  If caps is nonzero then the exponent
 *                  will appear as E(+/-)ddd.
 *
 *  Side Effects:   the buffer 'buf' is assumed to have a minimum length
 *                  of CVTBUFSIZE (defined in cvt.h) and the routines will
 *                  not write over this size.
 *
 *  Copyright:      written  R.K. Wyss, Microsoft,  Sept. 9, 1983
 *                  copyright (c) Microsoft Corp. 1983
 *
 *  History:
 *
 */

#ifdef MTHREAD
    static char * _cftoe2( char * buf, int ndec, int caps, STRFLT pflt, char g_fmt )
#else
    char * _CALLTYPE2 _cftoe( double * pvalue, char * buf, int ndec, int caps )
#endif
{
#ifndef MTHREAD
    STRFLT pflt;
#endif

char    *p;
int     exp;

#ifdef MTHREAD
    int g_magnitude = pflt->decpt - 1;
#endif

    /* first convert the value */

    /* place the output in the buffer and round.  Leave space in the buffer
     * for the '-' sign (if any) and the decimal point (if any)
     */

    if (g_fmt) {
#ifndef MTHREAD
        pflt = g_pflt;
#endif
        /* shift it right one place if nec. for decimal point */

        p = buf + (pflt->sign == '-');
        _shift(p, (ndec > 0));
                }
#ifndef MTHREAD
    else {
        pflt = _fltout(*pvalue);
        _fptostr(buf + (pflt->sign == '-') + (ndec > 0), ndec + 1, pflt);
        }
#endif


    /* now fix the number up to be in e format */

    p = buf;

    /* put in negative sign if needed */

    if (pflt->sign == '-')
        *p++ = '-';

    /* put in decimal point if needed.  Copy the first digit to the place
     * left for it and put the decimal point in its place
     */

    if (ndec > 0) {
        *p = *(p+1);
#ifdef _INTL
        *(++p) = *_decimal_point;
#else
        *(++p) = '.';
#endif
        }

    /* find the end of the string and attach the exponent field */

    p = strcpy(p+ndec+(!g_fmt), "e+000");

    /* adjust exponent indicator according to caps flag and increment
     * pointer to point to exponent sign
     */

    if (caps)
        *p = 'E';

    p++;

    /* if mantissa is zero, then the number is 0 and we are done; otherwise
     * adjust the exponent sign (if necessary) and value.
     */

    if (*pflt->mantissa != '0') {

        /* check to see if exponent is negative; if so adjust exponent sign and
         * exponent value.
         */

        if( (exp = pflt->decpt - 1) < 0 ) {
            exp = -exp;
            *p = '-';
            }

        p++;

        if (exp >= 100) {
            *p += (char)(exp / 100);
            exp %= 100;
            }
        p++;

        if (exp >= 10) {
            *p += (char)(exp / 10);
            exp %= 10;
            }

        *++p += (char)exp;
        }

    return(buf);
}


#ifdef MTHREAD

char * _CALLTYPE2 _cftoe( double * pvalue, char * buf, int ndec, int caps )
{
struct _strflt retstrflt;
char  resstr[21];
STRFLT pflt = &retstrflt;

    _fltout2(*pvalue, (struct _strflt *)&retstrflt,
              (char *)resstr);
    _fptostr(buf + (pflt->sign == '-') + (ndec > 0), ndec + 1, pflt);
    _cftoe2(buf, ndec, caps, pflt, /* g_fmt = */ 0);

    return( buf );
}

#else   /* not MTHREAD */

static char * _cftoe_g( double * pvalue, char * buf, int ndec, int caps )
{
    char *res;
    g_fmt = 1;
    res = _cftoe(pvalue, buf, ndec, caps);
    g_fmt = 0;
    return (res);
}

#endif  /* not MTHREAD */


#ifdef MTHREAD
static char * _cftof2( char * buf, int ndec, STRFLT pflt, char g_fmt )

#else
char * _CALLTYPE2 _cftof( double * pvalue, char * buf, int ndec )
#endif

{
#ifndef MTHREAD
STRFLT pflt;
#endif

char    *p;
char    addzero = 0;

#ifdef MTHREAD
int     g_magnitude = pflt->decpt - 1;
#endif


    /* first convert the value */

    /* place the output in the users buffer and round.  Save space for
     * the minus sign now if it will be needed
     */

    if (g_fmt) {
#ifndef MTHREAD
        pflt = g_pflt;
#endif

        p = buf + (pflt->sign == '-');
        if (g_magnitude == ndec) {
            char *q = p + g_magnitude;
            *q++ = '0';
            *q = '\0';
            /* allows for extra place-holding '0' in the exponent == precision
             * case of the g format
             */
            }
        }
#ifndef MTHREAD
    else {
        pflt = _fltout(*pvalue);
        _fptostr(buf+(pflt->sign == '-'), ndec + pflt->decpt, pflt);
        }
#endif


    /* now fix up the number to be in the correct f format */

    p = buf;

    /* put in negative sign, if necessary */

    if (pflt->sign == '-')
        *p++ = '-';

    /* insert leading 0 for purely fractional values and position ourselves
     * at the correct spot for inserting the decimal point
     */

    if (pflt->decpt <= 0) {
        _shift(p, 1);
        *p++ = '0';
        }
    else
        p += pflt->decpt;

        /* put in decimal point if required and any zero padding needed */

    if (ndec > 0) {
        _shift(p, 1);
#ifdef _INTL
        *p++ = *_decimal_point;
#else
        *p++ = '.';
#endif

        /* if the value is less than 1 then we may need to put 0's out in
         * front of the first non-zero digit of the mantissa
         */

        if (pflt->decpt < 0) {
            if( g_fmt )
                ndec = -pflt->decpt;
            else
                ndec = (ndec < -pflt->decpt ) ? ndec : -pflt->decpt;
            _shift(p, ndec);
            memset( p, '0', ndec);
            }
        }

    return( buf);
}


/*
 *  Function name:  _cftof
 *
 *  Arguments:      value  -  double * pointer
 *                  buf    -  char * pointer
 *                  ndec   -  int
 *
 *  Description:    _cftof converts the double pointed to by pvalue to a null
 *                  terminated string of ASCII digits in the c language
 *                  printf %f format, and returns a pointer to the result.
 *                  This format has the form [-]ddddd.ddddd, where there will
 *                  be ndec digits following the decimal point.  If ndec <= 0,
 *                  no decimal point will appear.  The low order digit is
 *                  rounded.
 *
 *  Side Effects:   the buffer 'buf' is assumed to have a minimum length
 *                  of CVTBUFSIZE (defined in cvt.h) and the routines will
 *                  not write over this size.
 *
 *  Copyright:      written  R.K. Wyss, Microsoft,  Sept. 9, 1983
 *                  copyright (c) Microsoft Corp. 1983
 *
 *  History:
 *
 */

#ifdef MTHREAD

char * _CALLTYPE2 _cftof( double * pvalue, char * buf, int ndec )
{
    struct _strflt retstrflt;
    char  resstr[21];
    STRFLT pflt = &retstrflt;
    _fltout2(*pvalue, (struct _strflt *) &retstrflt,
                                      (char *) resstr);
    _fptostr(buf+(pflt->sign == '-'), ndec + pflt->decpt, pflt);
    _cftof2(buf, ndec, pflt, /* g_fmt = */ 0);

    return( buf );
}

#else   /* not MTHREAD */


static char * _cftof_g( double * pvalue, char * buf, int ndec )
{
    char *res;
    g_fmt = 1;
    res = _cftof(pvalue, buf, ndec);
    g_fmt = 0;
    return (res);
}

#endif  /* not MTHREAD */

/*
 *  Function name:  _cftog
 *
 *  Arguments:      value  -  double * pointer
 *                  buf    -  char * pointer
 *                  ndec   -  int
 *
 *  Description:    _cftog converts the double pointed to by pvalue to a null
 *                  terminated string of ASCII digits in the c language
 *                  printf %g format, and returns a pointer to the result.
 *                  The form used depends on the value converted.  The printf
 *                  %e form will be used if the magnitude of valude is less
 *                  than -4 or is greater than ndec, otherwise printf %f will
 *                  be used.  ndec always specifies the number of digits
 *                  following the decimal point.  The low order digit is
 *                  appropriately rounded.
 *
 *  Side Effects:   the buffer 'buf' is assumed to have a minimum length
 *                  of CVTBUFSIZE (defined in cvt.h) and the routines will
 *                  not write over this size.
 *
 *  Copyright:      written  R.K. Wyss, Microsoft,  Sept. 9, 1983
 *                  copyright (c) Microsoft Corp. 1983
 *
 *  History:
 *
 */

char * _CALLTYPE2 _cftog( double * pvalue, char * buf, int ndec, int caps )
{
char *p;

#ifdef MTHREAD
char g_round_expansion = 0;
STRFLT g_pflt;
int g_magnitude;
struct _strflt retstrflt;
char  resstr[21];

    /* first convert the number */

    g_pflt = &retstrflt;
    _fltout2(*pvalue, (struct _strflt *)&retstrflt,
                  (char *)resstr);

#else   /* not MTHREAD */

    /* first convert the number */

    g_pflt = _fltout(*pvalue);
#endif  /* not MTHREAD */

    g_magnitude = g_pflt->decpt - 1;
    p = buf + (g_pflt->sign == '-');

    _fptostr(p, ndec, g_pflt);
    g_round_expansion = (char)(g_magnitude < (g_pflt->decpt-1));


    /* compute the magnitude of value */

    g_magnitude = g_pflt->decpt - 1;

    /* convert value to the c language g format */

    if (g_magnitude < -4 || g_magnitude >= ndec){     /* use e format */
        /*  (g_round_expansion ==>
         *  extra digit will be overwritten by 'e+xxx')
         */

#ifdef MTHREAD
        return(_cftoe2(buf, ndec, caps, g_pflt, /* g_fmt = */ 1));
#else
        return(_cftoe_g(pvalue, buf, ndec, caps));
#endif

        }
    else {                                                                                   /* use f format */
        if (g_round_expansion) {
            /* throw away extra final digit from expansion */
            while (*p++);
            *(p-2) = '\0';
            }

#ifdef MTHREAD
        return(_cftof2(buf, ndec, g_pflt, /* g_fmt = */ 1));
#else
        return(_cftof_g(pvalue, buf, ndec));
#endif

        }
}

/***
*_cfltcvt(arg, buf, format, precision, caps) - convert floating-point output
*Purpose:
*
*Entry:
*       arg = (double *) pointer to double-precision floating-point number
*       buf = (char *) pointer to buffer into which to put the converted
*                                  ASCII form of the number
*       format = (int) 'e', 'f', or 'g'
*       precision = (int) giving number of decimal places for %e and %f formats,
*                                         and giving maximum number of significant digits for
*                                         %g format
*       caps = (int) flag indicating whether 'E' in exponent should be capatilized
*                                (for %E and %G formats only)
*       
*Exit:
*       returns : (void)
*
*Exceptions:
*******************************************************************************/
/*
 *  Function name:  _cfltcvt
 *
 *  Arguments:      arg    -  double * pointer
 *                  buf    -  char * pointer
 *                                      format -  int
 *                  ndec   -  int
 *                  caps   -  int
 *
 *  Description:    _cfltcvt determines from the format, what routines to
 *                  call to generate the correct floating point format
 *
 *  Side Effects:   none
 *
 *      Author:            Dave Weil, Jan 12, 1985
 *
 *  Copyright:     Copyright (C) Microsoft Corp. 1985
 */

void _CALLTYPE2 _cfltcvt( double * arg, char * buffer, int format, int precision, int caps )
{
    if (format == 'e' || format == 'E')
        _cftoe(arg, buffer, precision, caps);
    else if (format == 'f')
        _cftof(arg, buffer, precision);
    else
        _cftog(arg, buffer, precision, caps);
}

/***
*_shift(s, dist) - shift a null-terminated string in memory (internal routine)
*Purpose:
*       _shift is a helper routine that shifts a null-terminated string
*       in memory, e.g., moves part of a buffer used for floating-point output
*
*       modifies memory locations (s+dist) through (s+dist+strlen(s))
*
*Entry:
*       s = (char *) pointer to string to move
*       dist = (int) distance to move the string to the right (if negative, to left)
*
*Exit:
*       returns : (void)
*
*Exceptions:
*******************************************************************************/

static void _CALLTYPE5 _shift( char *s, int dist )
{
    if( dist )
        memmove(s+dist, s, strlen(s)+1);
}