aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartijn Jasperse <m.jasperse@gmail.com>2019-02-01 13:33:07 +1100
committerMartijn Jasperse <m.jasperse@gmail.com>2019-02-01 13:33:07 +1100
commit0f97cc14633ca575a19c38e85f0600a03ad33e8a (patch)
tree729e7a2e7fa70702fc768976bd7a1433cd49a1c0
parent80b42fef5f070c980584b45de7b4a84322c749a0 (diff)
downloadprintf-0f97cc14633ca575a19c38e85f0600a03ad33e8a.tar.gz
printf-0f97cc14633ca575a19c38e85f0600a03ad33e8a.tar.bz2
printf-0f97cc14633ca575a19c38e85f0600a03ad33e8a.zip
Implementing %E and %G formatting specifications
-rw-r--r--printf.c123
1 files 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 <math.h>
+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;