//===========================================================================
//
//      strtod.cxx
//
//      ISO String to double-precision floating point conversion
//
//===========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004, 2005 Free Software Foundation, Inc.
// Copyright (C) 2004, 2005 eCosCentric Limited                             
//
// eCos 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 2 or (at your option) any later      
// version.                                                                 
//
// eCos 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 eCos; if not, write to the Free Software Foundation, Inc.,    
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.            
//
// As a special exception, if other files instantiate templates or use      
// macros or inline functions from this file, or you compile this file      
// and link it with other works to produce a work based on this file,       
// this file does not by itself cause the resulting work to be covered by   
// the GNU General Public License. However the source code for this file    
// must still be made available in accordance with section (3) of the GNU   
// General Public License v2.                                               
//
// This exception does not invalidate any other reasons why a work based    
// on this file might be covered by the GNU General Public License.         
// -------------------------------------------                              
// ####ECOSGPLCOPYRIGHTEND####                                              
//===========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):    jlarmour
// Contributors: 
// Date:         2000-04-30
// Purpose:     
// Description: 
// Usage:       
//
//####DESCRIPTIONEND####
//
//===========================================================================

// CONFIGURATION

#include <pkgconf/libc_stdlib.h>           // Configuration header

// INCLUDES

#include <cyg/infra/cyg_type.h>     // Common type definitions and support
#include <cyg/infra/cyg_trac.h>     // Tracing support
#include <cyg/infra/cyg_ass.h>      // Assertion support
#include <stddef.h>                 // NULL, wchar_t and size_t from compiler
#include <stdlib.h>                 // Main header for stdlib functions
#include <ctype.h>                  // isspace() and isdigit()
#include <float.h>                  // DBL_MIN_10_EXP and DBL_MAX_10_EXP
#include <math.h>                   // HUGE_VAL
#include <errno.h>                  // errno

// CONSTANTS

// fudge by 1 to allow for the discrepancy between the DBL_MAX and 10**DBL_MAX_10_EXP
// strictly this may allow under and overflow so that is dealt with specially
#define MAXE (DBL_MAX_10_EXP+1)
#define MINE (DBL_MIN_10_EXP-1)

// flags
#define SIGN    0x01
#define ESIGN   0x02
#define DECP    0x04


// MACROS

#define Ise(c)          ((c == 'e') || (c == 'E'))
#define Issign(c)       ((c == '-') || (c == '+'))
#define Val(c)          ((c - '0'))


// FUNCTIONS

/*
 * [atw] multiply 64 bit accumulator by 10 and add digit.
 */
static int
ten_mul(unsigned long long *acc, int digit, unsigned int *accum_length)
{
    // DBL_DIG == Number of decimal digits of precision in a double
    if ((*accum_length)++ >= DBL_DIG)
    {
        return 1; /* would overflow a double, so the extra significant digit is in fact lost */
    } else {
        *acc *= 10;
        *acc += digit;
        return 0;     /* no overflow */
    }
} // ten_mul()


/*
 * compute 10**x by successive squaring.
 */

static const double
exp10(unsigned x)
{
    static double powtab[] = {1.0,
                              10.0,
                              100.0,
                              1000.0,
                              10000.0};
    
    if (x < (sizeof(powtab)/sizeof(double)))
        return powtab[x];
    else if (x & 1)
        return 10.0 * exp10(x-1);
    else
        return exp10(x/2) * exp10(x/2);
} // exp10()


/*
 * return (*acc) scaled by 10**dexp.
 */

static double
adjust(unsigned long long *acc, int dexp, int sign, unsigned int accum_length)
     /* *acc    the 64 bit accumulator */
     /* dexp    decimal exponent       */
     /* sign    sign flag              */
{
    double r;
    /* Work out the "real" exponent so we know whether the number is
       too big. This is dictated not just by the exponent but by the
       length of the accumulator */
    /* Similarly if the number is small, we may have extra freedom due to
       the inherent exponent in the accumulator */
    int realexp = dexp+accum_length-1;
    int checkmax = 0;
    
    if (realexp > MAXE)
    {
        errno = ERANGE;
        return (sign) ? -HUGE_VAL : HUGE_VAL;
    }
    else if (realexp < MINE)
    {
        errno = ERANGE;
        return 0.0;
    }

    // ugh, somewhat kludgy, but at least treats as special case rather than doing extra throughout
    else if (realexp == MAXE)
    {
        checkmax = 1;
        // FIXME we should try and restore what we've shaved off: savedbit = (*acc) & 1;
        *acc >>= 1;
    }
    
    r = *acc;
    if (sign)
        r = -r;
    if (dexp==0)
        return r;
#define CYGSEM_LIBC_STDLIB_STRTOD_USES_LIBM
#ifdef CYGSEM_LIBC_STDLIB_STRTOD_USES_LIBM
    r = r * pow(10.0,dexp);
#else
    if (dexp < 0)
        r = r / exp10(abs(dexp));
    else
        r = r * exp10(dexp);
#endif
    if (checkmax)
    {
        // dividing/multiplying by 2 is the only accurate operation for FP (we assume FLT_RADIX==2)
        if (r > (DBL_MAX/2.0))
        {
            errno = ERANGE;
            return (sign) ? -HUGE_VAL : HUGE_VAL;
        }
        // return r to correct value
        r += r;
    }
    else if (realexp == MINE)
    {
        // rely on denormals or flushing to 0 to see us home
        if (r < DBL_MIN)
        {
            errno = ERANGE;
            return 0.0;
        }
    }
    return r;
} // adjust()


externC double
strtod( const char *nptr, char **endptr )
{
    const char *start=nptr;
    unsigned long long accum = 0ULL;
    int flags = 0;
    int texp  = 0;
    int e     = 0;
    int conv_done = 0;
  
    double retval;
    unsigned int accum_length=0;

    CYG_REPORT_FUNCNAMETYPE( "strtod", "returning %f" );

    CYG_CHECK_DATA_PTR( nptr, "nptr is an invalid pointer!" );

    // endptr is allowed to be NULL, but if it isn't, we check it
    if (endptr != NULL)
        CYG_CHECK_DATA_PTR( endptr, "endptr is an invalid pointer!" );
    
    while(isspace(*nptr)) nptr++;
    if(*nptr == '\0')
    {   /* just leading spaces */
        if(endptr != NULL) *endptr = (char *)start;
        return 0.0;
    }
    
    
    if(Issign(*nptr))
    {
        if(*nptr == '-') flags = SIGN;
        if(*++nptr == '\0')
        {   /* "+|-" : should be an error ? */
            if(endptr != NULL) *endptr = (char *)start;
            return 0.0;
        }
    }
    
    for(; (isdigit(*nptr) || (*nptr == '.')); nptr++)
    {
        conv_done = 1;
        if(*nptr == '.')
        {
            if (flags & DECP) // already set!
            {
                conv_done = 0;
                break;
            }
            flags |= DECP;
        }
        else
        {
            if( ten_mul(&accum, Val(*nptr), &accum_length) ) texp++;
            if(flags & DECP) texp--;
        }
    }
    
    if(Ise(*nptr))
    {
        conv_done = 0; // require exponent to have been supplied if e is present
        if(*++nptr != '\0') /* skip e|E */
        {  /* ! ([nptr]xxx[.[yyy]]e)  */
            
            while(isspace(*nptr)) nptr++; /* Ansi allows spaces after e */
            if(*nptr != '\0')
            { /*  ! ([nptr]xxx[.[yyy]]e[space])  */
                
                if(Issign(*nptr))
                    if(*nptr++ == '-') flags |= ESIGN;
                
                if(*nptr != '\0')
                { /*  ! ([nptr]xxx[.[yyy]]e[nptr])  -- error?? */
                    
                    for(; isdigit(*nptr); nptr++)
                        if (e < MAXE) /* prevent from grossly overflowing */
                            {
                                conv_done = 1; // require exponent to have been supplied if e is present
                                e = e*10 + Val(*nptr);
                            }
                        else
                            {
                                conv_done = 0;
                                texp = MAXE+1; // force overflow
                            }
                    
                    /* dont care what comes after this */
                    if(flags & ESIGN)
                        texp -= e;
                    else
                        texp += e;
                }
            }
        }
    }
    
    if(endptr != NULL) 
        *endptr = (char *)((conv_done) ? nptr : start);
    
    retval = adjust(&accum, (int)texp, (int)(flags & SIGN), accum_length);
  

    CYG_REPORT_RETVAL( retval );

    return retval;
} // strtod()


// EOF strtod.cxx
