/***************************************************************************
 *
 * num_get.cpp
 *
 * $Id: num_get.cpp 648752 2008-04-16 17:01:56Z faridz $
 *
 ***************************************************************************
 *
 * Licensed to the Apache Software  Foundation (ASF) under one or more
 * contributor  license agreements.  See  the NOTICE  file distributed
 * with  this  work  for  additional information  regarding  copyright
 * ownership.   The ASF  licenses this  file to  you under  the Apache
 * License, Version  2.0 (the  "License"); you may  not use  this file
 * except in  compliance with the License.   You may obtain  a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the  License is distributed on an  "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY  KIND, either  express or
 * implied.   See  the License  for  the  specific language  governing
 * permissions and limitations under the License.
 *
 * Copyright 2001-2007 Rogue Wave Software, Inc.
 * 
 **************************************************************************/

#define _RWSTD_LIB_SRC
#include <rw/_defs.h>     

#include "setlocale.h"
#include "strtol.h"
#include "punct.h"      // for __rw_get_stdio_fmat

#include <ios>          // for ios_base, needed by <rw/_punct.h>
#include <loc/_num_get.h>

#include <errno.h>      // for ERANGE, errno
#include <float.h>      // for {DBL,FLT,LDBL}_{MIN,MAX}
#include <stdio.h>      // for printf()
#include <stdlib.h>     // for strtod()
#include <string.h>     // for memcpy()

#include <rw/_defs.h>     


// in case EINVAL is not #defined in errno.h
#ifdef EINVAL
#  define _RWSTD_EINVAL EINVAL
#else   // if !defined (EINVAL)
// actual value of EINVAL on both Linux and SunOS is 22
#  define _RWSTD_EINVAL 22
#endif   // EINVAL



#if defined (_RWSTD_NO_STRTOF) && !defined (_RWSTD_NO_STRTOF_IN_LIBC)

#  undef _RWSTD_NO_STRTOF

extern "C" float strtof (const char*, char**);

#endif   // NO_STRTOF && !NO_STRTOF_IN_LIBC

#if defined (_RWSTD_NO_STRTOLD) && !defined (_RWSTD_NO_STRTOLD_IN_LIBC)

#  undef _RWSTD_NO_STRTOLD

extern "C" long double strtold (const char*, char**);

#endif   // NO_STRTOLD && !NO_STRTOLD_IN_LIBC


#ifdef _MSC_VER
    // shut up useless MSVC warning: unary minus operator
    // applied to unsigned type, result still unsigned
#  pragma warning (disable: 4146)
#endif   // _MSC_VER


_RWSTD_NAMESPACE (__rw) { 


typedef unsigned char UChar;


#ifndef _RWSTD_NO_OBJECT_MANGLING

extern "C" {

#else   // if defined (_RWSTD_NO_OBJECT_MANGLING)

extern "C++" {

#endif   // _RWSTD_NO_OBJECT_MANGLING


extern const float       __rw_flt_infinity;
extern const double      __rw_dbl_infinity;

#ifndef _RWSTD_NO_LONG_DOUBLE

extern const long double __rw_ldbl_infinity;

#endif   // _RWSTD_NO_LONG_DOUBLE

}   // extern "C"/"C++"


// verifies that the `grps' array of size `ngrps' representing the
// lengths of individual groups of digits separated by thousands_sep
// in the representation of a number corresponds to the `grping' array
// of `ngrpings' elements
// returns a non-negative number of success, negative number whose
// absolute value indicates the bad group on failure

// Examples of valid groups:
//
//      sequence        grps  grping
//
//           123        "\3"  "\3\2"   3 <= 3,
//         1,234      "\1\3"  "\3\2"   1 <= 2 && 3 == 3
//      1,23,456    "\1\2\3"  "\3\2"   1 <= 2 && 2 == 2 && 3 == 3
//     12,34,567    "\2\2\3"  "\3\2"   2 <= 2 && 2 == 2 && 3 == 3
//   1,34,56,789  "\1\2\2\3"  "\3\2"   1 <= 2 && 2 == 2 && 2 == 2 && 3 == 3

// Examples of invalid groups:
//
//      sequence        grps  grping
//
//           123        "\3"  "\1\2"   !(3 <= 1)
//        1,2,34    "\1\1\2"  "\1\3"   1 <= 1 && 1 == 1 && !(2 == 3)
//      1,23,456    "\1\2\3"  "\1\2"   1 <= 1 && 2 == 2 && !(3 == 2)

_RWSTD_EXPORT int
__rw_check_grouping (const char *grps,   _RWSTD_SIZE_T ngrps,
                     const char *grping, _RWSTD_SIZE_T ngrpings)
{
    if (!ngrps || !ngrpings)
        return 0;

    _RWSTD_ASSERT (0 != grps);
    _RWSTD_ASSERT (0 != grping);

    // current grouping number
    _RWSTD_SIZE_T grpn = ngrpings;

    // find the last non-zero grouping if it exists (see below)
    while (grpn-- != ngrpings && !grping [grpn]);

    // the index of the last meaningful grouping (e.g., `grphi' of
    // "\3\0\0\0" is 2 since the redundant NULs can be safely ignored)
    const _RWSTD_SIZE_T grphi = (_RWSTD_SIZE_T)(-1) == grpn ? 0 : grpn;

    grpn = 0;

    typedef const UChar* PCUChar;

    // iterate over elements of `groups' starting with the least significant
    // one (at the greatest index), comparing each against the relevant
    // element of `groupings' (at the lowest index)
    for (PCUChar g = _RWSTD_REINTERPRET_CAST (PCUChar, grps + ngrps - 1),
                 e = _RWSTD_REINTERPRET_CAST (PCUChar, grping); ; --g) {

        typedef const void* PVoid;

        if (PVoid (g) == PVoid (grps)) {
            // the most significant group may be shorter (but not empty)
            if (*g > *e || !*g) {
                // subtracting from 0 to avoid warnings about
                // applying the - operator to an unsigned type
                grpn = 0U - (grpn + 1U);
            }

            break;
        }

        if (*g != *e) {
            // all but the most significant group must be exactly
            // as long as the last expected group length
            grpn = 0U - (grpn + 1U);
            break;
        }

        if (grpn++ < grphi)
            ++e;   // `*e' is the expected length of the group
    }

    // negative return value indicates an error
    return int (grpn);
}


// array of 1-based indices for each 8-bit character into an internal
// table of values of each roman digit, i.e., I, V, X, L, C, D, and M
// elements with value greater than 7 do not correspond to a roman digit
_RWSTD_EXPORT extern const UChar
__rw_roman_inxs[] = {
    //        0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f
    //       NUL SOH STX ETX EOT ENQ ACK BEL BS  TAB LF  VT  FF  CR  SO  SI
    /*  0 */  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
    //       DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM  SUB ESC FS  GS  RS  US
    /*  1 */  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
    //       SPC  !   "   #   $   %   '   '   (   )   *   +   ,   -   .   /
    /*  2 */  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
    //        0   1   2   3   4   5   6   7   8   9   :   ;   <   =   >   ?
    /*  3 */  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
    //        @   A   B   C   D   E   F   G   H   I   J   K   L   M   N   O
    /*  4 */  9,  9,  9,  5,  6,  9,  9,  9,  9,  1,  9,  9,  4,  7,  9,  9,
    //        P   Q   R   S   T   U   V   W   X   Y   Z   [   \   ]   ^   _
    /*  5 */  9,  9,  9,  9,  9,  9,  2,  9,  3,  9,  9,  9,  9,  9,  9,  9,
    //        `   a   b   c   d   e   f   g   h   i   j   k   l   m   n   o
    /*  6 */  9,  9,  9,  5,  6,  9,  9,  9,  9,  1,  9,  9,  4,  7,  9,  9,
    //        p   q   r   s   t   u   v   w   x   y   z   {   |   }   ~  DEL
    /*  7 */  9,  9,  9,  9,  9,  9,  2,  9,  3,  9,  9,  9,  9,  9,  9,  9,
    // extended ascii below
    /*  8 */  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
    /*  9 */  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
    /*  a */  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
    /*  b */  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
    /*  c */  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
    /*  d */  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
    /*  e */  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,
    /*  f */  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9
};


static const int __rw_roman_digits[] = {
    //  1  2   3   4    5    6     7   8   9
    -1, 1, 5, 10, 50, 100, 500, 1000, -1, -1
};


// parse a string ot roman digits, produce a non-zero numeric value
// on success, 0 otherwise, set `end' to point one past the last
// successfully interpreted character
static unsigned long __rw_get_roman (const char *str)
{
    _RWSTD_ASSERT (str && *str);

#define ROMAN_DIGIT(c) __rw_roman_digits [__rw_roman_inxs [UChar (c)]]

    unsigned long val = 0;

    for (; str [1]; ++str) {
        const int digit_1 = ROMAN_DIGIT (str [0]);
        const int digit_2 = ROMAN_DIGIT (str [1]);

        if (digit_1 < digit_2)
            val -= digit_1;
        else
            val += digit_1;
    }

    val += ROMAN_DIGIT (*str);

    return val;
}


// validates a floating point value and errno value
template <class FloatT>
inline FloatT
__rw_validate (FloatT val, FloatT fmin, FloatT finf, char sign, int &err)
{
    if (ERANGE == err) {

        if (val < -(fmin)) {
            // negative overflow: store -infinity and set failbit
            val = -finf;
            err = _RWSTD_IOS_FAILBIT;
        }
        else if (val > (fmin)) {
            // positive overflow: store +infinity and set failbit
            val = finf;
            err = _RWSTD_IOS_FAILBIT;
        }
        else {   // val >= fmin && val <= max
            // benign underflow: store +/-fmin, clear failbit
            val = '-' == sign ? -(fmin) : (fmin);
            err = 0;
        }
    }
    else
        err = 0;

    return val;
}


_RWSTD_EXPORT int
__rw_get_num (void *pval, const char *buf, int type, int flags,
              const char *groups, _RWSTD_SIZE_T ngroups,
              const char *grouping, _RWSTD_SIZE_T ngroupings)
{
    // 22.2.2.1.2, p3: Stage 1
    int err = 0;   // errno value, ERANGE, or 0

    _RWSTD_ASSERT (0 != buf);
    _RWSTD_ASSERT ('+' == *buf || '-' == *buf);

    if (!(type & __rw_facet::_C_floating)) {

        //////////////////////////////////////////////////////////////
        // integral parsing (including void*)

        int base = unsigned (flags) >> _RWSTD_IOS_BASEOFF;

        if (!base)
            base = 10;

        union {
#ifdef _RWSTD_LONG_LONG

            _RWSTD_LONG_LONG           ll;
            unsigned _RWSTD_LONG_LONG ull;

#endif   // _RWSTD_LONG_LONG

            long           l;
            unsigned long ul;
        } val;

        if (1 == base) {
            // treat numeric (i.e., non-Roman) input in base-1 as decimal
            const UChar digit = __rw_digit_map [UChar (buf [1])];

            if (digit < 10) {
                val.ul = _RW::__rw_strtoul (buf, &err, 10);
            }
            else {
                _RWSTD_ASSERT ('+' == *buf);

                val.ul = __rw_get_roman (buf + 1);
            }

            val.l = _RWSTD_STATIC_CAST (long, val.ul);
        }
        else if (__rw_facet::_C_ullong == type) {

#ifdef _RWSTD_LONG_LONG

            val.ull = _RW::__rw_strtoull (buf, &err, base);

#else   // if !defined (_RWSTD_LONG_LONG)

            _RWSTD_ASSERT (!"logic error: unsigned long long not configured");

#endif   // _RWSTD_LONG_LONG
        }
        else if (__rw_facet::_C_llong == type) {

#ifdef _RWSTD_LONG_LONG

            val.ll = _RW::__rw_strtoll (buf, &err, base);

#else   // if !defined (_RWSTD_LONG_LONG)

            _RWSTD_ASSERT (!"logic error: long long not configured");

#endif   // _RWSTD_LONG_LONG
        }
        else if (type & __rw_facet::_C_signed || __rw_facet::_C_bool == type) {
            val.l = _RW::__rw_strtol (buf, &err, base);
        }
        else if (__rw_facet::_C_pvoid == type) {

#if defined (_RWSTD_LONG_LONG) && _RWSTD_PTR_SIZE > _RWSTD_LONG_SIZE
            // assume pointers fit into long long
            val.ull = _RW::__rw_strtoull (buf, &err, base);
#else
            val.ul = _RW::__rw_strtoul (buf, &err, base);
#endif
        }
        else {
            val.ul = _RW::__rw_strtoul (buf, &err, base);
        }

        if (_RWSTD_EINVAL == err)
            return _RWSTD_IOS_FAILBIT;

        _RWSTD_ASSERT (0 != pval);

        switch (type) {
        case __rw_facet::_C_bool:
            if (0 == val.l || 1 == val.l) {
                *_RWSTD_STATIC_CAST (bool*, pval) = 1 == val.l;
            }
            else
                err = ERANGE;

            break;

        case __rw_facet::_C_ushort:
            // detect and handle unsigned overflow
            if (val.ul > _RWSTD_USHRT_MAX) {
                val.ul = _RWSTD_USHRT_MAX;
                err    = ERANGE;
            }
            else {
                if ('-' == *buf)
                    val.ul = _RWSTD_USHRT_MAX - val.ul + 1;
            }

            *_RWSTD_STATIC_CAST (unsigned short*, pval) =
                _RWSTD_STATIC_CAST (unsigned short, val.ul);
            break;
                
        case __rw_facet::_C_short:
            // detect and handle overflow
            if (val.l < _RWSTD_SHRT_MIN) {
                val.l = long (_RWSTD_SHRT_MIN);
                err   = ERANGE;
            }
            else if (val.l > _RWSTD_SHRT_MAX) {
                val.l = long (_RWSTD_SHRT_MAX);
                err   = ERANGE;
            }

            *_RWSTD_STATIC_CAST (short*, pval) =
                _RWSTD_STATIC_CAST (short, val.l);
            break;

        case __rw_facet::_C_uint:

            // detect and handle unsigned overflow

#if _RWSTD_UINT_MAX < _RWSTD_ULONG_MAX

            if (val.ul > _RWSTD_UINT_MAX)

#else   // if _RWSTD_UINT_MAX >= _RWSTD_ULONG_MAX

            if (val.ul == _RWSTD_ULONG_MAX && ERANGE == err)

#endif   // _RWSTD_UINT_MAX < _RWSTD_ULONG_MAX

            {
                val.ul = _RWSTD_UINT_MAX;
            }
            else {
                if ('-' == *buf)
                    val.ul = _RWSTD_UINT_MAX - val.ul + 1;
            }

            *_RWSTD_STATIC_CAST (unsigned*, pval) = unsigned (val.ul);
            break;

        case __rw_facet::_C_int:

            // detect and handle overflow

#if _RWSTD_UINT_MAX < _RWSTD_ULONG_MAX

            if (val.l < _RWSTD_INT_MIN) {
                val.l = long (_RWSTD_INT_MIN);
                err   = ERANGE;
            }
            else if (val.l > _RWSTD_INT_MAX) {
                val.l = long (_RWSTD_INT_MAX);
                err   = ERANGE;
            }

#else   // if _RWSTD_UINT_MAX >= _RWSTD_ULONG_MAX

            if (ERANGE == err) {
                if (_RWSTD_LONG_MIN == val.l)
                    val.l = long (_RWSTD_INT_MIN);
                else if (_RWSTD_LONG_MAX == val.l)
                    val.l = long (_RWSTD_INT_MAX);
            }

#endif   // _RWSTD_UINT_MAX < _RWSTD_ULONG_MAX

            *_RWSTD_STATIC_CAST (int*, pval) = int (val.l);
            break;

        case __rw_facet::_C_ulong:
            *_RWSTD_STATIC_CAST (unsigned long*, pval) = val.ul;
            break;
            
        case __rw_facet::_C_long:
            *_RWSTD_STATIC_CAST (long*, pval) = val.l;
            break;

#ifdef _RWSTD_LONG_LONG

        case __rw_facet::_C_ullong:
            *_RWSTD_STATIC_CAST (unsigned _RWSTD_LONG_LONG*, pval) = val.ull;
            break;

        case __rw_facet::_C_llong:
            *_RWSTD_STATIC_CAST (_RWSTD_LONG_LONG*, pval) = val.ll;
            break;

#endif   // _RWSTD_LONG_LONG

        case __rw_facet::_C_pvoid:
            *_RWSTD_STATIC_CAST (void**, pval) =
#if defined (_RWSTD_LONG_LONG) && _RWSTD_PTR_SIZE > _RWSTD_LONG_SIZE
                _RWSTD_REINTERPRET_CAST (void*, val.ull);
#else
                _RWSTD_REINTERPRET_CAST (void*, val.ul);
#endif

            // disable grouping
            grouping = "";
            break;

        }

        if (err)
            err = _RWSTD_IOS_FAILBIT;
    }
    else {
        //////////////////////////////////////////////////////////////
        // floating point parsing

        // fail if the sequence is empty
        if (!*buf)
            return _RWSTD_IOS_FAILBIT;

        char *end;

        // save errno value, reset only if ERANGE to distinguish strtol()
        // failure from success if errno already set on function entry
        const int errno_save = errno;

        if (ERANGE == errno_save)
            errno = 0;

        _RWSTD_ASSERT (0 != pval);

        switch (type) {
        case __rw_facet::_C_float: {

#ifndef _RWSTD_NO_STRTOF

            // assumes `buf' is formatted for the current locale
            // specifically, affects the value of decimal_point
            float f = strtof (_RWSTD_CONST_CAST (char*, buf), &end);

            err = errno;

            _RWSTD_ASSERT (0 != end);

            if ('.' == *end || end == buf && '.' == buf [1]) {
                // on failure caused by an unrecognized decimal point
                // set teporarily the global locale to "C" and reparse
                __rw_setlocale loc ("C", _RWSTD_LC_NUMERIC);

                f   = strtof (_RWSTD_CONST_CAST (char*, buf), &end);
                err = errno;
            }

            // restore errno if it was reset above
            if (ERANGE == errno_save)
                errno = ERANGE;

            if (*end)
                return _RWSTD_IOS_FAILBIT;

            *_RWSTD_STATIC_CAST (float*, pval) =
                __rw_validate (f, float (_RWSTD_FLT_MIN),
                               __rw_flt_infinity, *buf, err);

            break;

#else   // if defined (_RWSTD_NO_STRTOF)

            // assumes `buf' is formatted for the current locale
            // specifically, affects the value of decimal_point
            double d = strtod (buf, &end);
            err = errno;

            // restore errno if it was reset above
            if (ERANGE == errno_save)
                errno = ERANGE;

            _RWSTD_ASSERT (0 != end);

            if ('.' == *end || end == buf && '.' == buf [1]) {
                // on failure caused by an unrecognized decimal point
                // set teporarily the global locale to "C" and reparse
                __rw_setlocale loc ("C", _RWSTD_LC_NUMERIC);

                d = strtod (buf, &end);
            }

            if (*end)
                return _RWSTD_IOS_FAILBIT;

            // handle (benign) overflow and underflow

            if (d < -double (_RWSTD_FLT_MAX)) {
                // negative overflow (magnitude is too large to represent)
                // store negative infinity and set failbit
                d   = -__rw_flt_infinity;
                err = _RWSTD_IOS_FAILBIT;
            }
            else if (d > double (_RWSTD_FLT_MAX)) {
                // positive overflow (magnitude is too large to represent)
                // store positive infinity and set failbit
                d   = __rw_flt_infinity;
                err = _RWSTD_IOS_FAILBIT;
            }
            else if (d > 0.0 && d < double (_RWSTD_FLT_MIN)) {
                // positive underflow (magnitude is too small to represent)
                // store FLT_MIN and clear failbit
                d   = _RWSTD_FLT_MIN;
                err = 0;
            }
            else if (d < 0.0 && d > -double (_RWSTD_FLT_MIN)) {
                // negative underflow (magnitude is too small to represent)
                // store -FLT_MIN and clear failbit
                d   = -_RWSTD_FLT_MIN;
                err = 0;
            }
            else
                err = 0;

            *_RWSTD_STATIC_CAST (float*, pval) =
                _RWSTD_STATIC_CAST (float, d);
            break;

#endif   // _RWSTD_NO_STRTOF

        }

#ifdef _RWSTD_NO_STRTOLD
#  if DBL_DIG == LDBL_DIG && DBL_MAX_EXP == LDBL_MAX_EXP

     // strtold() is not defined and the size and domain of long double
     // is the same as that of double; use strtod() instead of sscanf()
     // for better precision
#    define LDBL_SAME_AS_DBL

        case __rw_facet::_C_ldouble:

#  endif   // DBL_DIG == LDBL_DIG && DBL_MAX_EXP == LDBL_MAX_EXP
#endif   // _RWSTD_NO_STRTOLD

        case __rw_facet::_C_double: {
            // assumes `buf' is formatted for the current locale
            // specifically, affects the value of decimal_point
            double d = strtod (buf, &end);
            err = errno;

            _RWSTD_ASSERT (0 != end);

            if ('.' == *end || end == buf && '.' == buf [1]) {
                // on failure caused by an unrecognized decimal point
                // set teporarily the global locale to "C" and reparse
                __rw_setlocale loc ("C", _RWSTD_LC_NUMERIC);

                d   = strtod (buf, &end);
                err = errno;
            }

            // restore errno if it was reset above
            if (ERANGE == errno_save)
                errno = errno_save;

            if (*end)
                return _RWSTD_IOS_FAILBIT;

            *_RWSTD_STATIC_CAST (double*, pval) =
               __rw_validate (d, double (_RWSTD_DBL_MIN),
                              __rw_dbl_infinity, *buf, err);
            break;
        }


#ifndef LDBL_SAME_AS_DBL

        case __rw_facet::_C_ldouble: {

            typedef long double LDbl;

#  ifndef _RWSTD_NO_LONG_DOUBLE

            LDbl ld;

#    ifndef _RWSTD_NO_STRTOLD

            // assumes `buf' is formatted for the current locale
            // specifically, affects the value of decimal_point
#      if !defined (_RWSTD_OS_HP_UX) || !defined (_LONG_DOUBLE)

            // not HP-UX or HP-UX with gcc
            ld = strtold (buf, &end);

#      else   // UP-UX with _LONG_DOUBLE

            // HP-UX strtold() returns struct long_double
            // the macro _LONG_DOUBLE is #defined when the struct is defined
            // note that gcc's replacement <stdlib.h> may actually define the
            // function with the correct signature

            union {
                LDbl        ld;
                long_double words;
            } u;

            u.words = strtold (buf, &end);
            ld      = u.ld;

#        ifndef __ia64

            // inefficiently working around HP-UX/PA-RISC strtold("0")
            // bug #615: strtold("0", &end) fails to set the end pointer
            if (   end == buf
                && 0 == errno
                && (   '0' == *buf || '.' == *buf
                    || /* '+' or '-' */ 43 == __rw_digit_map [UChar (*buf)])) {

                char *tmpend;
                if (   0.0 == strtod (buf, &tmpend)
                    && '\0' == *tmpend && 0 == errno)
                    end = tmpend;
            }

#        endif   // __ia64
#      endif   // HP-UX, _LONG_DOUBLE

            err = errno;

            _RWSTD_ASSERT (0 != end);

            if ('.' == *end || end == buf && '.' == buf [1]) {
                // on failure caused by an unrecognized decimal point
                // set teporarily the global locale to "C" and reparse
                __rw_setlocale loc ("C", _RWSTD_LC_NUMERIC);

#      if !defined (_RWSTD_OS_HP_UX) || !defined (_LONG_DOUBLE)
                ld = strtold (buf, &end);
#      else   // HP-UX with _LONG_DOUBLE #defined
                u.words = strtold (buf, &end);
                ld      = u.ld;
#      endif   // HP-UX, _LONG_DOUBLE

                err = errno;
            }

            // restore errno if it was reset above
            if (ERANGE == errno_save)
                errno = errno_save;

            if (*end)
                return _RWSTD_IOS_FAILBIT;

#    else   // if defined (!_RWSTD_NO_STRTOLD)

            flags &=
            ~(_RWSTD_IOS_SHOWBASE | _RWSTD_IOS_SHOWPOS | _RWSTD_IOS_SHOWPOINT);

            char fmatbuf [32];

            const char* const fmt =
                __rw_get_stdio_fmat (fmatbuf, type, flags, -1 /* ignore */);

            // assumes `buf' is formatted for the current locale
            // specifically, affects the value of decimal_point
            const int n = sscanf (buf, fmt, pval);

            // restore errno if it was reset above
            if (ERANGE == errno_save)
                errno = errno_save;

            if (1 != n)
                return _RWSTD_IOS_FAILBIT;

            ld = *_RWSTD_STATIC_CAST (LDbl*, pval);

#    endif   // _RWSTD_NO_STRTOLD

            *_RWSTD_STATIC_CAST (LDbl*, pval) =
                __rw_validate (ld, LDbl (_RWSTD_LDBL_MIN),
                               __rw_ldbl_infinity, *buf, err);

#  endif   // _RWSTD_LONG_DOUBLE

            break;
        }

#endif   //  LDBL_SAME_AS_DBL
        }
    }

    _RWSTD_ASSERT (grouping);

    // 22.2.2.1.2, p12: check digit grouping, if any
    // note that grouping is optional and parsing fails due to bad format
    // only if the positions thousands_sep's do not match those specified
    // by grouping; in such cases, the value is still stored
    if (   *grouping && ngroups > 1
        && 0 > __rw_check_grouping (groups, ngroups, grouping, ngroupings)
        || !*grouping && ngroups > 1)
        err |= _RWSTD_IOS_FAILBIT;

    return err;
}


}   // namespace __rw
