| /* | |
| * Copyright (c) 2019, The Linux Foundation. All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions are | |
| * met: | |
| * * Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * * Redistributions in binary form must reproduce the above | |
| * copyright notice, this list of conditions and the following | |
| * disclaimer in the documentation and/or other materials provided | |
| * with the distribution. | |
| * * Neither the name of The Linux Foundation nor the names of its | |
| * contributors may be used to endorse or promote products derived | |
| * from this software without specific prior written permission. | |
| * | |
| * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED | |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT | |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS | |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR | |
| * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |
| * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN | |
| * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| #include "AEEstd.h" | |
| #include "AEEBufBound.h" | |
| #include "AEEsmath.h" | |
| #include "AEEStdErr.h" | |
| #include "std_dtoa.h" | |
| //#include "math.h" | |
| //============================================================================== | |
| // Macro definitions | |
| //============================================================================== | |
| #define ISDIGIT(c) ( (c) >= '0' && (c) <= '9') | |
| #define TOLOWER(c) ( (c) | 32 ) // works only for letters | |
| #define FAILED(b) ( (b) != AEE_SUCCESS ? TRUE : FALSE ) | |
| #define CLEANUP_ON_ERROR(b,l) if( FAILED( b ) ) { goto l; } | |
| #define ROUND(d, p) fp_round( d, p ) | |
| #define FP_POW_10(n) fp_pow_10(n) | |
| //============================================================================== | |
| // Type definitions | |
| //============================================================================== | |
| // Formatting flags | |
| #define FF_PLUS 1 // '+' | |
| #define FF_MINUS 2 // '-' | |
| #define FF_POUND 4 // '#' | |
| #define FF_BLANK 8 // ' ' | |
| #define FF_ZERO 16 // '0' | |
| typedef struct { | |
| // Parsed values (from "%..." expression) | |
| int flags; // FF_PLUS, FF_MINUS, etc. | |
| char cType; // d, s, c, x, X, etc. | |
| int32 nWidth; // number preceding '.' : controls padding | |
| int32 nPrecision; // number following '.' (-1 if not given) | |
| // Computed values | |
| const char * pszStr; // string holding prefix + value | |
| int nPrefix; // # of numeric prefix bytes in pszStr[] | |
| int nLen; // length of string (after prefix) | |
| int nNumWidth; // minimum numeric value size (pad with '0') | |
| } FieldFormat; | |
| typedef int (*pfnFormatFloat)(FieldFormat* me, double dNumber, char* pcBuffer); | |
| //============================================================================== | |
| // Function definitions | |
| //============================================================================== | |
| // Read an unsigned decimal integer | |
| // | |
| static int ScanDecimal(const char **ppsz) | |
| { | |
| int n = 0; | |
| const char *psz; | |
| for (psz = *ppsz; ISDIGIT(*psz); ++psz) { | |
| n = n*10 + (int) (*psz - '0'); | |
| } | |
| *ppsz = psz; | |
| return n; | |
| } | |
| #define FORMATNUMBER_SIZE 24 // octal: 22 + '0' + null ; decimal: 20 + sign + null | |
| // Convert number to string, setting computed fields in FieldFormat. | |
| // | |
| // pcBuf[] must have room for at least FORMATNUMBER_SIZE characters | |
| // return value: length of string. | |
| // | |
| static __inline void | |
| FormatNumber(FieldFormat *me, char pcBuf[FORMATNUMBER_SIZE], uint64 uNum64) | |
| { | |
| char cType = me->cType; | |
| const char *cpszDigits; | |
| char *pc = pcBuf; | |
| int nBase; | |
| char *pcRev; | |
| if (cType == 'p') { | |
| cType = 'X'; | |
| me->nPrecision = 8; | |
| } | |
| if (me->nPrecision >= 0) { | |
| me->nNumWidth = me->nPrecision; | |
| // Odd thing: '0' flag is ignored for numbers when precision is | |
| // specified. | |
| me->flags &= ~FF_ZERO; | |
| } else { | |
| me->nNumWidth = 1; | |
| } | |
| // Output prefix | |
| if (( 'd' == cType || 'i' == cType)) { | |
| if ((int64)uNum64 < 0) { | |
| *pc++ = '-'; | |
| uNum64 = (uint64)-(int64)uNum64; | |
| } else if (me->flags & FF_PLUS) { | |
| *pc++ = '+'; | |
| } else if (me->flags & FF_BLANK) { | |
| *pc++ = ' '; | |
| } | |
| } | |
| if ((me->flags & FF_POUND) && 0 != uNum64) { | |
| if ('x' == TOLOWER(cType)) { | |
| *pc++ = '0'; | |
| *pc++ = cType; | |
| } else if ('o' == cType) { | |
| *pc++ = '0'; | |
| // Odd thing about libc printf: "0" prefix counts as part of minimum | |
| // width, but "0x" prefix does not. | |
| --me->nNumWidth; | |
| } | |
| } | |
| me->nPrefix = pc - pcBuf; | |
| // Output unsigned numeric value | |
| nBase = ('o' == cType ? 8 : | |
| 'x' == TOLOWER(cType) ? 16 : | |
| 10); | |
| cpszDigits = ((cType == 'X') ? "0123456789ABCDEF" | |
| : "0123456789abcdef"); | |
| pcRev = pc; | |
| while (uNum64) { | |
| *pc++ = cpszDigits[uNum64 % (unsigned)nBase]; | |
| uNum64 /= (unsigned)nBase; | |
| } | |
| *pc = '\0'; | |
| me->pszStr = pcBuf; | |
| me->nLen = pc - pcRev; | |
| // Reverse string | |
| --pc; | |
| for (; pcRev < pc; ++pcRev, --pc) { | |
| char c = *pc; | |
| *pc = *pcRev; | |
| *pcRev = c; | |
| } | |
| } | |
| // | |
| // This function converts the input floating point number dNumber to an | |
| // ASCII string using either %f or %F formatting. This functions assumes | |
| // that dNumer is a valid floating point number (i.e., dNumber is NOT | |
| // +/-INF or NaN). The size of the output buffer pcBuffer should be at | |
| // least STD_DTOA_FORMAT_FLOAT_SIZE. | |
| // | |
| static int ConvertFloat(FieldFormat* me, double dNumber, char* pcBuffer, | |
| int nBufSize) | |
| { | |
| int nError = AEE_SUCCESS; | |
| int32 nPrecision = 0; | |
| int nIndex = 0; | |
| BufBound OutBuf; | |
| char szIntegerPart[STD_DTOA_FORMAT_INTEGER_SIZE] = {0}; | |
| char szFractionPart[STD_DTOA_FORMAT_FRACTION_SIZE] = {0}; | |
| int nExponent = 0; | |
| char cType = TOLOWER(me->cType); | |
| // Set the precision for conversion | |
| nPrecision = me->nPrecision; | |
| if (nPrecision < 0) { | |
| // No precision was specified, set it to the default value if the | |
| // format specifier is not %a | |
| if (cType != 'a') { | |
| nPrecision = STD_DTOA_DEFAULT_FLOAT_PRECISION; | |
| } | |
| } | |
| else if ((0 == nPrecision) && ('g' == cType)) { | |
| nPrecision = 1; | |
| } | |
| if (cType != 'a') { | |
| // For %g, check whether to use %e of %f formatting style. | |
| // Also, set the precision value accordingly since in this case the user | |
| // specified value is really the number of significant digits. | |
| // These next few steps should be skipped if the input number is 0. | |
| if (dNumber != 0.0) { | |
| nExponent = fp_log_10(dNumber); | |
| if ('g' == cType) { | |
| if ((nExponent < -4) || (nExponent >= nPrecision)) { | |
| cType = 'e'; | |
| nPrecision = nPrecision - 1; | |
| } | |
| else { | |
| cType = 'f'; | |
| nPrecision = nPrecision - nExponent - 1; | |
| } | |
| } | |
| // For %e, convert the number to the form d.ddd | |
| if ('e' == cType) { | |
| dNumber = dNumber / FP_POW_10(nExponent); | |
| } | |
| // Now, round the number to the specified precision | |
| dNumber = ROUND(dNumber, nPrecision); | |
| // For %e, the rounding operation may have resulted in a number dd.ddd | |
| // Reconvert it to the form d.ddd | |
| if (('e' == cType) && ((dNumber >= 10.0) || (dNumber <= -10.0))) { | |
| dNumber = dNumber / 10.0; | |
| nExponent++; | |
| } | |
| } | |
| // Convert the decmial number to string | |
| nError = std_dtoa_decimal(dNumber, nPrecision, szIntegerPart, szFractionPart); | |
| CLEANUP_ON_ERROR(nError, bail); | |
| } | |
| else | |
| { | |
| // Conver the hex floating point number to string | |
| nError = std_dtoa_hex(dNumber, nPrecision, me->cType, szIntegerPart, | |
| szFractionPart, &nExponent); | |
| CLEANUP_ON_ERROR(nError, bail); | |
| } | |
| // | |
| // Write the output as per the specified format. | |
| // First: Check for any prefixes that need to be added to the output. | |
| // The only possible prefixes are '-', '+' or ' '. The following rules | |
| // are applicable: | |
| // 1. One and only one prefix will be applicable at any time. | |
| // 2. If the number is negative, then '+' and ' ' are not applicable. | |
| // 3. For positive numbers, the prefix '+' takes precedence over ' '. | |
| // | |
| // In addition, we were dealing with a hex floating point number (%a), | |
| // then we need to write of the 0x prefix. | |
| // | |
| BufBound_Init(&OutBuf, pcBuffer, nBufSize); | |
| if (dNumber < 0.0) { | |
| // The '-' sign would have already been added to the szIntegerPart by | |
| // the conversion function. | |
| me->nPrefix = 1; | |
| } | |
| if (dNumber >= 0.0){ | |
| if (me->flags & FF_PLUS) { | |
| BufBound_Putc(&OutBuf, '+'); | |
| me->nPrefix = 1; | |
| } | |
| else if(me->flags & FF_BLANK) { | |
| BufBound_Putc(&OutBuf, ' '); | |
| me->nPrefix = 1; | |
| } | |
| } | |
| // For %a, write out the 0x prefix | |
| if ('a' == cType) { | |
| BufBound_Putc(&OutBuf, '0'); | |
| BufBound_Putc(&OutBuf, ('a' == me->cType) ? 'x' : 'X'); | |
| me->nPrefix += 2; | |
| } | |
| // Second: Write the integer part | |
| BufBound_Puts(&OutBuf, szIntegerPart); | |
| // Third: Write the decimal point followed by the fraction part. | |
| // For %g, we need to truncate the trailing zeros in the fraction. | |
| // Skip this if the '#' flag is specified | |
| if (!(me->flags & FF_POUND) && ('g' == TOLOWER(me->cType))) { | |
| for (nIndex = std_strlen(szFractionPart) - 1; | |
| (nIndex >= 0) && (szFractionPart[nIndex] == '0'); nIndex--) { | |
| szFractionPart[nIndex] = '\0'; | |
| } | |
| } | |
| // The decimal point is specified only if there are some decimal digits. | |
| // However, if the '#' format specifier is present then the decimal point | |
| // will be present. | |
| if ((me->flags & FF_POUND) || (*szFractionPart != 0)) { | |
| BufBound_Putc(&OutBuf, '.'); | |
| // Write the fraction part | |
| BufBound_Puts(&OutBuf, szFractionPart); | |
| } | |
| // For %e and %a, write out the exponent | |
| if (('e' == cType) || ('a' == cType)) { | |
| char* pcExpStart = NULL; | |
| char* pcExpEnd = NULL; | |
| char cTemp = 0; | |
| if ('a' == me->cType) { | |
| BufBound_Putc(&OutBuf, 'p'); | |
| } | |
| else if ('A' == me->cType) { | |
| BufBound_Putc(&OutBuf, 'P'); | |
| } | |
| else if (('e' == me->cType) || ('g' == me->cType)) { | |
| BufBound_Putc(&OutBuf, 'e'); | |
| } | |
| else { | |
| BufBound_Putc(&OutBuf, 'E'); | |
| } | |
| // Write the exponent sign | |
| if (nExponent < 0) { | |
| BufBound_Putc(&OutBuf, '-'); | |
| nExponent = -nExponent; | |
| } | |
| else { | |
| BufBound_Putc(&OutBuf, '+'); | |
| } | |
| // Write out the exponent. | |
| // For %e, the exponent should at least be two digits. | |
| // The exponent to be written will be at most 4 digits as any | |
| // overflow would have been take care of by now. | |
| if (BufBound_Left(&OutBuf) >= 4) { | |
| if ('e' == cType) { | |
| if (nExponent < 10) { | |
| BufBound_Putc(&OutBuf, '0'); | |
| } | |
| } | |
| pcExpStart = OutBuf.pcWrite; | |
| do { | |
| BufBound_Putc(&OutBuf, '0' + (nExponent % 10)); | |
| nExponent /= 10; | |
| } while (nExponent); | |
| pcExpEnd = OutBuf.pcWrite - 1; | |
| // Reverse the exponent | |
| for (; pcExpStart < pcExpEnd; pcExpStart++, pcExpEnd--) { | |
| cTemp = *pcExpStart; | |
| *pcExpStart = *pcExpEnd; | |
| *pcExpEnd = cTemp; | |
| } | |
| } | |
| } | |
| // Null-terminate the string | |
| BufBound_ForceNullTerm(&OutBuf); | |
| // Set the output parameters | |
| // We do not care if there was enough space in the output buffer or not. | |
| // The output would be truncated to a maximum length of | |
| // STD_DTOA_FORMAT_FLOAT_SIZE. | |
| me->pszStr = OutBuf.pcBuf; | |
| me->nLen = BufBound_ReallyWrote(&OutBuf) - me->nPrefix - 1; | |
| bail: | |
| return nError; | |
| } | |
| // | |
| // This is a wrapper function that converts an input floating point number | |
| // to a string based on a given format specifier %e, %f or %g. It first checks | |
| // if the specified number is a valid floating point number before calling | |
| // the function that does the conversion. | |
| // | |
| // The size of the output buffer pcBuffer should be at least STD_DTOA_FORMAT_FLOAT_SIZE. | |
| // | |
| static int FormatFloat(FieldFormat* me, double dNumber, | |
| char pcBuffer[STD_DTOA_FORMAT_FLOAT_SIZE]) | |
| { | |
| int nError = AEE_SUCCESS; | |
| FloatingPointType NumberType = FP_TYPE_UNKOWN; | |
| // Check for error conditions | |
| if (NULL == pcBuffer) { | |
| nError = AEE_EBADPARM; | |
| goto bail; | |
| } | |
| // Initialize the output params first | |
| me->nLen = 0; | |
| me->nPrefix = 0; | |
| // Check for special cases such as NaN and Infinity | |
| nError = fp_check_special_cases(dNumber, &NumberType); | |
| CLEANUP_ON_ERROR(nError, bail); | |
| switch(NumberType) { | |
| case FP_TYPE_NEGATIVE_INF: | |
| if (('E' == me->cType) || ('F' == me->cType) || ('G' == me->cType)) { | |
| me->nLen = std_strlcpy(pcBuffer, STD_DTOA_NEGATIVE_INF_UPPER_CASE, | |
| STD_DTOA_FORMAT_FLOAT_SIZE); | |
| } | |
| else { | |
| me->nLen = std_strlcpy(pcBuffer, STD_DTOA_NEGATIVE_INF_LOWER_CASE, | |
| STD_DTOA_FORMAT_FLOAT_SIZE); | |
| } | |
| // Don't pad with 0's | |
| me->flags &= ~FF_ZERO; | |
| break; | |
| case FP_TYPE_POSITIVE_INF: | |
| if (('E' == me->cType) || ('F' == me->cType) || ('G' == me->cType)) { | |
| me->nLen = std_strlcpy(pcBuffer, STD_DTOA_POSITIVE_INF_UPPER_CASE, | |
| STD_DTOA_FORMAT_FLOAT_SIZE); | |
| } | |
| else { | |
| me->nLen = std_strlcpy(pcBuffer, STD_DTOA_POSITIVE_INF_LOWER_CASE, | |
| STD_DTOA_FORMAT_FLOAT_SIZE); | |
| } | |
| // Don't pad with 0's | |
| me->flags &= ~FF_ZERO; | |
| break; | |
| case FP_TYPE_NAN: | |
| if (('E' == me->cType) || ('F' == me->cType) || ('G' == me->cType)) { | |
| me->nLen = std_strlcpy(pcBuffer, STD_DTOA_NAN_UPPER_CASE, | |
| STD_DTOA_FORMAT_FLOAT_SIZE); | |
| } | |
| else | |
| { | |
| me->nLen = std_strlcpy(pcBuffer, STD_DTOA_NAN_LOWER_CASE, | |
| STD_DTOA_FORMAT_FLOAT_SIZE); | |
| } | |
| // Don't pad with 0's | |
| me->flags &= ~FF_ZERO; | |
| break; | |
| case FP_TYPE_GENERAL: | |
| nError = ConvertFloat(me, dNumber, pcBuffer, | |
| STD_DTOA_FORMAT_FLOAT_SIZE); | |
| CLEANUP_ON_ERROR(nError, bail); | |
| break; | |
| default: | |
| // This should only happen if this function has been modified | |
| // to support other special cases and this block has not been | |
| // updated. | |
| nError = AEE_EFAILED; | |
| goto bail; | |
| } | |
| // Set the output parameters | |
| me->pszStr = pcBuffer; | |
| bail: | |
| return nError; | |
| } | |
| static int std_strlprintf_inner(char *pszDest, int nDestSize, | |
| const char *cpszFmt, AEEVaList args, | |
| pfnFormatFloat pfnFormatFloatFunc) | |
| { | |
| BufBound bb; | |
| const char *pcIn = cpszFmt; | |
| BufBound_Init(&bb, pszDest, nDestSize); | |
| for (;;) { | |
| FieldFormat ff; | |
| const char *pcEsc; | |
| char achBuf[FORMATNUMBER_SIZE]; | |
| char achBuf2[STD_DTOA_FORMAT_FLOAT_SIZE]; | |
| char cType; | |
| boolean bLong = 0; | |
| pcEsc = std_strchrend(pcIn, '%'); | |
| BufBound_Write(&bb, pcIn, pcEsc-pcIn); | |
| if (0 == *pcEsc) { | |
| break; | |
| } | |
| pcIn = pcEsc+1; | |
| //---------------------------------------------------- | |
| // Consume "%..." specifiers: | |
| // | |
| // %[FLAGS] [WIDTH] [.PRECISION] [{h | l | I64 | L}] | |
| //---------------------------------------------------- | |
| std_memset(&ff, 0, sizeof(FieldFormat)); | |
| ff.nPrecision = -1; | |
| // Consume all flags | |
| for (;;) { | |
| int f; | |
| f = (('+' == *pcIn) ? FF_PLUS : | |
| ('-' == *pcIn) ? FF_MINUS : | |
| ('#' == *pcIn) ? FF_POUND : | |
| (' ' == *pcIn) ? FF_BLANK : | |
| ('0' == *pcIn) ? FF_ZERO : 0); | |
| if (0 == f) { | |
| break; | |
| } | |
| ff.flags |= f; | |
| ++pcIn; | |
| } | |
| // Consume width | |
| if ('*' == *pcIn) { | |
| AEEVA_ARG(args, ff.nWidth, int32); | |
| pcIn++; | |
| } else { | |
| ff.nWidth = ScanDecimal(&pcIn); | |
| } | |
| if ((ff.flags & FF_MINUS) && ff.nWidth > 0) { | |
| ff.nWidth = -ff.nWidth; | |
| } | |
| // Consume precision | |
| if ('.' == *pcIn) { | |
| pcIn++; | |
| if ('*' == *pcIn) { // Can be *... (given in int * param) | |
| AEEVA_ARG(args, ff.nPrecision, int32); | |
| pcIn++; | |
| } else { | |
| ff.nPrecision = ScanDecimal(&pcIn); | |
| } | |
| } | |
| // Consume size designator | |
| { | |
| static const struct { | |
| char szPre[3]; | |
| boolean b64; | |
| } a[] = { | |
| { "l", 0, }, | |
| { "ll", 1, }, | |
| { "L", 1, }, | |
| { "j", 1, }, | |
| { "h", 0, }, | |
| { "hh", 0, }, | |
| { "z", 0 } | |
| }; | |
| int n = STD_ARRAY_SIZE(a); | |
| while (--n >= 0) { | |
| const char *psz = std_strbegins(pcIn, a[n].szPre); | |
| if ((const char*)0 != psz) { | |
| pcIn = psz; | |
| bLong = a[n].b64; | |
| break; | |
| } | |
| } | |
| } | |
| //---------------------------------------------------- | |
| // | |
| // Format output values | |
| // | |
| //---------------------------------------------------- | |
| ff.cType = cType = *pcIn++; | |
| if ('s' == cType) { | |
| // String | |
| char *psz; | |
| AEEVA_ARG(args, psz, char*); | |
| ff.pszStr = psz; | |
| ff.nLen = std_strlen(psz); | |
| if (ff.nPrecision >= 0 && ff.nPrecision < ff.nLen) { | |
| ff.nLen = ff.nPrecision; | |
| } | |
| } else if ('c' == cType) { | |
| // char | |
| AEEVA_ARG(args, achBuf[0], int); | |
| achBuf[1] = '\0'; | |
| ff.pszStr = achBuf; | |
| ff.nLen = 1; | |
| } else if ('u' == cType || | |
| 'o' == cType || | |
| 'd' == cType || | |
| 'i' == cType || | |
| 'p' == cType || | |
| 'x' == TOLOWER(cType) ) { | |
| // int | |
| uint64 uArg64; | |
| if (bLong) { | |
| AEEVA_ARG(args, uArg64, int64); // See how much room needed | |
| } else { | |
| uint32 uArg32; | |
| AEEVA_ARG(args, uArg32, int32); // See how much room needed | |
| uArg64 = uArg32; | |
| if ('d' == cType || 'i' == cType) { | |
| uArg64 = (uint64)(int64)(int32)uArg32; | |
| } | |
| } | |
| FormatNumber(&ff, achBuf, uArg64); | |
| } else if (pfnFormatFloatFunc && | |
| ('e' == TOLOWER(cType) || | |
| 'f' == TOLOWER(cType) || | |
| 'g' == TOLOWER(cType) || | |
| 'a' == TOLOWER(cType))) { | |
| // float | |
| int nError = AEE_SUCCESS; | |
| double dNumber; | |
| AEEVA_ARG(args, dNumber, double); | |
| nError = pfnFormatFloatFunc(&ff, dNumber, achBuf2); | |
| if (FAILED(nError)) { | |
| continue; | |
| } | |
| } else if ('\0' == cType) { | |
| // premature end | |
| break; | |
| } else { | |
| // Unknown type | |
| BufBound_Putc(&bb, cType); | |
| continue; | |
| } | |
| // FieldFormat computed variables + nWidth controls output | |
| if (ff.flags & FF_ZERO) { | |
| ff.nNumWidth = ff.nWidth - ff.nPrefix; | |
| } | |
| { | |
| int nLen1 = ff.nLen; | |
| int nLen2 = STD_MAX(ff.nNumWidth, nLen1) + ff.nPrefix; | |
| // Putnc() safely ignores negative sizes | |
| BufBound_Putnc(&bb, ' ', smath_Sub(ff.nWidth,nLen2)); | |
| BufBound_Write(&bb, ff.pszStr, ff.nPrefix); | |
| BufBound_Putnc(&bb, '0', smath_Sub(ff.nNumWidth, nLen1)); | |
| BufBound_Write(&bb, ff.pszStr+ff.nPrefix, nLen1); | |
| BufBound_Putnc(&bb, ' ', smath_Sub(-nLen2, ff.nWidth)); | |
| } | |
| } | |
| AEEVA_END(args); | |
| BufBound_ForceNullTerm(&bb); | |
| /* Return number of bytes required regardless if buffer bound was reached */ | |
| /* Note that we subtract 1 because the NUL byte which was added in | |
| BufBound_ForceNullTerm() is counted as a written byte; the semantics | |
| of both the ...printf() functions and the strl...() functions call for | |
| the NUL byte to be excluded from the count. */ | |
| return BufBound_Wrote(&bb)-1; | |
| } | |
| int std_vstrlprintf(char *pszDest, int nDestSize, | |
| const char *cpszFmt, | |
| AEEVaList args) | |
| { | |
| return std_strlprintf_inner(pszDest, nDestSize, cpszFmt, args, NULL); | |
| } | |
| int std_vsnprintf(char *pszDest, int nDestSize, | |
| const char *cpszFmt, | |
| AEEVaList args) | |
| /* | |
| Same as std_vstrlprintf with the additional support of floating point | |
| conversion specifiers - %e, %f, %g and %a | |
| */ | |
| { | |
| return std_strlprintf_inner(pszDest, nDestSize, cpszFmt, args, FormatFloat); | |
| } | |
| int std_strlprintf(char *pszDest, int nDestSize, const char *pszFmt, ...) | |
| { | |
| int nRet; | |
| AEEVaList args; | |
| AEEVA_START(args, pszFmt); | |
| nRet = std_vstrlprintf(pszDest, nDestSize, pszFmt, args); | |
| AEEVA_END(args); | |
| return nRet; | |
| } | |
| int std_snprintf(char *pszDest, int nDestSize, const char *pszFmt, ...) | |
| /* | |
| Same as std_strlprintf with the additional support of floating point | |
| conversion specifiers - %e, %f, %g and %a | |
| */ | |
| { | |
| int nRet; | |
| AEEVaList args; | |
| AEEVA_START(args, pszFmt); | |
| nRet = std_vsnprintf(pszDest, nDestSize, pszFmt, args); | |
| AEEVA_END(args); | |
| return nRet; | |
| } |