From 0f97cc14633ca575a19c38e85f0600a03ad33e8a Mon Sep 17 00:00:00 2001 From: Martijn Jasperse Date: Fri, 1 Feb 2019 13:33:07 +1100 Subject: Implementing %E and %G formatting specifications --- printf.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 111 insertions(+), 12 deletions(-) diff --git a/printf.c b/printf.c index d73da2f..dd92831 100644 --- a/printf.c +++ b/printf.c @@ -64,6 +64,21 @@ #define PRINTF_SUPPORT_FLOAT #endif +// support for exponential floating point notation (%e/%g) +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL +#define PRINTF_SUPPORT_EXPONENTIAL +#endif + +// define the default floating point precision +#ifndef PRINTF_DEFAULT_FLOAT_PRECISION +#define PRINTF_DEFAULT_FLOAT_PRECISION 6U +#endif + +// define the largest float suitable to print with %f +#ifndef PRINTF_MAX_FLOAT +#define PRINTF_MAX_FLOAT 1e9 +#endif + // support for the long long types (%llu or %p) // default: activated #ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG @@ -91,6 +106,7 @@ #define FLAGS_LONG (1U << 8U) #define FLAGS_LONG_LONG (1U << 9U) #define FLAGS_PRECISION (1U << 10U) +#define FLAGS_ADAPT_EXP (1U << 11U) // output function type @@ -170,7 +186,6 @@ static unsigned int _atoi(const char** str) return i; } - // internal itoa format static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags) { @@ -297,6 +312,11 @@ static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t #if defined(PRINTF_SUPPORT_FLOAT) +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT +static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags); +#endif + static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) { const size_t start_idx = idx; @@ -305,9 +325,6 @@ static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d size_t len = 0U; double diff = 0.0; - // if input is larger than thres_max, revert to exponential - const double thres_max = (double)0x7FFFFFFF; - // powers of 10 static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; @@ -319,6 +336,16 @@ static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d return idx; } + // test for very large values + // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad + if ((value > PRINTF_MAX_FLOAT)||(value < -PRINTF_MAX_FLOAT)) { +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + return _etoa(out, buffer, idx, maxlen, value, prec, width, flags); +#else + return 0U; +#endif + } + // test for negative bool negative = false; if (value < 0) { @@ -326,9 +353,9 @@ static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d value = 0 - value; } - // set default precision to 6, if not set explicitly + // set default precision, if not set explicitly if (!(flags & FLAGS_PRECISION)) { - prec = 6U; + prec = PRINTF_DEFAULT_FLOAT_PRECISION; } // limit precision to 9, cause a prec >= 10 can lead to overflow errors while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) { @@ -356,12 +383,6 @@ static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d ++frac; } - // TBD: for very large numbers switch back to native sprintf for exponentials. Anyone want to write code to replace this? - // Normal printf behavior is to print EVERY whole number digit which can be 100s of characters overflowing your buffers == bad - if (value > thres_max) { - return 0U; - } - if (prec == 0U) { diff = value - (double)whole; if (diff > 0.5) { @@ -445,6 +466,72 @@ static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d return idx; } + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +#include +static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) +{ + // determine the sign + bool negative = value < 0; + if (negative) value = -value; + + // determine the decimal exponent + int expval = (int)floor(log10(value)); // "value" must be +ve + + // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters + unsigned int minwidth = ((expval < 100)&&(expval > -100)) ? 4 : 5; + + // default precision + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + + // in "%g" mode, "prec" is the number of *significant figures* not decimals + if (flags & FLAGS_ADAPT_EXP) { + // do we want to fall-back to "%f" mode for small number? + if ((expval > -5)&&(expval < 6)) { + if ((int)prec > expval) { + prec = (unsigned)((int)prec - expval - 1); + } else { + prec = 0; + } + // TODO: there's also a special case where we're supposed to ELIMINATE digits from the whole part + flags |= FLAGS_PRECISION; // make sure _ftoa respects precision + + // no characters in exponent + minwidth = 0; + expval = 0; + } else { + // we use one sigfig for the whole part + if ((prec > 0)&&(flags & FLAGS_PRECISION)) --prec; + } + } + // will everything fit? + if (width > minwidth) { + // we didn't fall-back so subtract the characters required for the exponent + width -= minwidth; + } else { + // not enough characters, so go back to default sizing + width = 0; + } + + // rescale the float value + if (expval) value *= pow(10.0, -expval); + + // output the floating part + idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, width, flags & ~FLAGS_ADAPT_EXP); + + // output the exponent part + if (minwidth) { + // output the exponential symbol + out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen); + // output the exponent value + idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS); + } + return idx; +} + +#endif // PRINTF_SUPPORT_EXPONENTIAL #endif // PRINTF_SUPPORT_FLOAT @@ -632,9 +719,21 @@ static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const #if defined(PRINTF_SUPPORT_FLOAT) case 'f' : case 'F' : + if (*format == 'F') flags |= FLAGS_UPPERCASE; idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); format++; break; +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + case 'e': + case 'E': + case 'g': + case 'G': + if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP; + if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE; + idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); + format++; + break; +#endif // PRINTF_SUPPORT_EXPONENTIAL #endif // PRINTF_SUPPORT_FLOAT case 'c' : { unsigned int l = 1U; -- cgit v1.2.3