/* * 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; }