/***************************************************************************
 *
 * num_put.cpp
 *
 * $Id: num_put.cpp 651034 2008-04-23 20:04:15Z sebor $
 *
 ***************************************************************************
 *
 * 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-2008 Rogue Wave Software, Inc.
 * 
 **************************************************************************/

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

#include <limits>     // for numeric_limits

#include <stdio.h>    // for snprintf()
#include <string.h>   // for memmove(), memset()

#include <float.h>   // for _finite(), _fpclass(), _isnan(), _copysign()
#include <math.h>    // for isfinite(), isnan(), isinf(), signbit()

#ifndef _RWSTD_NO_IEEEFP_H
#  include <ieeefp.h>   // for fpclass(), isnan()
#endif   // _RWSTD_NO_IEEEFP_H

#include <loc/_num_put.h>

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


#ifndef _RWSTD_NO_SNPRINTF
   // cast away constness of the format argument to prevent errors
   // on platforms such as HP-UX 11.00 that incorrectly declare it
   // without the const qualifier
#  define _SNPRINTF(buf, size, fmt, arg)   \
     snprintf (buf, size, _RWSTD_CONST_CAST (char*, fmt), arg)
#else   // _RWSTD_NO_SNPRINTF
#  ifndef _RWSTD_NO_SNPRINTF_IN_LIBC

#    undef snprintf
#    define snprintf                         _RWSTD_LIBC_SYM (snprintf)
#    define _SNPRINTF(buf, size, fmt, arg)   snprintf (buf, size, fmt, arg)

extern "C" {

_RWSTD_DLLIMPORT int
snprintf (char*, _RWSTD_SIZE_T, const char*, ...) _LIBC_THROWS ();

}   // extern "C"

#  else   // if defined (_RWSTD_NO_SNPRINTF_IN_LIBC)
#    define _SNPRINTF(buf, ignore, fmt, arg) sprintf (buf, fmt, arg)
#  endif   // _RWSTD_NO_SNPRINTF_IN_LIBC
#endif   // _RWSTD_NO_SNPRINTF


_RWSTD_NAMESPACE (__rw) { 

static const char __rw_digits[] =
    "0123456789abcdefghijklmnopqrstuvwxyz"
    "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";


#if defined (_MSC_VER)

inline bool __rw_isfinite (double val) { return !!_finite (val); }

inline bool __rw_signbit (double val) { return 0 > _copysign (1., val); }

inline bool __rw_isinf (double val) {
    const int fpc = _fpclass (val);

    if (_FPCLASS_NINF == fpc) {
        // verify that __rw_signbit() correctly determines
        // the sign of negative infinity
        _RWSTD_ASSERT (__rw_signbit (val));
        return true;
    }
    else if (_FPCLASS_PINF == fpc) {
        // verify that __rw_signbit() correctly determines
        // the sign of positive infinity
        _RWSTD_ASSERT (!__rw_signbit (val));
        return true;
    }

    return false;
}

inline bool __rw_isnan (double val) { return !!_isnan (val); }

inline bool __rw_isqnan (double val) {
    return _FPCLASS_QNAN == _fpclass (val);
}

inline bool __rw_issnan (double val) {
    return _FPCLASS_SNAN == _fpclass (val);
}

#elif defined (_RWSTD_OS_SUNOS)

inline bool __rw_isfinite (double val) { return !!finite (val); }

inline bool __rw_signbit (double val)
{
    // implement own signbit() to avoid dragging in libm or libsunmath
    return _RWSTD_REINTERPRET_CAST (const _RWSTD_UINT64_T&, val) >> 63;
}

inline bool __rw_isinf (double val) {
    const int fpc = fpclass (val);

    if (FP_NINF == fpc) {
        // verify that __rw_signbit() correctly determines
        // the sign of negative infinity
        _RWSTD_ASSERT (__rw_signbit (val));
        return true;
    }
    else if (FP_PINF == fpc) {
        // verify that __rw_signbit() correctly determines
        // the sign of positive infinity
        _RWSTD_ASSERT (!__rw_signbit (val));
        return true;
    }

    return false;
}

inline bool __rw_isnan (double val) { return 0 != isnan (val); }

inline bool __rw_isqnan (double val) { return FP_QNAN == fpclass (val); }

inline bool __rw_issnan (double val) { return FP_SNAN == fpclass (val); }

#elif defined (fpclassify)

inline bool __rw_isfinite (double val) { return !!isfinite (val); }

inline bool __rw_signbit (double val) { return !!signbit (val); }

inline bool __rw_isinf (double val) { return !!isinf (val); }

inline bool __rw_isnan (double val) { return !!isnan (val); }

inline bool __rw_isqnan (double) { return false; }

inline bool __rw_issnan (double) { return false; }

#else

inline bool __rw_isfinite (double) { return true; }

inline bool __rw_signbit (double) { return false; }

inline bool __rw_isinf (double) { return false; }

inline bool __rw_isnan (double) { return false; }

inline bool __rw_isqnan (double) { return false; }

inline bool __rw_issnan (double) { return false; }

#endif


inline bool __rw_isfinite (float) { return true; }

inline bool __rw_signbit (float) { return false; }

inline bool __rw_isinf (float) { return false; }

inline bool __rw_isnan (float) { return false; }

inline bool __rw_isqnan (float) { return false; }

inline bool __rw_issnan (float) { return false; }


#ifndef _RWSTD_NO_LONG_DOUBLE

#  if _RWSTD_DBL_SIZE == _RWSTD_LDBL_SIZE

inline bool __rw_isfinite (long double x) { return __rw_isfinite (double (x)); }

inline bool __rw_signbit (long double x) { return __rw_signbit (double (x)); }

inline bool __rw_isinf (long double x) { return __rw_isinf (double (x)); }

inline bool __rw_isnan (long double x) { return __rw_isnan (double (x)); }

inline bool __rw_isqnan (long double x) { return __rw_isqnan (double (x)); }

inline bool __rw_issnan (long double x) { return __rw_issnan (double (x)); }

#  else   // _RWSTD_DBL_SIZE != _RWSTD_LDBL_SIZE

inline bool __rw_isfinite (long double) { return true; }

inline bool __rw_signbit (long double) { return false; }

inline bool __rw_isinf (long double) { return false; }

inline bool __rw_isnan (long double) { return false; }

inline bool __rw_isqnan (long double) { return false; }

inline bool __rw_issnan (long double) { return false; }

#  endif   // _RWSTD_DBL_SIZE == _RWSTD_LDBL_SIZE

#endif   // _RWSTD_NO_LONG_DOUBLE


static int
__rw_fmat_infinite (char *buf, size_t bufsize, double val, unsigned flags)
{
    _RWSTD_ASSERT (!__rw_isfinite (val));
    _RWSTD_ASSERT (5 <= bufsize);

    _RWSTD_UNUSED (bufsize); 

    char* end = buf;
    const bool cap = !!(flags & _RWSTD_IOS_UPPERCASE);

    if (__rw_isinf (val)) {
        if (__rw_signbit (val)) {
            *end++ = '-';
        }
        else if (flags & _RWSTD_IOS_SHOWPOS) {
            *end++ = '+';
        }

        const char str [] = "iInNfF";
        *end++ = str [cap + 0];
        *end++ = str [cap + 2];
        *end++ = str [cap + 4];
    }
    else {
        _RWSTD_ASSERT (__rw_isnan (val));
#if 0
        // disabled since not all platforms correctly handling sign of NaN's
        if (__rw_signbit (val)) {
            *end++ = '-';
        }
        else if (flags & _RWSTD_IOS_SHOWPOS) {
            *end++ = '+';
        }
#endif

        const char str [] = "nNaAqQsS";
        *end++ = str [cap + 0];
        *end++ = str [cap + 2];
        *end++ = str [cap + 0];
#if 0
        // disabled since not all platforms supporting 
        // the quiet and signaling NaN's
        if (__rw_isqnan (val))
            *end++ = str [cap + 4];
        else if (__rw_issnan (val))
            *end++ = str [cap + 6];
#endif
    }

    return int (end - buf);
}


#ifdef _RWSTD_LONG_LONG


typedef _RWSTD_LONG_LONG          _LLong;
typedef unsigned _RWSTD_LONG_LONG _ULLong;


static _RWSTD_SIZE_T
__rw_btoa (char *buf, _ULLong i, unsigned base)
{
    _RWSTD_ASSERT (base && base <= 36);

    const _RWSTD_SIZE_T dig = _STD::numeric_limits<_ULLong>::digits + 0U;

    char*             end   = buf + dig;
    const char* const begin = end;

    _ULLong tmp;
    do {
        tmp = i / base;

        // PA RISC has no integer divide, optimize the modulo operator
        // this is also faster than computing the modulo on i86, and
        // faster than calling ldiv()
        *--end = __rw_digits [i - tmp * base];
    } while ((i = tmp));

    const _RWSTD_SIZE_T len = begin - end;

   memmove (buf, end, len);

    return len;
}


static _RWSTD_SIZE_T
__rw_dtoa (char *buf, _ULLong i, unsigned flags)
{
    // get the maximum number of decimal digits for an unsigned long
    // with the largest magnitude, plus 1 for the (optional) sign
    const _RWSTD_SIZE_T dig10 = _STD::numeric_limits<_ULLong>::digits10 + 2U;

    char*             end   = buf + dig10;
    const char* const begin = end;

    _ULLong tmp;
    do {
        tmp = i / 10;

        // PA RISC has no integer divide, optimize the modulo operator
        // this is also faster than computing the modulo on i86, and
        // faster than calling ldiv()
        *--end = __rw_digits [i - tmp * 10];
    } while ((i = tmp));


    if (flags & _RWSTD_IOS_SHOWPOS)
        *--end = '+';

    const _RWSTD_SIZE_T len = begin - end;

   memmove (buf, end, len);

    return len;
}


static inline _RWSTD_SIZE_T
__rw_dtoa (char *buf, _LLong i, unsigned flags)
{
    if (i < 0) {
        flags  &= ~_RWSTD_IOS_SHOWPOS;
        *buf++  = '-';
        return 1 + __rw_dtoa (buf, _RWSTD_STATIC_CAST (_ULLong, -i), flags);
    }
        
    return __rw_dtoa (buf, _RWSTD_STATIC_CAST (_ULLong, i), flags);
}


static _RWSTD_SIZE_T
__rw_itoa (char *buf, _ULLong i, unsigned flags)
{
    const char* const pdigs = flags & _RWSTD_IOS_UPPERCASE ?
        __rw_digits + 36 : __rw_digits;

    const _LLong base = (flags >> _RWSTD_IOS_BASEOFF) - _LLong (1);

    int bits;

    char *end = buf;

    switch (base) {

    case  1: bits = 1; break;
    case  3: bits = 2; break;
    case  7:
        bits = 3;
        if (i && flags & _RWSTD_IOS_SHOWBASE)
            *end++ = '0';
        break;

    case 9:
        return __rw_dtoa (end, i, flags);

    case 15:
        bits = 4;
        if (i && flags & _RWSTD_IOS_SHOWBASE) {
            *end++ = '0';
            *end++ = pdigs [33];   // 'X' or 'x'
        }
        break;

    case 31: bits = 5; break;

    default:
        _RWSTD_ASSERT (base >= 0 && base <= _RWSTD_UINT_MAX);

        return __rw_btoa (buf, i, unsigned (base));
    }

    // maximum number of base-digits
    const int digits =
        (_STD::numeric_limits<_ULLong>::digits + bits - 1) / bits;

    int j;

    // skip leading zeros
    if (i) {
        for (j = digits - 1; j && !(i & (base << j * bits)); --j);
    }
    else
        j = 0;

    // format the remaining digits
    do {
        const int dig = int ((i & (base << j * bits)) >> j * bits);

        _RWSTD_ASSERT (dig >= 0 && dig < 36);

        *end++ = pdigs [dig];
    } while (j--);

    return end - buf;
}


static inline _RWSTD_SIZE_T
__rw_itoa (char *buf, _LLong i, unsigned flags)
{
    if (10 == flags >> _RWSTD_IOS_BASEOFF)
        return __rw_dtoa (buf, i, flags);

    return __rw_itoa (buf, _RWSTD_STATIC_CAST (_ULLong, i), flags);
}


#endif   // _RWSTD_LONG_LONG


static _RWSTD_SIZE_T
__rw_btoa (char *buf, unsigned long i, unsigned base)
{
    _RWSTD_ASSERT (base && base <= 36);

    const _RWSTD_SIZE_T dig = _STD::numeric_limits<unsigned long>::digits + 0U;

    char*             end   = buf + dig;
    const char* const begin = end;

    unsigned long tmp;
    do {
        tmp = i / base;

        // PA RISC has no integer divide, optimize the modulo operator
        // this is also faster than computing the modulo on i86, and
        // faster than calling ldiv()
        *--end = __rw_digits [i - tmp * base];
    } while ((i = tmp));

    const _RWSTD_SIZE_T len = begin - end;

   memmove (buf, end, len);

    return len;
}


static  inline _RWSTD_SIZE_T
__rw_dtoa (char *buf, unsigned long i, unsigned flags)
{
    // get the maximum number of decimal digits for an unsigned long
    // with the largest magnitude, plus 1 for the (optional) sign
    const _RWSTD_SIZE_T dig10 =
        _STD::numeric_limits<unsigned long>::digits10 + 2U;

    char*             end   = buf + dig10;
    const char* const begin = end;

    unsigned long tmp;
    do {
        tmp = i / 10;

        // PA RISC has no integer divide, optimize the modulo operator
        // this is also faster than computing the modulo on i86, and
        // faster than calling ldiv()
        *--end = __rw_digits [i - tmp * 10];
    } while ((i = tmp));


    // prepend a sign if necessary
    if (flags & _RWSTD_IOS_SHOWPOS)
        *--end = '+';

    // move the contents of the buffer to the beginning
    const _RWSTD_SIZE_T len = begin - end;

   memmove (buf, end, len);

    return len;
}


static inline _RWSTD_SIZE_T
__rw_dtoa (char *buf, long i, unsigned flags)
{
    if (i < 0) {
        flags  &= ~_RWSTD_IOS_SHOWPOS;
        *buf++  = '-';
        return 1 + __rw_dtoa (buf, _RWSTD_STATIC_CAST (unsigned long, -i),
                              flags);
    }
        
    return __rw_dtoa (buf, _RWSTD_STATIC_CAST (unsigned long, i), flags);
}


// convert unsigned long to a roman number
static _RWSTD_SIZE_T
__rw_utor (char *buf, unsigned long i, unsigned flags)
{
    //                          01234560123456
    static const char lit [] = "ivxlcdmIVXLCDM";

    if (0 == i || i > 4999)
        return __rw_dtoa (buf, i, flags);

    const char* const pdigs = flags & _RWSTD_IOS_UPPERCASE ? lit + 7 : lit;

    const char *begin = buf;

    for (; i >= 1000; i -= 1000)
        *buf++ = pdigs [6];   // 'M'

    for (unsigned long j = 0, ord = 100; j != 6; j += 2, ord /= 10) {

        unsigned long fact;

        if (i >= (fact = 9 * ord)) {
            *buf++ = pdigs [4 - j];   // {C,X,I}
            *buf++ = pdigs [6 - j];   // {M,C,X}
            i -= fact;
        }
        else if (i >= (fact = 5 * ord)) {
            *buf++ = pdigs [5 - j];   // {D,L,V}
            for (i -= fact; i >= ord; i -= ord)
                *buf++ = pdigs [4 - j];   // {C,X,I}
        }
        else if (i >= (fact = 4 * ord)) {
            *buf++ = pdigs [4 - j];   // {C,X,I}
            *buf++ = pdigs [5 - j];   // {D,L,V}
            i -= fact;
        }
        else {
            for (; i >= ord; i -= ord)
                *buf++ = pdigs [4 - j];   // {C,X,I}
        }
    }

    return buf - begin;
}


static _RWSTD_SIZE_T
__rw_itoa (char *buf, unsigned long i, unsigned flags)
{
    const char* const pdigs = flags & _RWSTD_IOS_UPPERCASE ?
        __rw_digits + 36 : __rw_digits;

    const unsigned basemask = (flags >> _RWSTD_IOS_BASEOFF) - 1;

    int bits;

    char *end = buf;

    switch (basemask) {
    case unsigned (-1): case 9:
        return __rw_dtoa (end, i, flags);

    case 0:
        return __rw_utor (end, i, flags);

    case  1: bits = 1; break;
    case  3: bits = 2; break;
    case  7:
        bits = 3;
        if (i && flags & _RWSTD_IOS_SHOWBASE)
            *end++ = '0';
        break;

    case 15:
        bits = 4;
        if (i && flags & _RWSTD_IOS_SHOWBASE) {
            *end++ = '0';
            *end++ = pdigs [33];   // 'X' or 'x'
        }
        break;

    case 31:
        bits = 5; break;

    default:
        return __rw_btoa (buf, i, basemask + 1);
    }

    int j;

    // skip leading zeros
    if (i) {
        // maximum number of digits in the given base (basemask + 1)
        const int digits =
            (_STD::numeric_limits<unsigned long>::digits + bits - 1) / bits;

        for (j = digits - 1; !(i & (long (basemask) << j * bits)); --j);
    }
    else
        j = 0;

    do {
        const unsigned dig = (i >> (j * bits)) & basemask;

        _RWSTD_ASSERT (dig <= basemask);

        *end++ = pdigs [dig];
    } while (j--);

    return end - buf;
}


static inline _RWSTD_SIZE_T
__rw_itoa (char *buf, long i, unsigned flags)
{
    if (10 == flags >> _RWSTD_IOS_BASEOFF)
        return __rw_dtoa (buf, i, flags);

    return __rw_itoa (buf, _RWSTD_STATIC_CAST (unsigned long, i), flags);
}


_RWSTD_EXPORT _RWSTD_SIZE_T
__rw_put_groups (char **pbuf, _RWSTD_SIZE_T len, _RWSTD_SIZE_T bufsize,
                 const char *end, const char *grouping)
{
    _RWSTD_ASSERT (0 != *pbuf);
    _RWSTD_ASSERT (0 != grouping);

    _RWSTD_UNUSED (bufsize);

    if (end) {
        // for floats, find the decimal point or one of { 'e', 'E' }
        end = strchr (*pbuf, '.');
        if (!end) {
            // try the comma in case sprintf() was used after
            // a call setlocale() that changed '.' to ','
            end = strchr (*pbuf, ',');
            if (!end)
                end = strpbrk (*pbuf, "Ee");
        }
    }

    if (!end) {
        // no decimal point for all other types
        end = *pbuf + len;
    }

    typedef unsigned char UChar;

    char *pd = *pbuf;   // pointer to the integral part (past any prefix)

    if (43 /* '-' or '+' */ == __rw_digit_map [UChar (*pd)])
        ++pd;
    else if ('0' == *pd) {
        ++pd;

        if (33 /* 'X' or 'x' */ == __rw_digit_map [UChar (*pd)])
            ++pd;
    }

    _RWSTD_SIZE_T ngrps  = 0;   // number of complete groups
    _RWSTD_SIZE_T grplen = 0;   // length of complete groups

    // compute the amount of buffer space needed for group separators
    const char *pg = grouping;
    for ( ; ; ++ngrps) {

        UChar grp = UChar (*pg);

        if (0 == grp) {
            // '\0' causes the last grouping to be repeated
            grp = UChar (pg [-1]);
        }
        else if (_RWSTD_CHAR_MAX == grp) {
            // _RWSTD_CHAR_MAX terminates the grouping algorithm
            break;
        }
        else
            ++pg;

        if (grplen + grp >= _RWSTD_STATIC_CAST (_RWSTD_SIZE_T, end - pd))
            break;

        grplen += grp;
    }

    pg   = grouping;   // reset to point to the first grouping
    end += ngrps;      // adjust the new position of the decimal point
    len += ngrps;      // adjust the length of buffer

    grplen = *pg++;    // length of the first grouping

    const bool no_groups = '\0' == **pbuf;

    // copy buffer onto itself starting with the rightmost digit,
    // inserting thousands_sep placeholders (';') according to
    // `grouping' in the integral portion of the number
    // using ';' rather than ',' to prevent potential collisions
    // with a comma inserted by sprintf() in some locales
    for (pd = *pbuf + (len - 1); pd != *pbuf && ngrps; --pd) {
        if (pd >= end) {
            // copy the fractional part (if any) of the number
            *pd = pd [-_RWSTD_STATIC_CAST (_RWSTD_PTRDIFF_T, ngrps)];
        }
        else {
            if ((_RWSTD_SIZE_T)(end - pd) == grplen + 1) {
                // insert throusands separator and compute
                // the offset of the next separator
                *pd = ';';
                --ngrps;

                if (_RWSTD_CHAR_MAX == *pg)
                    break;

                // '\0' forces the repetition of the last grouping
                grplen += (*pg ? *pg++ : pg [-1]) + 1;
            }
            else {
                // copy the integral part
                *pd = pd [-_RWSTD_STATIC_CAST (_RWSTD_PTRDIFF_T, ngrps)];
            }
        }
    }

    if (no_groups) {

        pd = *pbuf;

        // replace each group including the thousands_sep placeholder
        // in the buffer with its size
        for (char *ps = *pbuf, *last = *pbuf; ; ++pd) {

            if (pd == *pbuf + len) {
                // append the size of the last group, set `len', and break
                *ps++ = char (pd - last);
                len   = ps - *pbuf;
                break;
            }

            if (';' == *pd) {
                // replace group with it size
                *ps++ = char (pd - last);
                last  = pd + 1;
            }
        }
    }

    return len;
}


static inline void
__rw_fix_flt (char *&end, _RWSTD_SIZE_T &len,
              unsigned flags, _RWSTD_STREAMSIZE prec)
{
#if defined (_WIN32) || defined (_WIN64)

    char* beg = end - len;

    // workaround for the STDCXX-2 issue
    if (   _RWSTD_IOS_FIXED != (flags & _RWSTD_IOS_FLOATFIELD)
        && _RWSTD_IOS_SCIENTIFIC != (flags & _RWSTD_IOS_FLOATFIELD)
        && 2 < len
        && '0' == beg [0]
        && ('.' == beg [1] || ',' == beg [1])) {

        // for the 0.0 value the MSVC libc inserts redundant '0' character
        // when %g format is used in sprintf()
        const char* ptr;
        for (ptr = beg + 2; ptr != end && '0' == *ptr; ++ptr) ;

        if (ptr == end) {
            const _RWSTD_SIZE_T exp_len =
                0 > prec ? 7 : (1 < prec ? prec + 1 : 2);
            if (exp_len < len) {
                end = beg + exp_len;
                len = exp_len;
            }
        }
    }

    if (len > 5) {
        // make Win32 output conform to C99 printf() requirements
        // on the exponent: The exponent always contains at least
        // two digits, and only as many more digits as necessary
        // to represent the exponent.
        if (('e' == end [-5] || 'E' == end [-5]) && '0' == end [-3]) {
            end [-3] = end [-2];
            end [-2] = end [-1];
            --len;
            --end;
        }
        else if ('#' == end [-4]) {
            // may be #INF or #IND
            const bool cap = !!(flags & _RWSTD_IOS_UPPERCASE);

            if ('F' == end [-1]) {
                // assuming #INF
                // normalize the format of infinity to conform to C99

                const char str[] = "iInNfF";

                end [-6] = str [cap + 0];
                end [-5] = str [cap + 2];
                end [-4] = str [cap + 4];
            }
            else {
                // assuming #IND
                // normalize the format of NaN to conform to C99

                const char str[] = "nNaA";

                end [-6] = str [cap + 0];
                end [-5] = str [cap + 2];
                end [-4] = str [cap + 0];
            }

            end -= 3;
            len -= 3;
        }
        else if ('#' == end [-5]) {
            // normalize the format of NaN to conform to C99

            const char str[] = "nNaA";

            const bool cap = !!(flags & _RWSTD_IOS_UPPERCASE);

            end [-7] = str [cap + 0];
            end [-6] = str [cap + 2];
            end [-5] = str [cap + 0];
            end -= 4;
            len -= 4;
        }
    }

#else

    _RWSTD_UNUSED (prec);

    // normalize the format of infinity and NaN to one of
    // { INF, inf, NAN, nan, NANQ, nanq, NANS, nans }
    // for portability
    char* beg = end - len;

    typedef unsigned char UChar;

    // is there a sign?
    bool sgn = false;

    if (43 /* '+' or '-' */ == __rw_digit_map [UChar (*beg)]) {
        sgn = true;
        ++beg;
    }

    const char* pstr;

    switch (__rw_digit_map [UChar (*beg)]) {

    case 18 /* 'I' or 'i' */ :
        pstr = "iInNfF\0\0";
        len  = 3;
        break;

    case 23 /* 'N' or 'n' */ : {

        // distinguish between quiet and signaling NaN

        // FIXME: do this portably, regardless of printf output
        // (some libc, such as Solaris, output the same string
        // for both quiet and signaling NaN; others, such as
        // AIX, output NaNQ and NaNS, respectively)

        const UChar last = __rw_digit_map [UChar (*(end - 1))];
        if (26 /* 'Q' or 'q' */ == last) {
            pstr = "nNaAnNqQ";
            len  = 4;
        }
        else if (28 /* 'S' or 's' */ == last) {
            pstr = "nNaAnNsS";
            len  = 4;
        }
        else {
            pstr = "nNaAnN\0\0";
            len  = 3;
        }
        break;
    }

    default:
        return;
    }

    const bool cap = !!(flags & _RWSTD_IOS_UPPERCASE);

    beg [0] = pstr [cap + 0];
    beg [1] = pstr [cap + 2];
    beg [2] = pstr [cap + 4];
    beg [3] = pstr [cap + 6];

    end = beg + 3 + ('\0' != beg [3]);

    // increase the length by one for the sign
    if (sgn)
        ++len;

#endif   // _WIN{32,64}

}



_RWSTD_EXPORT _RWSTD_SIZE_T
__rw_put_num (char **pbuf, _RWSTD_SIZE_T bufsize,
              unsigned flags, int type, _RWSTD_STREAMSIZE prec,
              const void *pval, const char *grouping)
{
    char              fmtbuf [32];   // buffer for stdio format specifier
    const char       *fmt = 0;       // stdio format specifier
    char* const       buf = *pbuf;   // saved value of *pbuf
    char             *end = 0;       // decimal point
    _RWSTD_SIZE_T     len;           // length of formatted number
    _RWSTD_STREAMSIZE fpr;           // special case floating point precision

    typedef _RWSTD_SIZE_T SizeT;

    switch (type) {

    case __rw_facet::_C_bool:
        if (flags & _RWSTD_IOS_BOOLALPHA) {
            // no output
            len  = 0;
            *buf = '\0';
            break;
        }
        // else fall through...

    case __rw_facet::_C_short:
    case __rw_facet::_C_int:
    case __rw_facet::_C_long:
        len = __rw_itoa (buf, _RWSTD_REINTERPRET_CAST (long, pval), flags);
        break;

    case __rw_facet::_C_ushort:
    case __rw_facet::_C_uint:
    case __rw_facet::_C_ulong:
        // unsigned output behaves analogously printf() with %u
        // note that the plus in "%+u" is meaningless (i.e., the
        // sign is only used in signed conversions; 7.19 6.1, p6
        // of C99: The result of a signed conversion always begins
        // with a plus or minus sign.)
        len = __rw_itoa (buf, _RWSTD_REINTERPRET_CAST (unsigned long, pval),
                         flags & ~_RWSTD_IOS_SHOWPOS);
        break;

#ifdef _RWSTD_LONG_LONG

    case __rw_facet::_C_llong:
        len = __rw_itoa (buf, *(const _LLong*)pval, flags);
        break;

    case __rw_facet::_C_ullong:
        len = __rw_itoa (buf, *(const _ULLong*)pval,
                         flags & ~_RWSTD_IOS_SHOWPOS);
        break;

#endif   // _RWSTD_LONG_LONG

    case __rw_facet::_C_float | __rw_facet::_C_ptr: {
            const float fval = *(const float*)pval;
            if (!__rw_isfinite (fval)) {
                len = __rw_fmat_infinite (*pbuf, bufsize, fval, flags);
                end = *pbuf + len;
            }
            else {
                fpr = prec < 0 && flags & _RWSTD_IOS_FIXED ? 0 : prec;
                fmt = __rw_get_stdio_fmat (fmtbuf, type & ~__rw_facet::_C_ptr,
                                           flags, fpr);
                for (; ;) {
                    len = SizeT (_SNPRINTF (*pbuf, bufsize, fmt, fval));

                    if (len >= bufsize) {
                        if (*pbuf != buf)
                            delete[] *pbuf;

                        bufsize = len + 1 ? len + 1 : bufsize * 2;
                        *pbuf = new char [bufsize];
                    }
                    else {
                        _RWSTD_ASSERT (len > 0);

                        break;
                    }
                }

                end = *pbuf + len;

                // fix up output to conform to C99
                __rw_fix_flt (end, len, flags, fpr);
            }
        }
        break;

    case __rw_facet::_C_double | __rw_facet::_C_ptr: {
            const double dval = *(const double*)pval;
            if (!__rw_isfinite (dval)) {
                len = __rw_fmat_infinite (*pbuf, bufsize, dval, flags);
                end = *pbuf + len;
            }
            else {
                fpr = prec < 0 && flags & _RWSTD_IOS_FIXED ? 0 : prec;
                fmt = __rw_get_stdio_fmat (fmtbuf, type & ~__rw_facet::_C_ptr,
                                           flags, fpr);

                for ( ; ; ) {
                    len = SizeT (_SNPRINTF (*pbuf, bufsize, fmt, dval));

                    if (len >= bufsize) {
                        if (*pbuf != buf)
                            delete[] *pbuf;

                        bufsize = len + 1 ? len + 1 : bufsize * 2;
                        *pbuf = new char [bufsize];
                    }
                    else {
                        _RWSTD_ASSERT (len > 0);

                        break;
                    }
                }

                end = *pbuf + len;

                // fix up output to conform to C99
                __rw_fix_flt (end, len, flags, fpr);
            }
        }
        break;

#ifndef _RWSTD_NO_LONG_DOUBLE

    case __rw_facet::_C_ldouble | __rw_facet::_C_ptr: {
            const long double ldval = *(const long double*)pval;
            if (!__rw_isfinite (ldval)) {
                len = __rw_fmat_infinite (*pbuf, bufsize, ldval, flags);
                end = *pbuf + len;
            }
            else {
                fpr = prec < 0 && flags & _RWSTD_IOS_FIXED ? 0 : prec;
                fmt = __rw_get_stdio_fmat (fmtbuf, type & ~__rw_facet::_C_ptr,
                                           flags, fpr);

                for ( ; ; ) {
                    len = SizeT (_SNPRINTF (*pbuf, bufsize, fmt, ldval));
                    if (len >= bufsize) {
                        if (*pbuf != buf)
                            delete[] *pbuf;

                        bufsize = len + 1 ? len + 1 : bufsize * 2;
                        *pbuf = new char [bufsize];
                    }
                    else {
                        _RWSTD_ASSERT (len > 0);

                        break;
                    }
                }

                end = *pbuf + len;

                // fix up output to conform to C99
                __rw_fix_flt (end, len, flags, fpr);
            }
        }
        break;

#endif   // _RWSTD_NO_LONG_DOUBLE

    case __rw_facet::_C_pvoid:
        if (pval) {
            fmt = __rw_get_stdio_fmat (fmtbuf, type, flags, prec);
            len = SizeT (_SNPRINTF (buf, bufsize, fmt, pval));
        }
        else {
            // prevent GNU libc from formatting null pointers as "(nil)"
            *buf = '0';
            len  = 1;
        }

        // prevent grouping
        grouping = "";
        break;

    default:
        _RWSTD_ASSERT (!"logic error: bad type");
        len = 0;   // silence used before initialized warnings
    }

    if (prec < 0 && flags & _RWSTD_IOS_FIXED) {

        // extension: when ios::fixed is set, negative precision inserts
        // a decimal_point placeholder `prec' positions before the least
        // significant digit

        const bool sign = '-' == **pbuf || '+' == **pbuf;

        char* const beg = *pbuf + sign;

        char *src;   // first of the digits to move
        char *dst;   // where to move the first digit
        char *dp;    // where to insert the decimal point

        const _RWSTD_PTRDIFF_T ndigs = len - sign;

        _RWSTD_SIZE_T nfill;   // the number of 0's to insert
        _RWSTD_SIZE_T nmove;   // the number of digits to move

        if (-prec >= ndigs) {

            // insert 0's in addition to the decimal point
            // e.g., "1234" with prec == -6 yields "0.01234"

            nmove = ndigs;
            nfill = -prec - ndigs + 2;

            dst = beg + nfill;
            src = beg;
            dp  = beg + 1;

            // adjust the length of the block
            len += nfill;
        }
        else {

            // insert decimal point into the string of digits
            // e.g., "1234" with prec == -3 yields "1.234"

            nfill = 0;
            nmove = SizeT (-prec);

            src = beg + (ndigs + prec);
            dst = src + 1;
            dp  = src;

            ++len;   // grow the length
        }

        // move a block of digits to make room for 0's, if any,
        // and the decimal point placeholder
       memmove (dst, src, nmove);
       memset (beg, '0', nfill);

        *dp = '.';
        end = *pbuf + len;
    }

    // insert commas as thousands separators if grouping is required
    // integral and floating point types only (i.e., not for pointers)
    if (len > 1 && *grouping && _RWSTD_CHAR_MAX != *grouping)
        len = __rw_put_groups (pbuf, len, bufsize, end, grouping);

    // verify that the buffer hasn't overrun
    _RWSTD_ASSERT (len <= bufsize);

    return len;
}


}   // namespace __rw
