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 From 5032d3d550207112dbf7b79cb55f2364dea6123d Mon Sep 17 00:00:00 2001 From: Martijn Jasperse Date: Fri, 1 Feb 2019 13:34:53 +1100 Subject: Factoring output padding code to ensure "nan" gets padded correctly --- printf.c | 76 ++++++++++++++++++++++++---------------------------------------- 1 file changed, 28 insertions(+), 48 deletions(-) diff --git a/printf.c b/printf.c index dd92831..7c346bf 100644 --- a/printf.c +++ b/printf.c @@ -186,11 +186,34 @@ 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) +// output the specified string in reverse, taking care of any zero-padding +static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags) { const size_t start_idx = idx; + + // pad spaces up to given width + if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { + for (size_t i = len; i < width; i++) { + out(' ', buffer, idx++, maxlen); + } + } + + // reverse string + while (len) out(buf[--len], buffer, idx++, maxlen); + // append pad spaces up to given width + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) { + out(' ', buffer, idx++, maxlen); + } + } + + return idx; +} + +// 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) +{ // pad leading zeros if (!(flags & FLAGS_LEFT)) { if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { @@ -238,26 +261,7 @@ static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t ma } } - // pad spaces up to given width - if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { - for (size_t i = len; i < width; i++) { - out(' ', buffer, idx++, maxlen); - } - } - - // reverse string - for (size_t i = 0U; i < len; i++) { - out(buf[len - i - 1U], buffer, idx++, maxlen); - } - - // append pad spaces up to given width - if (flags & FLAGS_LEFT) { - while (idx - start_idx < width) { - out(' ', buffer, idx++, maxlen); - } - } - - return idx; + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); } @@ -319,8 +323,6 @@ static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d 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; - char buf[PRINTF_FTOA_BUFFER_SIZE]; size_t len = 0U; double diff = 0.0; @@ -330,10 +332,7 @@ static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d // test for NaN if (value != value) { - out('n', buffer, idx++, maxlen); - out('a', buffer, idx++, maxlen); - out('n', buffer, idx++, maxlen); - return idx; + return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags); // ensure value is padded as required } // test for very large values @@ -445,26 +444,7 @@ static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d } } - // pad spaces up to given width - if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { - for (size_t i = len; i < width; i++) { - out(' ', buffer, idx++, maxlen); - } - } - - // reverse string - for (size_t i = 0U; i < len; i++) { - out(buf[len - i - 1U], buffer, idx++, maxlen); - } - - // append pad spaces up to given width - if (flags & FLAGS_LEFT) { - while (idx - start_idx < width) { - out(' ', buffer, idx++, maxlen); - } - } - - return idx; + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); } #if defined(PRINTF_SUPPORT_EXPONENTIAL) -- cgit v1.2.3 From 2019bc0192a1b69036b0659f642a9e7e6c426a32 Mon Sep 17 00:00:00 2001 From: Martijn Jasperse Date: Fri, 1 Feb 2019 13:52:24 +1100 Subject: Fix for left-justified %E formatting Adding %E/%G test cases to suite --- printf.c | 16 ++++++++++--- test/test_suite.cpp | 69 +++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/printf.c b/printf.c index 7c346bf..0ee5c3e 100644 --- a/printf.c +++ b/printf.c @@ -487,19 +487,25 @@ static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d } } // will everything fit? + unsigned int fwidth = width; if (width > minwidth) { // we didn't fall-back so subtract the characters required for the exponent - width -= minwidth; + fwidth -= minwidth; } else { // not enough characters, so go back to default sizing - width = 0; + fwidth = 0; + } + if ((flags & FLAGS_LEFT) && minwidth) { + // if we're padding on the right, DON'T pad the floating part + fwidth = 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); + const size_t start_idx = idx; + idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP); // output the exponent part if (minwidth) { @@ -507,6 +513,10 @@ static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d 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); + // might need to right-pad spaces + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) out(' ', buffer, idx++, maxlen); + } } return idx; } diff --git a/test/test_suite.cpp b/test/test_suite.cpp index 2103a5b..875fa82 100644 --- a/test/test_suite.cpp +++ b/test/test_suite.cpp @@ -346,6 +346,13 @@ TEST_CASE("- flag", "[]" ) { test::sprintf(buffer, "%0-15d", -42); REQUIRE(!strcmp(buffer, "-42 ")); + + test::sprintf(buffer, "%0-15.3e", -42.); + REQUIRE(!strcmp(buffer, "-4.200e+01 ")); + + test::sprintf(buffer, "%0-15.3g", -42.); + REQUIRE(!strcmp(buffer, "-42.0 ")); + } @@ -911,6 +918,15 @@ TEST_CASE("float padding neg numbers", "[]" ) { test::sprintf(buffer, "% 5.1f", -5.); REQUIRE(!strcmp(buffer, " -5.0")); + test::sprintf(buffer, "% 6.1g", -5.); + REQUIRE(!strcmp(buffer, " -5")); + + test::sprintf(buffer, "% 6.1e", -5.); + REQUIRE(!strcmp(buffer, "-5.0e+00")); + + test::sprintf(buffer, "% 10.1e", -5.); + REQUIRE(!strcmp(buffer, " -5.0e+00")); + // zero padding test::sprintf(buffer, "%03.1f", -5.); @@ -921,6 +937,9 @@ TEST_CASE("float padding neg numbers", "[]" ) { test::sprintf(buffer, "%05.1f", -5.); REQUIRE(!strcmp(buffer, "-05.0")); + + test::sprintf(buffer, "%010.1e", -5.); + REQUIRE(!strcmp(buffer, "-005.0e+00")); // zero padding no decimal point @@ -932,6 +951,12 @@ TEST_CASE("float padding neg numbers", "[]" ) { test::sprintf(buffer, "%03.0f", -5.); REQUIRE(!strcmp(buffer, "-05")); + + test::sprintf(buffer, "%07.0E", -5.); + REQUIRE(!strcmp(buffer, "-05E+00")); + + test::sprintf(buffer, "%03.0g", -5.); + REQUIRE(!strcmp(buffer, "-05")); } TEST_CASE("length", "[]" ) { @@ -1020,8 +1045,8 @@ TEST_CASE("length", "[]" ) { TEST_CASE("float", "[]" ) { char buffer[100]; - test::sprintf(buffer, "%.4f", NAN); // using the NAN macro of math.h - REQUIRE(!strcmp(buffer, "nan")); + test::sprintf(buffer, "%8f", NAN); // using the NAN macro of math.h + REQUIRE(!strcmp(buffer, " nan")); test::sprintf(buffer, "%.4f", 3.1415354); REQUIRE(!strcmp(buffer, "3.1415")); @@ -1090,9 +1115,39 @@ TEST_CASE("float", "[]" ) { test::sprintf(buffer, "a%-5.1fend", 0.5); REQUIRE(!strcmp(buffer, "a0.5 end")); - // out of range in the moment, need to be fixed by someone + test::sprintf(buffer, "%G", 12345.678); + REQUIRE(!strcmp(buffer, "12345.7")); + + test::sprintf(buffer, "%.7G", 12345.678); + REQUIRE(!strcmp(buffer, "12345.68")); + + test::sprintf(buffer, "%.5G", 123456789.); + REQUIRE(!strcmp(buffer, "1.2346E+08")); + + test::sprintf(buffer, "%.6G", 12345.); + REQUIRE(!strcmp(buffer, "12345.0")); + + test::sprintf(buffer, "%+12.4g", 123456789.); + REQUIRE(!strcmp(buffer, " +1.235e+08")); + + test::sprintf(buffer, "%.2G", 0.001234); + REQUIRE(!strcmp(buffer, "0.0012")); + + test::sprintf(buffer, "%+10.4G", 0.001234); + REQUIRE(!strcmp(buffer, " +0.001234")); + + test::sprintf(buffer, "%+012.4g", 0.00001234); + REQUIRE(!strcmp(buffer, "+001.234e-05")); + + test::sprintf(buffer, "%.3g", -1.2345e-308); + REQUIRE(!strcmp(buffer, "-1.23e-308")); + + test::sprintf(buffer, "%+.3E", 1.23e+308); + REQUIRE(!strcmp(buffer, "+1.230E+308")); + + // out of range for float: should switch to exp notation test::sprintf(buffer, "%.1f", 1E20); - REQUIRE(!strcmp(buffer, "")); + REQUIRE(!strcmp(buffer, "1.0e+20")); } @@ -1346,6 +1401,12 @@ TEST_CASE("misc", "[]" ) { test::sprintf(buffer, "%.*f", 2, 0.33333333); REQUIRE(!strcmp(buffer, "0.33")); + test::sprintf(buffer, "%.*g", 2, 0.33333333); + REQUIRE(!strcmp(buffer, "0.33")); + + test::sprintf(buffer, "%.*e", 2, 0.33333333); + REQUIRE(!strcmp(buffer, "3.33e-01")); + test::sprintf(buffer, "%.*d", -1, 1); REQUIRE(!strcmp(buffer, "1")); -- cgit v1.2.3 From 56d9df0bd3ff3161c86b387c36823a62472980e2 Mon Sep 17 00:00:00 2001 From: Martijn Jasperse Date: Fri, 1 Feb 2019 14:07:26 +1100 Subject: Added special-case handling to %E, added +inf and -inf handling, test cases --- printf.c | 17 ++++++++++++----- test/test_suite.cpp | 12 +++++++++++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/printf.c b/printf.c index 0ee5c3e..62cc52c 100644 --- a/printf.c +++ b/printf.c @@ -316,6 +316,7 @@ static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t #if defined(PRINTF_SUPPORT_FLOAT) +#include #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); @@ -330,10 +331,13 @@ static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d // powers of 10 static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; - // test for NaN - if (value != value) { - return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags); // ensure value is padded as required - } + // test for special values + if (value != value) + return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags); + if (value < -DBL_MAX) + return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags); + if (value > DBL_MAX) + return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4 : 3, width, flags); // 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 @@ -448,9 +452,12 @@ static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d } #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) { + // check for special values + if ((value != value)||(value > DBL_MAX)||(value < -DBL_MAX)) + return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags); + // determine the sign bool negative = value < 0; if (negative) value = -value; diff --git a/test/test_suite.cpp b/test/test_suite.cpp index 875fa82..27efa4e 100644 --- a/test/test_suite.cpp +++ b/test/test_suite.cpp @@ -1045,9 +1045,19 @@ TEST_CASE("length", "[]" ) { TEST_CASE("float", "[]" ) { char buffer[100]; - test::sprintf(buffer, "%8f", NAN); // using the NAN macro of math.h + // test special-case floats using math.h macros + test::sprintf(buffer, "%8f", NAN); REQUIRE(!strcmp(buffer, " nan")); + test::sprintf(buffer, "%8f", INFINITY); + REQUIRE(!strcmp(buffer, " inf")); + + test::sprintf(buffer, "%-8f", -INFINITY); + REQUIRE(!strcmp(buffer, "-inf ")); + + test::sprintf(buffer, "%+8e", INFINITY); + REQUIRE(!strcmp(buffer, " +inf")); + test::sprintf(buffer, "%.4f", 3.1415354); REQUIRE(!strcmp(buffer, "3.1415")); -- cgit v1.2.3 From 0fea7e5ae428857079f493d56cee50d58d0ed069 Mon Sep 17 00:00:00 2001 From: Martijn Jasperse Date: Fri, 1 Feb 2019 14:38:15 +1100 Subject: Fixed whitespace, added %e/%g to README.md --- README.md | 5 +++- printf.c | 80 +++++++++++++++++++++++++++++++-------------------------------- 2 files changed, 44 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 0b39488..f86b7e4 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Therefore I decided to write an own, final implementation which meets the follow - Support of decimal/floating number representation (with an own fast itoa/ftoa) - Reentrant and thread-safe, malloc free, no static vars/buffers - LINT and compiler L4 warning free, mature, coverity clean, automotive ready - - Extensive test suite (> 370 test cases) passing + - Extensive test suite (> 390 test cases) passing - Simply the best *printf* around the net - MIT license @@ -92,6 +92,8 @@ The following format specifiers are supported: | x | Unsigned hexadecimal integer (lowercase) | | X | Unsigned hexadecimal integer (uppercase) | | f or F | Decimal floating point | +| e or E | Scientific-notation (exponential) floating point | +| g or G | Scientific or decimal floating point | | c | Single character | | s | String of characters | | p | Pointer address | @@ -164,6 +166,7 @@ int length = sprintf(NULL, "Hello, world"); // length is set to 12 | PRINTF_NTOA_BUFFER_SIZE | 32 | ntoa (integer) conversion buffer size. This must be big enough to hold one converted numeric number _including_ leading zeros, normally 32 is a sufficient value. Created on the stack | | PRINTF_FTOA_BUFFER_SIZE | 32 | ftoa (float) conversion buffer size. This must be big enough to hold one converted float number _including_ leading zeros, normally 32 is a sufficient value. Created on the stack | | PRINTF_DISABLE_SUPPORT_FLOAT | undefined | Define this to disable floating point (%f) support | +| PRINTF_DISABLE_SUPPORT_EXPONENTIAL | undefined | Define this to disable exponential floating point (%e) support | | PRINTF_DISABLE_SUPPORT_LONG_LONG | undefined | Define this to disable long long (%ll) support | | PRINTF_DISABLE_SUPPORT_PTRDIFF_T | undefined | Define this to disable ptrdiff_t (%t) support | diff --git a/printf.c b/printf.c index 62cc52c..99eb7c6 100644 --- a/printf.c +++ b/printf.c @@ -106,7 +106,7 @@ #define FLAGS_LONG (1U << 8U) #define FLAGS_LONG_LONG (1U << 9U) #define FLAGS_PRECISION (1U << 10U) -#define FLAGS_ADAPT_EXP (1U << 11U) +#define FLAGS_ADAPT_EXP (1U << 11U) // output function type @@ -337,7 +337,7 @@ static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d if (value < -DBL_MAX) return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags); if (value > DBL_MAX) - return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4 : 3, width, flags); + return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4 : 3, width, flags); // 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 @@ -456,14 +456,14 @@ static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d { // check for special values if ((value != value)||(value > DBL_MAX)||(value < -DBL_MAX)) - return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags); + return _ftoa(out, buffer, idx, maxlen, value, prec, width, 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 + 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; @@ -475,23 +475,23 @@ static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d // 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); + // 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; - } + 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? unsigned int fwidth = width; @@ -499,12 +499,12 @@ static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d // we didn't fall-back so subtract the characters required for the exponent fwidth -= minwidth; } else { - // not enough characters, so go back to default sizing - fwidth = 0; + // not enough characters, so go back to default sizing + fwidth = 0; } if ((flags & FLAGS_LEFT) && minwidth) { - // if we're padding on the right, DON'T pad the floating part - fwidth = 0; + // if we're padding on the right, DON'T pad the floating part + fwidth = 0; } // rescale the float value @@ -516,14 +516,14 @@ static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d // 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); - // might need to right-pad spaces - if (flags & FLAGS_LEFT) { - while (idx - start_idx < width) out(' ', buffer, idx++, maxlen); - } + // 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); + // might need to right-pad spaces + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) out(' ', buffer, idx++, maxlen); + } } return idx; } @@ -716,20 +716,20 @@ 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; + 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 '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; + 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' : { -- cgit v1.2.3 From 496e5aa066d33490492d71baf39d271114fd9d42 Mon Sep 17 00:00:00 2001 From: Martijn Jasperse Date: Sun, 3 Feb 2019 10:37:54 +1100 Subject: Implemented custom log10/pow in _etoa to eliminate dependency on for %E format --- printf.c | 49 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/printf.c b/printf.c index 99eb7c6..9d70faf 100644 --- a/printf.c +++ b/printf.c @@ -316,12 +316,13 @@ static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t #if defined(PRINTF_SUPPORT_FLOAT) -#include +#include #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 +// internal ftoa for fixed decimal floating point 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) { char buf[PRINTF_FTOA_BUFFER_SIZE]; @@ -452,6 +453,8 @@ static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d } #if defined(PRINTF_SUPPORT_EXPONENTIAL) +// internal ftoa variant for exponential floating-point type +// contributed by Martijn Jasperse 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) { // check for special values @@ -462,29 +465,49 @@ static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d 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; } + // determine the decimal exponent + // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) + union { + uint64_t U; + double F; + } conv; + conv.F = value; + int exp2 = (int)((conv.U >> 52) & 0x07FF) - 1023; // effectively log2 + conv.U = (conv.U & ((1ULL << 52) - 1)) | (1023ULL << 52); // drop the exponent so conv.F is now in [1,2) + // now approximate log10 from the log2 integer part and an expansion of ln around 1.5 + int expval = (int)(0.1760912590558 + exp2*0.301029995663981 + (conv.F - 1.5)*0.289529654602168); + // now we want to compute 10^expval but we want to be sure it won't overflow + exp2 = (int)(expval*3.321928094887362 + 0.5); + double z = expval*2.302585092994046 - exp2*0.6931471805599453; + double z2 = z*z; + conv.U = (uint64_t)(exp2 + 1023) << 52; + // compute exp(z) using continued fractions + // https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex + conv.F *= 1 + 2*z/(2 - z + (z2/(6 + (z2/(10 + z2/14))))); + // correct for rounding errors + if (value < conv.F) { + expval--; + conv.F /= 10; + } + + // 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; + // 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)) { + // do we want to fall-back to "%f" mode? + if ((value >= 1e-4)&&(value < 1e6)) { 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 - + flags |= FLAGS_PRECISION; // make sure _ftoa respects precision // no characters in exponent minwidth = 0; expval = 0; @@ -508,7 +531,7 @@ static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, d } // rescale the float value - if (expval) value *= pow(10.0, -expval); + if (expval) value /= conv.F; // output the floating part const size_t start_idx = idx; -- cgit v1.2.3