blob: c927dc8f7b1687434d6e57f6ffa844efce92110e [file] [log] [blame]
/*
* 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;
}