diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 00000000..0bcb5e98 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,41 @@ +--- +# TODO(jkff): Re-enable google-readability-casting: it can be annoying, but it +# can catch some nasty bugs. E.g. C-style casts happily cast a pointer to the +# wrong type. +Checks: > + *, + -*-magic-numbers, + -hicpp-*, + -cppcoreguidelines-*, + -fuchsia-*, + -clion-*, + -cert-*, + -readability-named-parameter, + -llvm-header-guard, + -google-readability-todo, + -misc-unused-parameters, + -*-braces-around-statements, + -google-readability-casting, + -readability-else-after-return, + -modernize-use-auto, + -modernize-use-trailing-return-type, + -modernize-deprecated-headers, + -llvm-include-order, + -modernize-avoid-c-arrays, + -readability-uppercase-literal-suffix, + -bugprone-narrowing-conversions, + -readability-isolate-declaration, + -readability-convert-member-functions-to-static, + -modernize-use-default-member-init, + -misc-non-private-member-variables-in-classes, + -readability-implicit-bool-conversion, + -modernize-use-using, + -modernize-use-nodiscard, + -readability-non-const-parameter, + -google-runtime-int, + -clang-analyzer-security.FloatLoopCounter + +WarningsAsErrors: '*' + +# TODO: Add naming style checks (readability-identifier-naming). +... diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..68b1ae9c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.4.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files diff --git a/Makefile b/Makefile index 7b28a1a1..d48c9189 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------------ -# +# # Generic Makefile # # Copyright Marco Paland 2007 - 2017 @@ -7,16 +7,18 @@ # # ------------------------------------------------------------------------------ +SHELL := /bin/bash + # ------------------------------------------------------------------------------ # Paths # ------------------------------------------------------------------------------ PATH_TOOLS_CC = /usr/bin/ PATH_TOOLS_CC_LIB = /usr/lib/ -PATH_TOOLS_UTIL = +PATH_TOOLS_UTIL = PATH_BIN = bin PATH_TMP = tmp -PATH_NUL = /dev/null +PATH_NUL = /dev/stderr PATH_OBJ = $(PATH_TMP)/obj PATH_LST = $(PATH_TMP)/lst PATH_ERR = $(PATH_TMP)/err @@ -50,10 +52,13 @@ FILES_PRJ = test/test_suite # -Iinclude_path3 \ # ------------------------------------------------------------------------------ -C_INCLUDES = +C_INCLUDES = -C_DEFINES = +C_DEFINES = +ifndef $(CC_STD) + CC_STD = -std=c++14 +endif # ------------------------------------------------------------------------------ # The target name and location @@ -107,7 +112,7 @@ SED = $(PATH_TOOLS_UTIL)sed GCCFLAGS = $(C_INCLUDES) \ $(C_DEFINES) \ - -std=c++11 \ + $(CC_STD) \ -g \ -Wall \ -pedantic \ @@ -166,7 +171,7 @@ LFLAGS = $(GCCFLAGS) \ # Main-Dependencies (app: all) # ------------------------------------------------------------------------------ .PHONY: all -all: clean_prj $(TRG) $(TRG)_nm.txt +all: clean_prj $(TRG) $(TRG)_nm.txt diagnostics checks cov_app # ------------------------------------------------------------------------------ @@ -223,49 +228,99 @@ version: # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ -# Link/locate application +# tests and lint-like checkers # ------------------------------------------------------------------------------ -$(TRG) : $(FILES_O) - @-$(ECHO) +++ linkink application to generate: $(TRG) - @-$(CL) $(LFLAGS) -L. -lc $(PATH_OBJ)/*.o -Wl,-Map,$(TRG).map -o $(TRG) - # profiling - @-$(CL) $(LFLAGS) -L. -lc $(PATH_COV)/*.o --coverage -o $(PATH_COV)/$(APP) +.PHONY: most +most : $(TRG) tests lints + +.PHONY: lints +lints : cppcheck tidy + +.PHONY: cppcheck +cppcheck : printf.cppcheck.out + +.PHONY: tidy +tidy : test/test_suite.tidy.out + +.PHONY: tests +tests : $(TRG) + $(TRG) -r compact + +.PHONY: tests_verbose +tests_verbose : $(TRG) + $(TRG) -r compact -s +%.cppcheck.out: %.cpp + cppcheck --enable=warning,style --inline-suppr $< > $@ 2>&1 + +test/%.tidy.out: test/%.cpp + clang-tidy $< > $@ 2>&1 # ------------------------------------------------------------------------------ # parse the object files to obtain symbol information, and create a size summary # ------------------------------------------------------------------------------ $(TRG)_nm.txt : $(TRG) @-$(ECHO) +++ parsing symbols with nm to generate: $(TRG)_nm.txt - @-$(NM) --numeric-sort --print-size $(TRG) > $(TRG)_nm.txt + $(NM) --numeric-sort --print-size $(TRG) > $(TRG)_nm.txt @-$(ECHO) +++ demangling symbols with c++filt to generate: $(TRG)_cppfilt.txt - @-$(NM) --numeric-sort --print-size $(TRG) | $(CPPFILT) > $(TRG)_cppfilt.txt + $(NM) --numeric-sort --print-size $(TRG) | $(CPPFILT) > $(TRG)_cppfilt.txt @-$(ECHO) +++ creating size summary table with size to generate: $(TRG)_size.txt - @-$(SIZE) -A -t $(TRG) > $(TRG)_size.txt + $(SIZE) -A -t $(TRG) > $(TRG)_size.txt + +# ------------------------------------------------------------------------------ +# compiling/assembling/linking/locating: normal executable target +# ------------------------------------------------------------------------------ +.PHONY: bin_app +bin_app : $(TRG) + +.PHONY: diagnostics +diagnostics: obj_d obj_lst pre_pre + +.PHONY: obj_o +obj_o : $(PATH_OBJ)/*.o + +.PHONY: obj_d +obj_d : $(PATH_OBJ)/*.d + +.PHONY: obj_lst +obj_lst : $(PATH_OBJ)/*.lst +.PHONY: pre_pre +pre_pre : $(PATH_PRE)/*.pre -%.o : %.cpp - @$(ECHO) +++ compile: $< +$(TRG) : $(PATH_OBJ)/*.o + @-$(ECHO) +++ linkink application to generate: $(TRG) + $(CL) $(LFLAGS) -L. -lc $(PATH_OBJ)/*.o -Wl,-Map,$(TRG).map -o $(TRG) + +$(PATH_OBJ)/%.o : test/%.cpp # Compile the source file + $(CL) $(CPPFLAGS) $< -c -o $(PATH_OBJ)/$(basename $(@F)).o 2> $(PATH_ERR)/$(basename $(@F)).err # ...and Reformat (using sed) any possible error/warning messages for the VisualStudio(R) output window - # ...and Create an assembly listing using objdump + $(SED) -e 's|.h:\([0-9]*\),|.h(\1) :|' -e 's|:\([0-9]*\):|(\1) :|' $(PATH_ERR)/$(basename $(@F)).err + +$(PATH_PRE)/%.pre : test/%.cpp + $(CL) $(CPPFLAGS) $< -E -o $(PATH_PRE)/$(basename $(@F)).pre + +$(PATH_OBJ)/%.d : test/%.cpp # ...and Generate a dependency file (using the -MM flag) - @-$(CL) $(CPPFLAGS) $< -E -o $(PATH_PRE)/$(basename $(@F)).pre - @-$(CL) $(CPPFLAGS) $< -c -o $(PATH_OBJ)/$(basename $(@F)).o 2> $(PATH_ERR)/$(basename $(@F)).err - @-$(SED) -e 's|.h:\([0-9]*\),|.h(\1) :|' -e 's|:\([0-9]*\):|(\1) :|' $(PATH_ERR)/$(basename $(@F)).err - @-$(OBJDUMP) --disassemble --line-numbers -S $(PATH_OBJ)/$(basename $(@F)).o > $(PATH_LST)/$(basename $(@F)).lst - @-$(CL) $(CPPFLAGS) $< -MM > $(PATH_OBJ)/$(basename $(@F)).d - # profiling - @-$(CL) $(CPPFLAGS) -O0 --coverage $< -c -o $(PATH_COV)/$(basename $(@F)).o 2> $(PATH_NUL) - -%.o : %.c - @$(ECHO) +++ compile: $< - # Compile the source file - # ...and Reformat (using sed) any possible error/warning messages for the VisualStudio(R) output window + $(CL) $(CPPFLAGS) $< -MM > $(PATH_OBJ)/$(basename $(@F)).d + +$(PATH_OBJ)/%.lst : $(PATH_OBJ)/%.o # ...and Create an assembly listing using objdump - # ...and Generate a dependency file (using the -MM flag) - @-$(CL) $(CFLAGS) $< -E -o $(PATH_PRE)/$(basename $(@F)).pre - @-$(CC) $(CFLAGS) $< -c -o $(PATH_OBJ)/$(basename $(@F)).o 2> $(PATH_ERR)/$(basename $(@F)).err - @-$(SED) -e 's|.h:\([0-9]*\),|.h(\1) :|' -e 's|:\([0-9]*\):|(\1) :|' $(PATH_ERR)/$(basename $(@F)).err - @-$(OBJDUMP) -S $(PATH_OBJ)/$(basename $(@F)).o > $(PATH_LST)/$(basename $(@F)).lst - @-$(CC) $(CFLAGS) $< -MM > $(PATH_OBJ)/$(basename $(@F)).d + $(OBJDUMP) --disassemble --line-numbers -S $(PATH_OBJ)/$(basename $(@F)).o > $(PATH_LST)/$(basename $(@F)).lst + +# ------------------------------------------------------------------------------ +# compiling/assembling/linking/locating: profiling target +# ------------------------------------------------------------------------------ +.PHONY: cov_app +cov_app : $(PATH_COV)/$(APP) + +.PHONY: cov_o +cov_o : $(PATH_COV)/*.o + +$(PATH_COV)/$(APP) : $(PATH_COV)/*.o + @-$(ECHO) +++ linking application to generate: $(PATH_COV)/$(APP) + $(CL) $(LFLAGS) -L. -lc $(PATH_COV)/*.o --coverage -o $(PATH_COV)/$(APP) + +$(PATH_COV)/%.o : test/%.cpp + $(CL) $(CPPFLAGS) -O0 --coverage $< -c -o $(PATH_COV)/$(basename $(@F)).o 2> $(PATH_ERR)/$(basename $(@F)).coverr diff --git a/make_for_all_stds b/make_for_all_stds new file mode 100755 index 00000000..1c9c6b51 --- /dev/null +++ b/make_for_all_stds @@ -0,0 +1,8 @@ +#!/bin/bash +set -o xtrace +for v in "-std=c++11" "-std=c++14" "-std=c++17" +do + echo building for $v + touch test/test_suite.cpp printf.cpp + make most CC_STD=$v +done diff --git a/printf.c b/printf.cpp similarity index 51% rename from printf.c rename to printf.cpp index 8a700add..d485ba5b 100644 --- a/printf.c +++ b/printf.cpp @@ -1,914 +1,1117 @@ -/////////////////////////////////////////////////////////////////////////////// -// \author (c) Marco Paland (info@paland.com) -// 2014-2019, PALANDesign Hannover, Germany -// -// \license The MIT License (MIT) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on -// embedded systems with a very limited resources. These routines are thread -// safe and reentrant! -// Use this instead of the bloated standard/newlib printf cause these use -// malloc for printf (and may not be thread safe). -// -/////////////////////////////////////////////////////////////////////////////// - -#include -#include - -#include "printf.h" - - -// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the -// printf_config.h header file -// default: undefined -#ifdef PRINTF_INCLUDE_CONFIG_H -#include "printf_config.h" -#endif - - -// 'ntoa' conversion buffer size, this must be big enough to hold one converted -// numeric number including padded zeros (dynamically created on stack) -// default: 32 byte -#ifndef PRINTF_NTOA_BUFFER_SIZE -#define PRINTF_NTOA_BUFFER_SIZE 32U -#endif - -// 'ftoa' conversion buffer size, this must be big enough to hold one converted -// float number including padded zeros (dynamically created on stack) -// default: 32 byte -#ifndef PRINTF_FTOA_BUFFER_SIZE -#define PRINTF_FTOA_BUFFER_SIZE 32U -#endif - -// support for the floating point type (%f) -// default: activated -#ifndef PRINTF_DISABLE_SUPPORT_FLOAT -#define PRINTF_SUPPORT_FLOAT -#endif - -// support for exponential floating point notation (%e/%g) -// default: activated -#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL -#define PRINTF_SUPPORT_EXPONENTIAL -#endif - -// define the default floating point precision -// default: 6 digits -#ifndef PRINTF_DEFAULT_FLOAT_PRECISION -#define PRINTF_DEFAULT_FLOAT_PRECISION 6U -#endif - -// define the largest float suitable to print with %f -// default: 1e9 -#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 -#define PRINTF_SUPPORT_LONG_LONG -#endif - -// support for the ptrdiff_t type (%t) -// ptrdiff_t is normally defined in as long or long long type -// default: activated -#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T -#define PRINTF_SUPPORT_PTRDIFF_T -#endif - -/////////////////////////////////////////////////////////////////////////////// - -// internal flag definitions -#define FLAGS_ZEROPAD (1U << 0U) -#define FLAGS_LEFT (1U << 1U) -#define FLAGS_PLUS (1U << 2U) -#define FLAGS_SPACE (1U << 3U) -#define FLAGS_HASH (1U << 4U) -#define FLAGS_UPPERCASE (1U << 5U) -#define FLAGS_CHAR (1U << 6U) -#define FLAGS_SHORT (1U << 7U) -#define FLAGS_LONG (1U << 8U) -#define FLAGS_LONG_LONG (1U << 9U) -#define FLAGS_PRECISION (1U << 10U) -#define FLAGS_ADAPT_EXP (1U << 11U) - - -// import float.h for DBL_MAX -#if defined(PRINTF_SUPPORT_FLOAT) -#include -#endif - - -// output function type -typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen); - - -// wrapper (used as buffer) for output function type -typedef struct { - void (*fct)(char character, void* arg); - void* arg; -} out_fct_wrap_type; - - -// internal buffer output -static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen) -{ - if (idx < maxlen) { - ((char*)buffer)[idx] = character; - } -} - - -// internal null output -static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen) -{ - (void)character; (void)buffer; (void)idx; (void)maxlen; -} - - -// internal _putchar wrapper -static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen) -{ - (void)buffer; (void)idx; (void)maxlen; - if (character) { - _putchar(character); - } -} - - -// internal output function wrapper -static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen) -{ - (void)idx; (void)maxlen; - if (character) { - // buffer is the output fct pointer - ((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg); - } -} - - -// internal secure strlen -// \return The length of the string (excluding the terminating 0) limited by 'maxsize' -static inline unsigned int _strnlen_s(const char* str, size_t maxsize) -{ - const char* s; - for (s = str; *s && maxsize--; ++s); - return (unsigned int)(s - str); -} - - -// internal test if char is a digit (0-9) -// \return true if char is a digit -static inline bool _is_digit(char ch) -{ - return (ch >= '0') && (ch <= '9'); -} - - -// internal ASCII string to unsigned int conversion -static unsigned int _atoi(const char** str) -{ - unsigned int i = 0U; - while (_is_digit(**str)) { - i = i * 10U + (unsigned int)(*((*str)++) - '0'); - } - return i; -} - - -// 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)))) { - width--; - } - while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) { - buf[len++] = '0'; - } - while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) { - buf[len++] = '0'; - } - } - - // handle hash - if (flags & FLAGS_HASH) { - if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) { - len--; - if (len && (base == 16U)) { - len--; - } - } - if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { - buf[len++] = 'x'; - } - else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { - buf[len++] = 'X'; - } - else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) { - buf[len++] = 'b'; - } - if (len < PRINTF_NTOA_BUFFER_SIZE) { - buf[len++] = '0'; - } - } - - if (len < PRINTF_NTOA_BUFFER_SIZE) { - if (negative) { - buf[len++] = '-'; - } - else if (flags & FLAGS_PLUS) { - buf[len++] = '+'; // ignore the space if the '+' exists - } - else if (flags & FLAGS_SPACE) { - buf[len++] = ' '; - } - } - - return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); -} - - -// internal itoa for 'long' type -static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags) -{ - char buf[PRINTF_NTOA_BUFFER_SIZE]; - size_t len = 0U; - - // no hash for 0 values - if (!value) { - flags &= ~FLAGS_HASH; - } - - // write if precision != 0 and value is != 0 - if (!(flags & FLAGS_PRECISION) || value) { - do { - const char digit = (char)(value % base); - buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; - value /= base; - } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); - } - - return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); -} - - -// internal itoa for 'long long' type -#if defined(PRINTF_SUPPORT_LONG_LONG) -static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags) -{ - char buf[PRINTF_NTOA_BUFFER_SIZE]; - size_t len = 0U; - - // no hash for 0 values - if (!value) { - flags &= ~FLAGS_HASH; - } - - // write if precision != 0 and value is != 0 - if (!(flags & FLAGS_PRECISION) || value) { - do { - const char digit = (char)(value % base); - buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; - value /= base; - } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); - } - - return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); -} -#endif // PRINTF_SUPPORT_LONG_LONG - - -#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 - - -// 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]; - size_t len = 0U; - double diff = 0.0; - - // powers of 10 - static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; - - // 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) ? 4U : 3U, 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 - 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) { - negative = true; - value = 0 - value; - } - - // set default precision, if not set explicitly - if (!(flags & FLAGS_PRECISION)) { - 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)) { - buf[len++] = '0'; - prec--; - } - - int whole = (int)value; - double tmp = (value - whole) * pow10[prec]; - unsigned long frac = (unsigned long)tmp; - diff = tmp - frac; - - if (diff > 0.5) { - ++frac; - // handle rollover, e.g. case 0.99 with prec 1 is 1.0 - if (frac >= pow10[prec]) { - frac = 0; - ++whole; - } - } - else if (diff < 0.5) { - } - else if ((frac == 0U) || (frac & 1U)) { - // if halfway, round up if odd OR if last digit is 0 - ++frac; - } - - if (prec == 0U) { - diff = value - (double)whole; - if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) { - // exactly 0.5 and ODD, then round up - // 1.5 -> 2, but 2.5 -> 2 - ++whole; - } - } - else { - unsigned int count = prec; - // now do fractional part, as an unsigned number - while (len < PRINTF_FTOA_BUFFER_SIZE) { - --count; - buf[len++] = (char)(48U + (frac % 10U)); - if (!(frac /= 10U)) { - break; - } - } - // add extra 0s - while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) { - buf[len++] = '0'; - } - if (len < PRINTF_FTOA_BUFFER_SIZE) { - // add decimal - buf[len++] = '.'; - } - } - - // do whole part, number is reversed - while (len < PRINTF_FTOA_BUFFER_SIZE) { - buf[len++] = (char)(48 + (whole % 10)); - if (!(whole /= 10)) { - break; - } - } - - // pad leading zeros - if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { - if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { - width--; - } - while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) { - buf[len++] = '0'; - } - } - - if (len < PRINTF_FTOA_BUFFER_SIZE) { - if (negative) { - buf[len++] = '-'; - } - else if (flags & FLAGS_PLUS) { - buf[len++] = '+'; // ignore the space if the '+' exists - } - else if (flags & FLAGS_SPACE) { - buf[len++] = ' '; - } - } - - return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); -} - - -#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 NaN and special values - if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) { - return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags); - } - - // determine the sign - const bool negative = value < 0; - if (negative) { - value = -value; - } - - // 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 >> 52U) & 0x07FFU) - 1023; // effectively log2 - conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // 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); - const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453; - const double z2 = z * z; - conv.U = (uint64_t)(exp2 + 1023) << 52U; - // compute exp(z) using continued fractions, see 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)) ? 4U : 5U; - - // 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? - if ((value >= 1e-4) && (value < 1e6)) { - if ((int)prec > expval) { - prec = (unsigned)((int)prec - expval - 1); - } - else { - prec = 0; - } - flags |= FLAGS_PRECISION; // make sure _ftoa respects precision - // no characters in exponent - minwidth = 0U; - 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; - if (width > minwidth) { - // 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 = 0U; - } - if ((flags & FLAGS_LEFT) && minwidth) { - // if we're padding on the right, DON'T pad the floating part - fwidth = 0U; - } - - // rescale the float value - if (expval) { - value /= conv.F; - } - - // output the floating part - 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) { - // 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; -} -#endif // PRINTF_SUPPORT_EXPONENTIAL -#endif // PRINTF_SUPPORT_FLOAT - - -// internal vsnprintf -static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va) -{ - unsigned int flags, width, precision, n; - size_t idx = 0U; - - if (!buffer) { - // use null output function - out = _out_null; - } - - while (*format) - { - // format specifier? %[flags][width][.precision][length] - if (*format != '%') { - // no - out(*format, buffer, idx++, maxlen); - format++; - continue; - } - else { - // yes, evaluate it - format++; - } - - // evaluate flags - flags = 0U; - do { - switch (*format) { - case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break; - case '-': flags |= FLAGS_LEFT; format++; n = 1U; break; - case '+': flags |= FLAGS_PLUS; format++; n = 1U; break; - case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break; - case '#': flags |= FLAGS_HASH; format++; n = 1U; break; - default : n = 0U; break; - } - } while (n); - - // evaluate width field - width = 0U; - if (_is_digit(*format)) { - width = _atoi(&format); - } - else if (*format == '*') { - const int w = va_arg(va, int); - if (w < 0) { - flags |= FLAGS_LEFT; // reverse padding - width = (unsigned int)-w; - } - else { - width = (unsigned int)w; - } - format++; - } - - // evaluate precision field - precision = 0U; - if (*format == '.') { - flags |= FLAGS_PRECISION; - format++; - if (_is_digit(*format)) { - precision = _atoi(&format); - } - else if (*format == '*') { - const int prec = (int)va_arg(va, int); - precision = prec > 0 ? (unsigned int)prec : 0U; - format++; - } - } - - // evaluate length field - switch (*format) { - case 'l' : - flags |= FLAGS_LONG; - format++; - if (*format == 'l') { - flags |= FLAGS_LONG_LONG; - format++; - } - break; - case 'h' : - flags |= FLAGS_SHORT; - format++; - if (*format == 'h') { - flags |= FLAGS_CHAR; - format++; - } - break; -#if defined(PRINTF_SUPPORT_PTRDIFF_T) - case 't' : - flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); - format++; - break; -#endif - case 'j' : - flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); - format++; - break; - case 'z' : - flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); - format++; - break; - default : - break; - } - - // evaluate specifier - switch (*format) { - case 'd' : - case 'i' : - case 'u' : - case 'x' : - case 'X' : - case 'o' : - case 'b' : { - // set the base - unsigned int base; - if (*format == 'x' || *format == 'X') { - base = 16U; - } - else if (*format == 'o') { - base = 8U; - } - else if (*format == 'b') { - base = 2U; - } - else { - base = 10U; - flags &= ~FLAGS_HASH; // no hash for dec format - } - // uppercase - if (*format == 'X') { - flags |= FLAGS_UPPERCASE; - } - - // no plus or space flag for u, x, X, o, b - if ((*format != 'i') && (*format != 'd')) { - flags &= ~(FLAGS_PLUS | FLAGS_SPACE); - } - - // ignore '0' flag when precision is given - if (flags & FLAGS_PRECISION) { - flags &= ~FLAGS_ZEROPAD; - } - - // convert the integer - if ((*format == 'i') || (*format == 'd')) { - // signed - if (flags & FLAGS_LONG_LONG) { -#if defined(PRINTF_SUPPORT_LONG_LONG) - const long long value = va_arg(va, long long); - idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); -#endif - } - else if (flags & FLAGS_LONG) { - const long value = va_arg(va, long); - idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); - } - else { - const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int); - idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); - } - } - else { - // unsigned - if (flags & FLAGS_LONG_LONG) { -#if defined(PRINTF_SUPPORT_LONG_LONG) - idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags); -#endif - } - else if (flags & FLAGS_LONG) { - idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags); - } - else { - const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int); - idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags); - } - } - format++; - break; - } -#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; - // pre padding - if (!(flags & FLAGS_LEFT)) { - while (l++ < width) { - out(' ', buffer, idx++, maxlen); - } - } - // char output - out((char)va_arg(va, int), buffer, idx++, maxlen); - // post padding - if (flags & FLAGS_LEFT) { - while (l++ < width) { - out(' ', buffer, idx++, maxlen); - } - } - format++; - break; - } - - case 's' : { - const char* p = va_arg(va, char*); - unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1); - // pre padding - if (flags & FLAGS_PRECISION) { - l = (l < precision ? l : precision); - } - if (!(flags & FLAGS_LEFT)) { - while (l++ < width) { - out(' ', buffer, idx++, maxlen); - } - } - // string output - while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) { - out(*(p++), buffer, idx++, maxlen); - } - // post padding - if (flags & FLAGS_LEFT) { - while (l++ < width) { - out(' ', buffer, idx++, maxlen); - } - } - format++; - break; - } - - case 'p' : { - width = sizeof(void*) * 2U; - flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE; -#if defined(PRINTF_SUPPORT_LONG_LONG) - const bool is_ll = sizeof(uintptr_t) == sizeof(long long); - if (is_ll) { - idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags); - } - else { -#endif - idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags); -#if defined(PRINTF_SUPPORT_LONG_LONG) - } -#endif - format++; - break; - } - - case '%' : - out('%', buffer, idx++, maxlen); - format++; - break; - - default : - out(*format, buffer, idx++, maxlen); - format++; - break; - } - } - - // termination - out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen); - - // return written chars without terminating \0 - return (int)idx; -} - - -/////////////////////////////////////////////////////////////////////////////// - -int printf_(const char* format, ...) -{ - va_list va; - va_start(va, format); - char buffer[1]; - const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va); - va_end(va); - return ret; -} - - -int sprintf_(char* buffer, const char* format, ...) -{ - va_list va; - va_start(va, format); - const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va); - va_end(va); - return ret; -} - - -int snprintf_(char* buffer, size_t count, const char* format, ...) -{ - va_list va; - va_start(va, format); - const int ret = _vsnprintf(_out_buffer, buffer, count, format, va); - va_end(va); - return ret; -} - - -int vprintf_(const char* format, va_list va) -{ - char buffer[1]; - return _vsnprintf(_out_char, buffer, (size_t)-1, format, va); -} - - -int vsnprintf_(char* buffer, size_t count, const char* format, va_list va) -{ - return _vsnprintf(_out_buffer, buffer, count, format, va); -} - - -int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...) -{ - va_list va; - va_start(va, format); - const out_fct_wrap_type out_fct_wrap = { out, arg }; - const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va); - va_end(va); - return ret; -} +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. These routines are thread +// safe and reentrant! +// Use this instead of the bloated standard/newlib printf cause these use +// malloc for printf (and may not be thread safe). +// +/////////////////////////////////////////////////////////////////////////////// + + +#include "printf.h" +#include +#include + +/////////////////////////////////////////////////////////////////////////////// +// +// Check c++ with (or simply run "make checks"): +// +// clang-tidy test/test_suite.cpp +// cppcheck --enable=warning,style --inline-suppr printf.cpp +// +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// +// configuration options: +// Pass these to compiler if desired. +// +/////////////////////////////////////////////////////////////////////////////// + +// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the +// printf_config.h header file +// default: undefined +#ifdef PRINTF_INCLUDE_CONFIG_H +#include "printf_config.h" +#endif + +// support for the floating point type (%f) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_FLOAT +#define PRINTF_SUPPORT_FLOAT +#endif + +// support for exponential floating point notation (%e/%g) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL +#define PRINTF_SUPPORT_EXPONENTIAL +#endif + +// support for the long long types (%llu or %p) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG +#define PRINTF_SUPPORT_LONG_LONG +#endif + +// support for the ptrdiff_t type (%t) +// ptrdiff_t is normally defined in as long or long long type +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T +#define PRINTF_SUPPORT_PTRDIFF_T +#endif + +// if not true, minimize the code size. +// default: activated +#ifndef PRINTF_DISABLE_MINIMIZE_CODE_SIZE +#define PRINTF_MINIMIZE_CODE_SIZE +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// +// internal configuration options +// +/////////////////////////////////////////////////////////////////////////////// + +// define the largest float suitable to print with %f +// default: 1e9 (minus an epsilon) +constexpr double PRINTF_MAX_FLOAT = (0.999 * 1e+9); +// define the smallest float suitable to print with %f +// default: 1e-9 (plus an epsilon) +constexpr double PRINTF_MIN_FLOAT = (1.001 * 1e-9); +// define the default floating point precision +// default: 6 digits +constexpr unsigned PRINTF_DEFAULT_FLOAT_PRECISION = 6U; +// 'ntoa' conversion buffer size, this must be big enough to hold one converted +// numeric number including padded zeros (dynamically created on stack) +// default: 32 byte +constexpr unsigned PRINTF_NTOA_BUFFER_SIZE = 32U; +// 'ftoa' conversion buffer size, this must be big enough to hold one converted +// float number including padded zeros (dynamically created on stack) +// default: 32 byte +constexpr unsigned PRINTF_FTOA_BUFFER_SIZE = 32U; + + +/////////////////////////////////////////////////////////////////////////////// +// +// constants +// +/////////////////////////////////////////////////////////////////////////////// + +// internal flag definitions +constexpr unsigned FLAGS_ZEROPAD = (1U << 0U); +constexpr unsigned FLAGS_LEFT = (1U << 1U); +constexpr unsigned FLAGS_PLUS = (1U << 2U); +constexpr unsigned FLAGS_SPACE = (1U << 3U); +constexpr unsigned FLAGS_HASH = (1U << 4U); +constexpr unsigned FLAGS_UPPERCASE = (1U << 5U); +constexpr unsigned FLAGS_CHAR = (1U << 6U); +constexpr unsigned FLAGS_SHORT = (1U << 7U); +constexpr unsigned FLAGS_LONG = (1U << 8U); +constexpr unsigned FLAGS_LONG_LONG = (1U << 9U); +constexpr unsigned FLAGS_PRECISION = (1U << 10U); +constexpr unsigned FLAGS_ADAPT_EXP = (1U << 11U); + +// math constants +const unsigned BASE_2U = 2U; +const unsigned BASE_8U = 8U; +const unsigned BASE_10U = 10U; +const unsigned BASE_16U = 16U; + +// import float.h for DBL_MAX +#if defined(PRINTF_SUPPORT_FLOAT) +#include +#endif + + + +// output function type +typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen); + + +// wrapper (used as buffer) for output function type +typedef struct { + void (*fct)(char character, void* arg); + void* arg; +} out_fct_wrap_type; + + +// internal buffer output +static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen) +{ + if (idx < maxlen) { + (static_cast(buffer))[idx] = character; + } +} + + +// internal null output +static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen) +{ + (void)character; (void)buffer; (void)idx; (void)maxlen; +} + + +// internal _putchar wrapper +static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen) +{ + (void)buffer; (void)idx; (void)maxlen; + if (character) { + _putchar(character); + } +} + + +// internal output function wrapper +static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen) +{ + (void)idx; (void)maxlen; + if (character) { + // buffer is the output fct pointer + ((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg); + } +} + + +// internal secure strlen +// \return The length of the string (excluding the terminating 0) limited by 'maxsize' +static inline unsigned int _strnlen_s(const char* str, size_t maxsize) +{ + const char* s = str; + for ( ; *s && maxsize--; ++s) { } + return static_cast(s - str); +} + + +// internal test if char is a digit (0-9) +// \return true if char is a digit +static inline bool _is_digit(char ch) +{ + return (ch >= '0') && (ch <= '9'); +} + + +// internal ASCII string to unsigned int conversion +static unsigned int _atoi(const char** str) +{ + unsigned int i = 0U; + while (_is_digit(**str)) { + i = i * BASE_10U + static_cast(*((*str)++) - '0'); + } + return i; +} + + +// 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)))) { + width--; + } + while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + // handle hash + if (flags & FLAGS_HASH) { + if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) { + len--; + if (len && (base == BASE_16U)) { + len--; + } + } + if ( len < PRINTF_NTOA_BUFFER_SIZE ) { + if ( base == BASE_16U ) { + if ( !(flags & FLAGS_UPPERCASE) ) { + buf[len++] = 'x'; + } else { + buf[len++] = 'X'; + } + } else if ( base == BASE_2U ) { + buf[len++] = 'b'; + } + } + + if (len < PRINTF_NTOA_BUFFER_SIZE) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_NTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } + else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } + else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); +} + + +// internal itoa for 'long' type +static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags) +{ + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if (!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const unsigned long digit = (value % base); + buf[len++] = static_cast(digit < 10 ? '0' + digit : ((flags & FLAGS_UPPERCASE) ? 'A' : 'a') + digit - 10); + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, static_cast(base), prec, width, flags); +} + + +// internal itoa for 'long long' type +#if defined(PRINTF_SUPPORT_LONG_LONG) +static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags) +{ + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if (!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const unsigned long long digit = value % base; + buf[len++] = static_cast(digit < 10 ? '0' + digit : ((flags & FLAGS_UPPERCASE) ? 'A' : 'a') + digit - 10); + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, static_cast(base), prec, width, flags); +} +#endif // PRINTF_SUPPORT_LONG_LONG + + +#if defined(PRINTF_SUPPORT_FLOAT) + +const int I_1 = 1; +const int I_10 = 10; +const int I_100 = 100; +const int I_1000 = 1000; +const int I_10000 = 10000; +const int I_100000 = 100000; +const int I_1000000 = 1000000; +const int I_10000000 = 10000000; +const int I_100000000 = 100000000; +const int I_1000000000 = 1000000000; + +const double FL_DOUBLE_HALF = 0.5; +const unsigned int ONE_U = 1U; + + +bool calc_frac( double value_abs, unsigned int prec, unsigned long *frac, unsigned *whole ) { + double diff = 0.0; + bool rollover = false; + + // powers of 10 + static const double pow10[] = { I_1, I_10, I_100, I_1000, I_10000, I_100000, I_1000000, I_10000000, I_100000000, I_1000000000 }; + + *whole = static_cast(value_abs); + // run cppcheck (see cmd-line at top of file) + // cppcheck-suppress arrayIndexOutOfBoundsCond + double tmp = (value_abs - static_cast(*whole)) * pow10[prec]; + *frac = static_cast(tmp); + diff = tmp - static_cast(*frac); + + if (diff > FL_DOUBLE_HALF) { + ++(*frac); + // handle rollover, e.g. case 0.99 with prec 1 is 1.0 + if (*frac >= static_cast (pow10[prec])) { + *frac = 0; + ++(*whole); + rollover = true; + } + } + else if (diff < FL_DOUBLE_HALF) { + } + else if ( *frac & 1U ) { + // if halfway and ODD, then round up + ++(*frac); + } + if (prec == 0U) { + diff = value_abs - static_cast(*whole); + if ( !(diff < FL_DOUBLE_HALF) && !(diff > FL_DOUBLE_HALF) && (*whole & 1U) ) { + // exactly 0.5 and ODD, then round up + // 1.5 -> 2, but 2.5 -> 2 + ++(*whole); + rollover = true; + } + } + + return rollover; +} + + +size_t fill_mantissa( size_t idx, char buf[], size_t *len, bool negative, unsigned long frac, unsigned whole, unsigned int excess_prec, unsigned int prec, unsigned int *width, unsigned int flags ) { + // Start filling the buf with our fractional-part and whole-part. + // We are filling the buf in reverse order. + + if (prec > 0U) { + // Output trailing 0s for the excess precision. + while ((*len < PRINTF_FTOA_BUFFER_SIZE) && (excess_prec > 0)) { + buf[(*len)++] = '0'; + excess_prec--; + } + + unsigned int count = prec; + // now do fractional part, as an unsigned number + while (*len < PRINTF_FTOA_BUFFER_SIZE) { + --count; + buf[(*len)++] = static_cast(static_cast('0') + (frac % BASE_10U)); + if (!(frac /= BASE_10U)) { + break; + } + } + // add extra 0s + while ((*len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) { + buf[(*len)++] = '0'; + } + if (*len < PRINTF_FTOA_BUFFER_SIZE) { + // add decimal + buf[(*len)++] = '.'; + } + } + + // do whole part, number is reversed + while (*len < PRINTF_FTOA_BUFFER_SIZE) { + buf[(*len)++] = static_cast(static_cast('0') + (whole % 10)); + if (!(whole /= 10)) { + break; + } + } + + // pad leading zeros + if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { + if (*width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + (*width)--; + } + while ((*len < *width) && (*len < PRINTF_FTOA_BUFFER_SIZE)) { + buf[(*len)++] = '0'; + } + } + + if (*len < PRINTF_FTOA_BUFFER_SIZE) { + if (negative) { + buf[(*len)++] = '-'; + } + else if (flags & FLAGS_PLUS) { + buf[(*len)++] = '+'; // ignore the space if the '+' exists + } + else if (flags & FLAGS_SPACE) { + buf[(*len)++] = ' '; + } + } + return idx; +} + + + + + +// 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) +{ + + // determine the sign + double value_abs = value; + const bool negative = value < 0; + if (negative) { + value_abs = -value; + } + + // set default precision, if not set explicitly + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + // limit precision to 9, cause a prec >= 10 can lead to overflow errors + unsigned int excess_prec = 0; + if( prec > 9U ) { + excess_prec = prec - 9U; + prec = 9U; + } + + // calc frac, whole ...... + unsigned long frac = 0; + unsigned whole = 0; + (void) calc_frac( value_abs, prec, &frac, &whole ); + // Ignore the ro (rollover) flag (returned by calc_frac()). + //cout << "calc_frac...processed: value_abs=" << value_abs << ", prec=" << prec << ", frac=" << frac << ", whole=" << whole << ", ro=" << ro << endl; + + // given frac and whole, we can now express (into buf) the number to be printed. + char buf[PRINTF_FTOA_BUFFER_SIZE]; + size_t len = 0U; + idx = fill_mantissa( idx, buf, &len, negative, frac, whole, excess_prec, prec, &width, flags ); + + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); +} + + +const unsigned SHIFT_52U = 52U; +const int I_1023 = 1023; +const unsigned long long I_1023ULL = 1023ULL; +const unsigned X_0x07FFU = 0x07FFU; + +typedef union { + uint64_t U; + double F; +} fconv_t; + + +void calc_exp10 ( double value, int *exp10, fconv_t *conv ) { + +// Cannot call log() in the defs of these constexpr's, because +// clang-tidy complains that +// "non-constexpr function 'log' cannot be used in a constant expression'". + static constexpr double LN_OF_10 = 2.302585092994046 ; // = (log(10.0)) + static constexpr double LN_OF_2 = 0.6931471805599453 ; // = (log(2.0)) + static constexpr double LN_OF_2_over_LN_OF_10 = 0.301029995663981 ; // = (log(2.0)/log(10.0)) + static constexpr double LN_OF_10_over_LN_OF_2 = 3.321928094887362 ; // = (log(10.0)/log(2.0)) + static constexpr double LN_OF_1_5_over_LN_OF_10 = 0.1760912590558 ; // = (log(1.5)/log(10.0)) + static constexpr double ONE_over_1_5_LN_OF_10 = 0.289529654602168 ; // = (1.0/(1.5*log(10.0))) + + conv->F = value; + int exp2 = static_cast((conv->U >> SHIFT_52U) & X_0x07FFU) - I_1023; // effectively log2 + conv->U = (conv->U & ((1ULL << SHIFT_52U) - 1U)) | (I_1023ULL << SHIFT_52U); // 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 + *exp10 = static_cast(LN_OF_1_5_over_LN_OF_10 + exp2 * LN_OF_2_over_LN_OF_10 + (conv->F - 1.5) * ONE_over_1_5_LN_OF_10); + + // now we want to compute 10^exp10 but we want to be sure it won't overflow + exp2 = static_cast(*exp10 * LN_OF_10_over_LN_OF_2 + FL_DOUBLE_HALF); + + const double z = *exp10 * LN_OF_10 - exp2 * LN_OF_2; + const double z2 = z * z; + conv->U = (uint64_t)(exp2 + I_1023) << SHIFT_52U; + // compute exp(z) using continued fractions, see 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) { + (*exp10)--; + conv->F /= BASE_10U; + } +} + + +#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) +{ + // determine the sign + double value_abs = value; + const bool negative = value < 0; + if (negative) { + value_abs = -value; + } + + // set default precision, if not set explicitly + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + + // limit precision to 9, cause a prec >= 10 can lead to overflow errors + unsigned int excess_prec = 0; + if( prec > 9U ) { + excess_prec = prec - 9U; + prec = 9U; + } + + // calc exp10 ...... + int exp10 = 0; + fconv_t conv; + calc_exp10( value_abs, &exp10, &conv ); + //int log2 = static_cast((conv.U >> SHIFT_52U) & X_0x07FFU) - I_1023; // effectively log2 (calculated here for cout diagnostic msg). + //cout << "calc_exp10...processed: value_abs=" << value_abs << ", exp10=" << exp10 << ", conv.F=" << conv.F << ", conv.U i.e. log2=" << log2 << endl; + + // the exponent format is "%+03d" and largest value is "308", so set aside 4 characters for e+nn (or 5 for e+nnn). + unsigned int minwidth = ((exp10 < 100) && (exp10 > -100)) ? 4U : 5U; + bool printAsSciNot = true; + + // in "%g" mode, "prec" is the number of *significant figures* not decimals + if (flags & FLAGS_ADAPT_EXP) { + // prec and exp10 determinte whether we want to fall-back to "%f" mode. + // printAsSciNot records that fact. + int prec_tmp = static_cast(prec); + if (prec_tmp == 0) { + prec_tmp = 1; + } + if ( (prec_tmp > exp10) && (exp10 >= -4) ) { + printAsSciNot = false; + prec_tmp -= exp10 + 1; + } else { + printAsSciNot = true; + prec_tmp--; + } + prec = static_cast( (prec_tmp > 0)? prec_tmp : 0 ); + } + + if (! printAsSciNot) { + flags |= FLAGS_PRECISION; // make sure _ftoa respects precision + // no characters in exponent + minwidth = 0U; + exp10 = 0; + } + + // rescale the float value_abs + if (exp10) { + value_abs /= conv.F; + } + + // will everything fit? + unsigned int fwidth = width; + if (width > minwidth) { + // 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 = 0U; + } + if ((flags & FLAGS_LEFT) && minwidth) { + // if we're padding on the right, DON'T pad the floating part + fwidth = 0U; + } + + const size_t start_idx = idx; + if (! printAsSciNot) { + // output the floating part: + + // calc frac, whole. + unsigned long frac = 0; + unsigned whole = 0; + (void) calc_frac( value_abs, prec, &frac, &whole ); + // Ignore the ro (rollover) flag (returned by calc_frac()) in non-sci-notat case. + + // fill this buffer with the number to be output, and output it. + char buf[PRINTF_FTOA_BUFFER_SIZE]; + size_t len = 0U; + idx = fill_mantissa( idx, buf, &len, negative, frac, whole, excess_prec, prec, &fwidth, flags & ~FLAGS_ADAPT_EXP ); + return _out_rev(out, buffer, idx, maxlen, buf, len, fwidth, flags); + + } else { + // output the floating part: + + // calc frac, whole. + unsigned long frac = 0; + unsigned whole = 0; + bool ro = calc_frac( value_abs, prec, &frac, &whole ); + // In sci-notat case, the ro (rollover) flag tells us to increment exp10. + if( ro ) { + if( whole >= 10.0 ) { + whole /= 10.0; + exp10++; + } + } + // fill this buffer with the mantissa to be output, and output it. + char buf[PRINTF_FTOA_BUFFER_SIZE]; + size_t len = 0U; + idx = fill_mantissa( idx, buf, &len, negative, frac, whole, excess_prec, prec, &fwidth, flags & ~FLAGS_ADAPT_EXP ); + idx = _out_rev(out, buffer, idx, maxlen, buf, len, fwidth, flags); + + // Now, proceed to print the exponent. + // 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, static_cast ((exp10 < 0) ? -exp10 : exp10), exp10 < 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; +} +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + + +// internal vsnprintf +static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va) +{ + unsigned int flags = 0U; + unsigned int width = 0U; + unsigned int precision = 0U; + unsigned int n = 0U; + size_t idx = 0U; + + if (!buffer) { + // use null output function + out = _out_null; + } + + while (*format) + { + // format specifier? %[flags][width][.precision][length] + if (*format != '%') { + // no + out(*format, buffer, idx++, maxlen); + format++; + continue; + } + // yes, evaluate it + format++; + + // evaluate flags + flags = 0U; + do { + switch (*format) { + case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break; + case '-': flags |= FLAGS_LEFT; format++; n = 1U; break; + case '+': flags |= FLAGS_PLUS; format++; n = 1U; break; + case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break; + case '#': flags |= FLAGS_HASH; format++; n = 1U; break; + default : n = 0U; break; + } + } while (n); + + // evaluate width field + width = 0U; + if (_is_digit(*format)) { + width = _atoi(&format); + } + else if (*format == '*') { + const int w = va_arg(va, int); + if (w < 0) { + flags |= FLAGS_LEFT; // reverse padding + width = static_cast(-w); + } + else { + width = static_cast(w); + } + format++; + } + + // evaluate precision field + precision = 0U; + if (*format == '.') { + flags |= FLAGS_PRECISION; + format++; + if (_is_digit(*format)) { + precision = _atoi(&format); + } + else if (*format == '*') { + const int prec = va_arg(va, int); + precision = prec > 0 ? static_cast(prec) : 0U; + format++; + } + } + + // evaluate length field + switch (*format) { + case 'l' : + flags |= FLAGS_LONG; + format++; + if (*format == 'l') { + flags |= FLAGS_LONG_LONG; + format++; + } + break; + case 'h' : + flags |= FLAGS_SHORT; + format++; + if (*format == 'h') { + flags |= FLAGS_CHAR; + format++; + } + break; +#if defined(PRINTF_SUPPORT_PTRDIFF_T) + case 't' : + flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; +#endif + case 'j' : + flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + case 'z' : + flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + default : + break; + } + + // evaluate specifier + switch (*format) { + case 'd' : + case 'i' : + case 'u' : + case 'x' : + case 'X' : + case 'o' : + case 'b' : { + // set the base + unsigned int base; + if (*format == 'x' || *format == 'X') { + base = BASE_16U; + } + else if (*format == 'o') { + base = BASE_8U; + } + else if (*format == 'b') { + base = BASE_2U; + } + else { + base = BASE_10U; + flags &= ~FLAGS_HASH; // no hash for dec format + } + // uppercase + if (*format == 'X') { + flags |= FLAGS_UPPERCASE; + } + + // no plus or space flag for u, x, X, o, b + if ((*format != 'i') && (*format != 'd')) { + flags &= ~(FLAGS_PLUS | FLAGS_SPACE); + } + + // ignore '0' flag when precision is given + if (flags & FLAGS_PRECISION) { + flags &= ~FLAGS_ZEROPAD; + } + + // convert the integer + if ((*format == 'i') || (*format == 'd')) { + // signed + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + const long long value = va_arg(va, long long); + idx = _ntoa_long_long(out, buffer, idx, maxlen, static_cast(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); +#else + const long value = va_arg(va, long); + idx = _ntoa_long(out, buffer, idx, maxlen, static_cast(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); +#endif + } + else if (flags & FLAGS_LONG) { + const long value = va_arg(va, long); + idx = _ntoa_long(out, buffer, idx, maxlen, static_cast(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); + } + else { + const int value = (flags & FLAGS_CHAR) ? static_cast(va_arg(va, int)) : (flags & FLAGS_SHORT) ? static_cast(va_arg(va, int)) : va_arg(va, int); + idx = _ntoa_long(out, buffer, idx, maxlen, static_cast(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); + } + } + else { + // unsigned + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags); +#else + idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags); +#endif + } + else if (flags & FLAGS_LONG) { + idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags); + } + else { + const unsigned int value = (flags & FLAGS_CHAR) ? static_cast(va_arg(va, unsigned int)) : (flags & FLAGS_SHORT) ? static_cast(va_arg(va, unsigned int)) : va_arg(va, unsigned int); + idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags); + } + } + format++; + break; + } +#if defined(PRINTF_SUPPORT_FLOAT) + case 'f' : + case 'F' : + if (*format == 'F') { + flags |= FLAGS_UPPERCASE; + } + { + double value = va_arg(va, double); + // test for special values + if ( std::isnan(value) ) { + idx = _out_rev(out, buffer, idx, maxlen, "nan", 3U, width, flags); + } + else if ( (std::isinf(value) && (value < 0)) || (value < -DBL_MAX) ) { + idx = _out_rev(out, buffer, idx, maxlen, "fni-", 4U, width, flags); + } + else if ( (std::isinf(value) && (value > 0)) || (value > +DBL_MAX) ) { + idx = _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags); + } + else if ( + // test for very large values + (value > +PRINTF_MAX_FLOAT) || + (value < -PRINTF_MAX_FLOAT) || + // test for very small values + ((value > 0) && (value < +PRINTF_MIN_FLOAT)) || + ((value < 0) && (value > -PRINTF_MIN_FLOAT)) + ) { + // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + idx = _etoa(out, buffer, idx, maxlen, value, precision, width, flags); +#else + idx = 0U; +#endif + } + else { + idx = _ftoa(out, buffer, idx, maxlen, value, 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; + } + { + double value = va_arg(va, double); + // test for special values + if ( std::isnan(value) ) { + idx = _out_rev(out, buffer, idx, maxlen, "nan", 3U, width, flags); + } + else if ( (std::isinf(value) && (value < 0)) || (value < -DBL_MAX) ) { + idx = _out_rev(out, buffer, idx, maxlen, "fni-", 4U, width, flags); + } + else if ( (std::isinf(value) && (value > 0)) || (value > +DBL_MAX) ) { + idx = _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags); + } + else { + idx = _etoa(out, buffer, idx, maxlen, value, precision, width, flags); + } + } + format++; + break; +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + case 'c' : { + unsigned int l = 1U; + // pre padding + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // char output + out(static_cast(va_arg(va, int)), buffer, idx++, maxlen); + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 's' : { + const char* p = va_arg(va, char*); + unsigned int l = _strnlen_s(p, precision ? precision : static_cast(-1)); + // pre padding + if (flags & FLAGS_PRECISION) { + l = (l < precision ? l : precision); + } + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // string output + while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) { + out(*(p++), buffer, idx++, maxlen); + } + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 'p' : { + width = sizeof(void*) * 2U; + flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE; +#if defined(PRINTF_SUPPORT_LONG_LONG) + const bool is_ll = sizeof(uintptr_t) == sizeof(long long); + // run cppcheck (see cmd-line at top of file) + // cppcheck-suppress knownConditionTrueFalse + if (is_ll) { + idx = _ntoa_long_long(out, buffer, idx, maxlen, static_cast(va_arg(va, unsigned long long)), false, BASE_16U, precision, width, flags); + } + else { + idx = _ntoa_long(out, buffer, idx, maxlen, static_cast(va_arg(va, unsigned long)), false, BASE_16U, precision, width, flags); + } +#else + idx = _ntoa_long(out, buffer, idx, maxlen, static_cast(va_arg(va, unsigned long)), false, BASE_16U, precision, width, flags); +#endif + format++; + break; + } + + case '%' : + out('%', buffer, idx++, maxlen); + format++; + break; + + default : + out(*format, buffer, idx++, maxlen); + format++; + break; + } + } + + // termination + out(static_cast(0), buffer, idx < maxlen ? idx : maxlen - 1U, maxlen); + + // return written chars without terminating \0 + return static_cast(idx); +} + + +/////////////////////////////////////////////////////////////////////////////// + +int printf_(const char* format, ...) +{ + va_list va; + va_start(va, format); + char buffer[1]; + const int ret = _vsnprintf(_out_char, buffer, static_cast(-1), format, va); + va_end(va); + return ret; +} + + +int sprintf_(char* buffer, const char* format, ...) +{ + va_list va; + va_start(va, format); + const int ret = _vsnprintf(_out_buffer, buffer, static_cast(-1), format, va); + va_end(va); + return ret; +} + + +int snprintf_(char* buffer, size_t count, const char* format, ...) +{ + va_list va; + va_start(va, format); + const int ret = _vsnprintf(_out_buffer, buffer, count, format, va); + va_end(va); + return ret; +} + + +int vprintf_(const char* format, va_list va) +{ + char buffer[1]; + return _vsnprintf(_out_char, buffer, static_cast(-1), format, va); +} + + +int vsnprintf_(char* buffer, size_t count, const char* format, va_list va) +{ + return _vsnprintf(_out_buffer, buffer, count, format, va); +} + + +int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...) +{ + va_list va; + va_start(va, format); + const out_fct_wrap_type out_fct_wrap = { out, arg }; + const int ret = _vsnprintf(_out_fct, const_cast(reinterpret_cast(&out_fct_wrap)), static_cast(-1), format, va); + va_end(va); + return ret; +} + + +/* +#define SIZE 200 +int main() { + char buf[SIZE]; + const int ret = pr_names::snprintf_( buf, SIZE, "hello double:'%7.2f', int:'%5d', str:'%10s'\n", 1234.567, 9876, "hellodolly" ); + fprintf( stdout, "snprintf_() returned %d\n", ret ); + return 0; +} +*/ diff --git a/printf.h b/printf.h index 6104ccfb..fa088f56 100644 --- a/printf.h +++ b/printf.h @@ -1,117 +1,121 @@ -/////////////////////////////////////////////////////////////////////////////// -// \author (c) Marco Paland (info@paland.com) -// 2014-2019, PALANDesign Hannover, Germany -// -// \license The MIT License (MIT) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on -// embedded systems with a very limited resources. -// Use this instead of bloated standard/newlib printf. -// These routines are thread safe and reentrant. -// -/////////////////////////////////////////////////////////////////////////////// - -#ifndef _PRINTF_H_ -#define _PRINTF_H_ - -#include -#include - - -#ifdef __cplusplus -extern "C" { -#endif - - -/** - * Output a character to a custom device like UART, used by the printf() function - * This function is declared here only. You have to write your custom implementation somewhere - * \param character Character to output - */ -void _putchar(char character); - - -/** - * Tiny printf implementation - * You have to implement _putchar if you use printf() - * To avoid conflicts with the regular printf() API it is overridden by macro defines - * and internal underscore-appended functions like printf_() are used - * \param format A string that specifies the format of the output - * \return The number of characters that are written into the array, not counting the terminating null character - */ -#define printf printf_ -int printf_(const char* format, ...); - - -/** - * Tiny sprintf implementation - * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD! - * \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output! - * \param format A string that specifies the format of the output - * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character - */ -#define sprintf sprintf_ -int sprintf_(char* buffer, const char* format, ...); - - -/** - * Tiny snprintf/vsnprintf implementation - * \param buffer A pointer to the buffer where to store the formatted string - * \param count The maximum number of characters to store in the buffer, including a terminating null character - * \param format A string that specifies the format of the output - * \param va A value identifying a variable arguments list - * \return The number of characters that COULD have been written into the buffer, not counting the terminating - * null character. A value equal or larger than count indicates truncation. Only when the returned value - * is non-negative and less than count, the string has been completely written. - */ -#define snprintf snprintf_ -#define vsnprintf vsnprintf_ -int snprintf_(char* buffer, size_t count, const char* format, ...); -int vsnprintf_(char* buffer, size_t count, const char* format, va_list va); - - -/** - * Tiny vprintf implementation - * \param format A string that specifies the format of the output - * \param va A value identifying a variable arguments list - * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character - */ -#define vprintf vprintf_ -int vprintf_(const char* format, va_list va); - - -/** - * printf with output function - * You may use this as dynamic alternative to printf() with its fixed _putchar() output - * \param out An output function which takes one character and an argument pointer - * \param arg An argument pointer for user data passed to output function - * \param format A string that specifies the format of the output - * \return The number of characters that are sent to the output function, not counting the terminating null character - */ -int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...); - - -#ifdef __cplusplus -} -#endif - - -#endif // _PRINTF_H_ +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. +// Use this instead of bloated standard/newlib printf. +// These routines are thread safe and reentrant. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _PRINTF_H_ +#define _PRINTF_H_ + +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Output a character to a custom device like UART, used by the printf() function + * This function is declared here only. You have to write your custom implementation somewhere + * \param character Character to output + */ +//void _putchar(char character); + + +/** + * Tiny printf implementation + * You have to implement _putchar if you use printf() + * To avoid conflicts with the regular printf() API it is overridden by macro defines + * and internal underscore-appended functions like printf_() are used + * \param format A string that specifies the format of the output + * \return The number of characters that are written into the array, not counting the terminating null character + */ +#define printf printf_ +int printf_(const char* format, ...); + + +/** + * Tiny sprintf implementation + * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD! + * \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output! + * \param format A string that specifies the format of the output + * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character + */ +#define sprintf sprintf_ +int sprintf_(char* buffer, const char* format, ...); + + +/** + * Tiny snprintf/vsnprintf implementation + * \param buffer A pointer to the buffer where to store the formatted string + * \param count The maximum number of characters to store in the buffer, including a terminating null character + * \param format A string that specifies the format of the output + * \param va A value identifying a variable arguments list + * \return The number of characters that COULD have been written into the buffer, not counting the terminating + * null character. A value equal or larger than count indicates truncation. Only when the returned value + * is non-negative and less than count, the string has been completely written. + */ +#define snprintf snprintf_ +int snprintf_(char* buffer, size_t count, const char* format, ...); + + +/** + * Tiny vprintf implementation + * \param format A string that specifies the format of the output + * \param va A value identifying a variable arguments list + * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character + */ +#define vprintf vprintf_ +int vprintf_(const char* format, va_list va); + + +/** + * Tiny vsnprintf implementation. See snprintf above. + */ +#define vsnprintf vsnprintf_ +int vsnprintf_(char* buffer, size_t count, const char* format, va_list va); + + +/** + * printf with output function + * You may use this as dynamic alternative to printf() with its fixed _putchar() output + * \param out An output function which takes one character and an argument pointer + * \param arg An argument pointer for user data passed to output function + * \param format A string that specifies the format of the output + * \return The number of characters that are sent to the output function, not counting the terminating null character + */ +int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...); + + +#ifdef __cplusplus +} +#endif + + +#endif // _PRINTF_H_ diff --git a/test/test_suite.cpp b/test/test_suite.cpp index 5507c3bd..6e4cf643 100644 --- a/test/test_suite.cpp +++ b/test/test_suite.cpp @@ -10,10 +10,10 @@ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -30,50 +30,128 @@ #define CATCH_CONFIG_MAIN #include "catch.hpp" -#include -#include -#include +#include +#include +#include +#include +#include + +static constexpr double nan_double = std::numeric_limits::quiet_NaN(); +static constexpr float nan_float = std::numeric_limits::quiet_NaN(); namespace test { + + // dummy putchar + static char printf_buffer[100]; + static size_t printf_idx = 0U; + + void _putchar(char character) + { + printf_buffer[printf_idx++] = character; + } + + void _out_fct(char character, void* arg) + { + (void)arg; + printf_buffer[printf_idx++] = character; + } + // use functions in own test namespace to avoid stdio conflicts #include "../printf.h" - #include "../printf.c" + #include "../printf.cpp" + } // namespace test -// dummy putchar -static char printf_buffer[100]; -static size_t printf_idx = 0U; +/*********** +* Utilities +***********/ -void test::_putchar(char character) -{ - printf_buffer[printf_idx++] = character; -} +auto adjust_sigfigs( const std::string &in, unsigned desired_sigfigs, unsigned desired_width ) -> std::string { + std::string out(in); + // Find positions of exponent and decimal point. + size_t pos_exponent = out.find_first_of( "eEgG" ); + size_t pos_decimal = out.find( '.' ); + if( pos_exponent == std::string::npos ) { + return in; + } -void _out_fct(char character, void* arg) -{ - (void)arg; - printf_buffer[printf_idx++] = character; + // Insert decimal point if needed. + if( pos_decimal == std::string::npos ) { + out.insert( pos_exponent, "." ); + } + + // Remove the leading spaces, if any. + while( out.length() > 0 && out[0] == ' ' ) { + out.erase(0,1); + } + + // Find positions again. + pos_exponent = out.find_first_of( "eEgG" ); + pos_decimal = out.find( '.' ); + size_t decimal_places_found = (pos_exponent < (pos_decimal + 1))? 0 : (pos_exponent - (pos_decimal + 1)); + size_t current_sigfigs = decimal_places_found + 1; + //std::cout << "aft rm spaces:: desired_sigfigs=" << desired_sigfigs << ", current_sigfigs=" << current_sigfigs << ", decimal_places_found=" << decimal_places_found << ", pos_exponent=" << pos_exponent << ", pos_decimal=" << pos_decimal << std::endl; + + if( current_sigfigs > desired_sigfigs ) { + size_t iz = 1; + // Remove just enough 0's to achieve desired_sigfigs. + while( out.length() > 0 && out[pos_exponent-iz] == '0' ) { + if( iz > (current_sigfigs - desired_sigfigs) ) break; + out.erase(pos_exponent-iz,1); + iz++; + } + } else if( current_sigfigs < desired_sigfigs ) { + // Insert just enough 0's to achieve desired_sigfigs. + for( size_t j = 0; j < desired_sigfigs - current_sigfigs; j++ ) { + out.insert( pos_exponent, "0" ); + } + } + + // Find positions again. + pos_exponent = out.find_first_of( "eEgG" ); + pos_decimal = out.find( '.' ); + decimal_places_found = (pos_exponent < (pos_decimal + 1))? 0 : (pos_exponent - (pos_decimal + 1)); + current_sigfigs = decimal_places_found + 1; + //std::cout << "aft rm/ins 0s:: desired_sigfigs=" << desired_sigfigs << ", current_sigfigs=" << current_sigfigs << ", decimal_places_found=" << decimal_places_found << ", pos_exponent=" << pos_exponent << ", pos_decimal=" << pos_decimal << std::endl; + + // Remove decimal point, if there are now no decimal places. + if( current_sigfigs == 1 ) { + if( out.length() > 0 && out[pos_decimal] == '.' ) { + out.erase(pos_decimal,1); + } + } + + // Insert just enough leading spaces to achieve desired_width. + while( out.length() < desired_width ) { + out.insert( 0, " " ); + } + return out; } + +/*********** +* Test Cases +***********/ + TEST_CASE("printf", "[]" ) { - printf_idx = 0U; - memset(printf_buffer, 0xCC, 100U); + test::printf_idx = 0U; + memset(test::printf_buffer, 0xCC, 100U); REQUIRE(test::printf("% d", 4232) == 5); - REQUIRE(printf_buffer[5] == (char)0xCC); - printf_buffer[5] = 0; - REQUIRE(!strcmp(printf_buffer, " 4232")); + REQUIRE(test::printf_buffer[5] == (char)0xCC); + test::printf_buffer[5] = 0; + REQUIRE(!strcmp(test::printf_buffer, " 4232")); } TEST_CASE("fctprintf", "[]" ) { - printf_idx = 0U; - memset(printf_buffer, 0xCC, 100U); - test::fctprintf(&_out_fct, nullptr, "This is a test of %X", 0x12EFU); - REQUIRE(!strncmp(printf_buffer, "This is a test of 12EF", 22U)); - REQUIRE(printf_buffer[22] == (char)0xCC); + test::printf_idx = 0U; + memset(test::printf_buffer, 0xCC, 100U); + test::fctprintf(&test::_out_fct, nullptr, "This is a test of %X", 0x12EFU); + REQUIRE(!strncmp(test::printf_buffer, "This is a test of 12EF", 22U)); + REQUIRE(test::printf_buffer[22] == (char)0xCC); } @@ -114,12 +192,12 @@ static void vsnprintf_builder_3(char* buffer, ...) TEST_CASE("vprintf", "[]" ) { char buffer[100]; - printf_idx = 0U; - memset(printf_buffer, 0xCC, 100U); + test::printf_idx = 0U; + memset(test::printf_buffer, 0xCC, 100U); vprintf_builder_1(buffer, 2345); - REQUIRE(printf_buffer[4] == (char)0xCC); - printf_buffer[4] = 0; - REQUIRE(!strcmp(printf_buffer, "2345")); + REQUIRE(test::printf_buffer[4] == (char)0xCC); + test::printf_buffer[4] = 0; + REQUIRE(!strcmp(test::printf_buffer, "2345")); } @@ -134,6 +212,394 @@ TEST_CASE("vsnprintf", "[]" ) { } +TEST_CASE("float: various special cases, pt 1", "[]" ) { + char buffer[100]; + + // out of range for float: should switch to exp notation if supported, else empty + test::sprintf(buffer, "%.1f", 1E20); +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL + REQUIRE(!strcmp(buffer, "1.0e+20")); +#else + REQUIRE(!strcmp(buffer, "")); +#endif +} + + +TEST_CASE("float: various special cases, pt 2", "[]" ) { + char buffer[100]; + const char * s = ""; + + { + test::sprintf(buffer, "%0-15.3g", -0.042); +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL + s = "-0.0420 "; +#else + s = "g"; +#endif + CHECK( std::string( buffer ) == s ); + + test::sprintf(buffer, "%0-15.4g", -0.042); +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL + s = "-0.04200 "; +#else + s = "g"; +#endif + CHECK( std::string( buffer ) == s ); + } +} + + +using CaseSpec = struct { const char *fmt; double stimulus; const char *shouldBe; }; + + +TEST_CASE("float: various large exponents", "[]" ) { + char buffer[100]; + { + CaseSpec specs[] = { + { "%9.3f", 1e+200, "1.000e+200" }, + { "%9.3f", 1e-200, "1.000e-200" }, + { "%9.3f", 1e+17, "1.000e+17" }, + { "%9.3f", 1e-17, "1.000e-17" }, + { "%9.3f", 1e+307, "1.000e+307" }, + { "%9.3f", 1e+257, "1.000e+257" }, + { "%9.3f", 1e+207, "1.000e+207" }, + { "%9.3f", 1e+157, "1.000e+157" }, + { "%9.3f", 1e+107, "1.000e+107" }, + { "%9.3f", 1e+87, "1.000e+87" }, + { "%9.3f", 1e+67, "1.000e+67" }, + { "%9.3f", 1e+57, "1.000e+57" }, + { "%9.3f", 1e+47, "1.000e+47" }, + { "%9.3f", 1e+37, "1.000e+37" }, + { "%9.3f", 1e+27, "1.000e+27" }, + { "%9.3f", 1e+17, "1.000e+17" }, + { "%9.3f", 1e-307, "1.000e-307" }, + { "%9.3f", 1e-257, "1.000e-257" }, + { "%9.3f", 1e-207, "1.000e-207" }, + { "%9.3f", 1e-157, "1.000e-157" }, + { "%9.3f", 1e-107, "1.000e-107" }, + { "%9.3f", 1e-87, "1.000e-87" }, + { "%9.3f", 1e-67, "1.000e-67" }, + { "%9.3f", 1e-57, "1.000e-57" }, + { "%9.3f", 1e-47, "1.000e-47" }, + { "%9.3f", 1e-37, "1.000e-37" }, + { "%9.3f", 1e-27, "1.000e-27" }, + { "%9.3f", 1e-17, "1.000e-17" }, + }; + + for( CaseSpec spec : specs ) { + test::sprintf(buffer, spec.fmt, spec.stimulus); + CHECK( std::string( buffer ) == spec.shouldBe ); + } + } +} + + + + +TEST_CASE("float: basics", "[]" ) { + char buffer[100]; + +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL + { + CaseSpec specs[] = { + { "%f", 42167.0, "42167.000000" }, + { "%10.3f", 42167.0, " 42167.000" }, + { "%10.3f", -42167.0, "-42167.000" }, + { "%e", 42167.0, "4.216700e+04" }, + { "%+10.3e", 42167.0, "+4.217e+04" }, + { "%10.3e", -42167.0, "-4.217e+04" }, + { "%g", 42167.0, "42167.0" }, + { "%+10.3g", 42167.0, " +4.22e+04" }, + { "%10.3g", -42167.0, " -4.22e+04" }, + { "%+012.4g", 0.00001234, "+001.234e-05" }, + { "%.3g", -1.2345e-308, "-1.23e-308" }, + { "%+.3E", 1.23e+308, "+1.230E+308" }, + { "%+10.4G", 0.001234, " +0.001234" }, + }; + + for( CaseSpec spec : specs ) { + test::sprintf(buffer, spec.fmt, spec.stimulus); + CHECK( std::string( buffer ) == spec.shouldBe ); + } + } + +#endif + +} + + +TEST_CASE("float, set 1", "[]" ) { + char buffer[100]; + +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL + { + CaseSpec specs[] = { + + // test special-case floats using std::numeric_limits. + { "%8f", nan_double , " nan" }, + { "%8f", static_cast( nan_float ), " nan" }, + { "%8f", std::numeric_limits::infinity() /* INFINITY */ , " inf" }, + { "%-8f", -std::numeric_limits::infinity() /* -INFINITY */ , "-inf " }, + { "%8e", nan_double , " nan" }, + { "%8e", static_cast( nan_float ), " nan" }, + { "%+8e", std::numeric_limits::infinity() /* INFINITY */ , " +inf" }, + { "%-8e", -std::numeric_limits::infinity() /* -INFINITY */ , "-inf " }, + { "%.4f", 3.1415354, "3.1415" }, + { "%.3f", 30343.1415354, "30343.142" }, + { "%.0f", 34.1415354, "34" }, + { "%.0f", 1.3, "1" }, + { "%.0f", 1.55, "2" }, + { "%.1f", 1.64, "1.6" }, + { "%.2f", 42.8952, "42.90" }, + { "%.9f", 42.8952, "42.895200000" }, + { "%.10f", 42.895223, "42.8952230000" }, + // this testcase checks, that the precision is truncated to 9 digits. + // a perfect working float should return the whole number + { "%.12f", 42.89522312345678, "42.895223123000" }, + // this testcase checks, that the precision is truncated AND rounded to 9 digits. + // a perfect working float should return the whole number + { "%.12f", 42.89522387654321, "42.895223877000" }, + { "%6.2f", 42.8952, " 42.90" }, + { "%+6.2f", 42.8952, "+42.90" }, + { "%+5.1f", 42.9252, "+42.9" }, + { "%f", 42.5, "42.500000" }, + { "%.1f", 42.5, "42.5" }, + { "%f", 42167.0, "42167.000000" }, + { "%.9f", -12345.987654321, "-12345.987654321" }, + { "%.1f", 3.999, "4.0" }, + { "%.0f", 3.5, "4" }, + { "%.0f", 4.5, "4" }, + { "%.0f", 3.49, "3" }, + { "%.1f", 3.49, "3.5" }, + { "%.0F", 3.49, "3" }, + { "%.1F", 3.49, "3.5" }, + { "a%-5.1f", 0.5, "a0.5 " }, + { "a%-5.1fend", 0.5, "a0.5 end" }, + }; + + for( CaseSpec spec : specs ) { + test::sprintf(buffer, spec.fmt, spec.stimulus); + CHECK( std::string( buffer ) == spec.shouldBe ); + } + } +#endif +} + + +TEST_CASE("float, set 2", "[]" ) { + char buffer[100]; + +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL + { + CaseSpec specs[] = { + + { "%G", 12345.678, "12345.7" }, + { "%.4G", 12345.678, "1.235E+04" }, + { "%.5G", 12345.678, "12346" }, + { "%.6G", 12345.678, "12345.7" }, + { "%.7G", 12345.678, "12345.68" }, + { "%.5G", 123456789., "1.2346E+08" }, + { "%.6G", 12345., "12345.0" }, + { "%+12.4g", 123456789., " +1.235e+08" }, + { "%.2G", 0.001234, "0.0012" }, + }; + + for( CaseSpec spec : specs ) { + test::sprintf(buffer, spec.fmt, spec.stimulus); + CHECK( std::string( buffer ) == spec.shouldBe ); + } + } +#endif +} + +TEST_CASE("float, set 3", "[]" ) { + char buffer[100]; + +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL + { + CaseSpec specs[] = { + { "%+012.4g", 0.00001234, "+001.234e-05" }, + { "%.3g", -1.2345e-308, "-1.23e-308" }, + { "%+.3E", 1.23e+308, "+1.230E+308" }, + { "%+10.4G", 0.001234, " +0.001234" }, + }; + + for( CaseSpec spec : specs ) { + test::sprintf(buffer, spec.fmt, spec.stimulus); + CHECK( std::string( buffer ) == spec.shouldBe ); + } + } +#endif +} + + +TEST_CASE("float: %g: precision vs exponent, part 1", "[]" ) { + char buffer[100]; + +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL + { + CaseSpec specs[] = { + + { "%7.0g", static_cast(8.34f), " 8" }, + { "%7.0g", static_cast(8.34e1f), " 8e+01" }, + { "%7.0g", static_cast(8.34e2f), " 8e+02" }, + { "%7.1g", static_cast(8.34f), " 8" }, + { "%7.1g", static_cast(8.34e1f), " 8e+01" }, + { "%7.1g", static_cast(8.34e2f), " 8e+02" }, + { "%7.2g", static_cast(8.34f), " 8.3" }, + { "%7.2g", static_cast(8.34e1f), " 83" }, + { "%7.2g", static_cast(8.34e2f), "8.3e+02" }, + { "%7.3g", static_cast(8.34f), " 8.34" }, + { "%7.3g", static_cast(8.34e1f), " 83.4" }, + { "%7.3g", static_cast(8.34e2f), " 834" }, + }; + + for( CaseSpec spec : specs ) { + test::sprintf(buffer, spec.fmt, spec.stimulus); + CHECK( std::string( buffer ) == spec.shouldBe ); + } + } +#endif +} + +TEST_CASE("float: %g: precision vs exponent, part 2", "[]" ) { + char buffer[100]; + +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL + { + CaseSpec specs[] = { + + { "%7.3g", static_cast(8.34e9f), "8.34e+09" }, + { "%7.3g", static_cast(8.34e3f), "8.34e+03" }, + { "%7.3g", static_cast(8.34e-2f), " 0.0834" }, + { "%7.3g", static_cast(8.34e-7f), "8.34e-07" }, + { "%10.7g", static_cast(8.34e9f), "8.340000e+09" }, + { "%10.7g", static_cast(8.34e3f), " 8340.000" }, + { "%10.7g", static_cast(8.34e-2f), "0.08340000" }, + { "%10.7g", static_cast(8.34e-7f), "8.340000e-07" }, + }; + + for( CaseSpec spec : specs ) { + test::sprintf(buffer, spec.fmt, spec.stimulus); + CHECK( std::string( buffer ) == spec.shouldBe ); + } + } +#endif +} + + +TEST_CASE("float: %g: precision vs exponent, part 3", "[]" ) { + char buffer[100]; + +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL + { + CaseSpec specs[] = { + { "%7.3g", static_cast(8.34e-1f), " 0.834" }, + { "%7.3g", static_cast(8.34e-2f), " 0.0834" }, + { "%7.3g", static_cast(8.34e-3f), "0.00834" }, + { "%7.4g", static_cast(8.34e-1f), " 0.8340" }, + { "%7.4g", static_cast(8.34e-2f), "0.08340" }, + { "%7.4g", static_cast(8.34e-3f), "0.008340" }, + }; + + for( CaseSpec spec : specs ) { + test::sprintf(buffer, spec.fmt, spec.stimulus); + CHECK( std::string( buffer ) == spec.shouldBe ); + } + } +#endif +} + + +TEST_CASE("float: %f-to-%e, case 1", "[]" ) { + char buffer[100]; + std::stringstream sstr; + +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL + float f = -9.999999; + for( int i=1; i<20; i++ ) { + sstr.str(""); + sstr.unsetf(std::ios::floatfield); + if( i >= 9 ) { + sstr.precision(4); + sstr.setf(std::ios::scientific); + } else { + sstr.precision(3); + sstr.setf(std::ios::fixed); + } + test::sprintf(buffer, "%10.3f", static_cast(f)); + sstr << std::setw(10) << f; + std::string str2 = adjust_sigfigs( sstr.str(), 4, 10 ); + CHECK( std::string( buffer ) == str2 ); + f *= 10.0f; + } +#endif +} + + +TEST_CASE("float, %f-to-%e, case 2", "[]" ) { + char buffer[100]; + std::stringstream sstr; + +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL + // brute force exp + for (float f = -1e17f; f < +1e17f; f+= 0.9e15f) { + test::sprintf(buffer, "%10.2f", static_cast(f)); + sstr.str(""); + sstr.unsetf(std::ios::floatfield); + sstr.precision(3); + sstr << std::setw(10) << f; + std::string str2 = adjust_sigfigs( sstr.str(), 3, 10 ); + CHECK( std::string( buffer ) == str2 ); + } +#endif +} + + +TEST_CASE("float: %g-to-%e, case 1", "[]" ) { + char buffer[100]; + std::stringstream sstr; + +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL + float f = -999.9999; + for( int i=3; i<20; i++ ) { + sstr.str(""); + sstr.unsetf(std::ios::floatfield); + sstr.precision(3); + if( i >= 3 ) { + sstr.setf(std::ios::scientific); + } else { + sstr.setf(std::ios::fixed); + } + test::sprintf(buffer, "%10.2g", static_cast(f)); + sstr << std::setw(10) << f; + std::string str2 = adjust_sigfigs( sstr.str(), 2, 10 ); + CHECK( std::string( buffer ) == str2 ); + f *= 10.0f; + } +#endif +} + + +TEST_CASE("float, %g-to-%e, case 2", "[]" ) { + char buffer[100]; + std::stringstream sstr; + +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL + // brute force exp + for (float f = -1e17f; f < +1e17f; f+= 0.9e15f) { + test::sprintf(buffer, "%10.3g", static_cast(f)); + sstr.str(""); + sstr.unsetf(std::ios::floatfield); + sstr.precision(3); + sstr << std::setw(10) << f; + std::string str2 = adjust_sigfigs( sstr.str(), 3, 10 ); + CHECK( std::string( buffer ) == str2 ); + } +#endif +} + + TEST_CASE("space flag", "[]" ) { char buffer[100]; @@ -207,7 +673,6 @@ TEST_CASE("space flag", "[]" ) { REQUIRE(!strcmp(buffer, "x")); } - TEST_CASE("+ flag", "[]" ) { char buffer[100]; @@ -311,7 +776,7 @@ TEST_CASE("0 flag", "[]" ) { } -TEST_CASE("- flag", "[]" ) { +TEST_CASE("- flag, part 1", "[]" ) { char buffer[100]; test::sprintf(buffer, "%-d", 42); @@ -374,13 +839,31 @@ TEST_CASE("- flag", "[]" ) { #else REQUIRE(!strcmp(buffer, "e")); #endif +} + + +TEST_CASE("- flag, part 2", "[]" ) { + char buffer[100]; + const char * s = ""; + + { + test::sprintf(buffer, "%0-15.3g", -42.); +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL + s = "-42.0 "; +#else + s = "g"; +#endif + CHECK( std::string( buffer ) == s ); - test::sprintf(buffer, "%0-15.3g", -42.); + test::sprintf(buffer, "%0-15.4g", -42.); #ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL - REQUIRE(!strcmp(buffer, "-42.0 ")); + s = "-42.00 "; #else - REQUIRE(!strcmp(buffer, "g")); + s = "g"; #endif + CHECK( std::string( buffer ) == s ); + } + } @@ -391,7 +874,7 @@ TEST_CASE("# flag", "[]" ) { REQUIRE(!strcmp(buffer, "")); test::sprintf(buffer, "%#.1x", 0); REQUIRE(!strcmp(buffer, "0")); - test::sprintf(buffer, "%#.0llx", (long long)0); + test::sprintf(buffer, "%#.0llx", 0LL); REQUIRE(!strcmp(buffer, "")); test::sprintf(buffer, "%#.8x", 0x614e); REQUIRE(!strcmp(buffer, "0x0000614e")); @@ -936,7 +1419,7 @@ TEST_CASE("padding neg numbers", "[]" ) { } -TEST_CASE("float padding neg numbers", "[]" ) { +TEST_CASE("float padding neg numbers, part 1", "[]" ) { char buffer[100]; // space padding @@ -950,8 +1433,6 @@ TEST_CASE("float padding neg numbers", "[]" ) { REQUIRE(!strcmp(buffer, " -5.0")); #ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL - test::sprintf(buffer, "% 6.1g", -5.); - REQUIRE(!strcmp(buffer, " -5")); test::sprintf(buffer, "% 6.1e", -5.); REQUIRE(!strcmp(buffer, "-5.0e+00")); @@ -986,12 +1467,29 @@ TEST_CASE("float padding neg numbers", "[]" ) { test::sprintf(buffer, "%07.0E", -5.); REQUIRE(!strcmp(buffer, "-05E+00")); +#endif +} - test::sprintf(buffer, "%03.0g", -5.); - REQUIRE(!strcmp(buffer, "-05")); + +TEST_CASE("float padding neg numbers, part 2", "[]" ) { + char buffer[100]; + +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL + { + CaseSpec specs[] = { + { "% 6.1g", -5., " -5" }, + { "%03.0g", -5., "-05" }, + }; + + for( CaseSpec spec : specs ) { + test::sprintf(buffer, spec.fmt, spec.stimulus); + CHECK( std::string( buffer ) == spec.shouldBe ); + } + } #endif } + TEST_CASE("length", "[]" ) { char buffer[100]; @@ -1075,170 +1573,6 @@ TEST_CASE("length", "[]" ) { } -TEST_CASE("float", "[]" ) { - char buffer[100]; - - // 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 ")); - -#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL - test::sprintf(buffer, "%+8e", INFINITY); - REQUIRE(!strcmp(buffer, " +inf")); -#endif - - test::sprintf(buffer, "%.4f", 3.1415354); - REQUIRE(!strcmp(buffer, "3.1415")); - - test::sprintf(buffer, "%.3f", 30343.1415354); - REQUIRE(!strcmp(buffer, "30343.142")); - - test::sprintf(buffer, "%.0f", 34.1415354); - REQUIRE(!strcmp(buffer, "34")); - - test::sprintf(buffer, "%.0f", 1.3); - REQUIRE(!strcmp(buffer, "1")); - - test::sprintf(buffer, "%.0f", 1.55); - REQUIRE(!strcmp(buffer, "2")); - - test::sprintf(buffer, "%.1f", 1.64); - REQUIRE(!strcmp(buffer, "1.6")); - - test::sprintf(buffer, "%.2f", 42.8952); - REQUIRE(!strcmp(buffer, "42.90")); - - test::sprintf(buffer, "%.9f", 42.8952); - REQUIRE(!strcmp(buffer, "42.895200000")); - - test::sprintf(buffer, "%.10f", 42.895223); - REQUIRE(!strcmp(buffer, "42.8952230000")); - - // this testcase checks, that the precision is truncated to 9 digits. - // a perfect working float should return the whole number - test::sprintf(buffer, "%.12f", 42.89522312345678); - REQUIRE(!strcmp(buffer, "42.895223123000")); - - // this testcase checks, that the precision is truncated AND rounded to 9 digits. - // a perfect working float should return the whole number - test::sprintf(buffer, "%.12f", 42.89522387654321); - REQUIRE(!strcmp(buffer, "42.895223877000")); - - test::sprintf(buffer, "%6.2f", 42.8952); - REQUIRE(!strcmp(buffer, " 42.90")); - - test::sprintf(buffer, "%+6.2f", 42.8952); - REQUIRE(!strcmp(buffer, "+42.90")); - - test::sprintf(buffer, "%+5.1f", 42.9252); - REQUIRE(!strcmp(buffer, "+42.9")); - - test::sprintf(buffer, "%f", 42.5); - REQUIRE(!strcmp(buffer, "42.500000")); - - test::sprintf(buffer, "%.1f", 42.5); - REQUIRE(!strcmp(buffer, "42.5")); - - test::sprintf(buffer, "%f", 42167.0); - REQUIRE(!strcmp(buffer, "42167.000000")); - - test::sprintf(buffer, "%.9f", -12345.987654321); - REQUIRE(!strcmp(buffer, "-12345.987654321")); - - test::sprintf(buffer, "%.1f", 3.999); - REQUIRE(!strcmp(buffer, "4.0")); - - test::sprintf(buffer, "%.0f", 3.5); - REQUIRE(!strcmp(buffer, "4")); - - test::sprintf(buffer, "%.0f", 4.5); - REQUIRE(!strcmp(buffer, "4")); - - test::sprintf(buffer, "%.0f", 3.49); - REQUIRE(!strcmp(buffer, "3")); - - test::sprintf(buffer, "%.1f", 3.49); - REQUIRE(!strcmp(buffer, "3.5")); - - test::sprintf(buffer, "a%-5.1f", 0.5); - REQUIRE(!strcmp(buffer, "a0.5 ")); - - test::sprintf(buffer, "a%-5.1fend", 0.5); - REQUIRE(!strcmp(buffer, "a0.5 end")); - -#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL - 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")); -#endif - - // out of range for float: should switch to exp notation if supported, else empty - test::sprintf(buffer, "%.1f", 1E20); -#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL - REQUIRE(!strcmp(buffer, "1.0e+20")); -#else - REQUIRE(!strcmp(buffer, "")); -#endif - - // brute force float - bool fail = false; - std::stringstream str; - str.precision(5); - for (float i = -100000; i < 100000; i += 1) { - test::sprintf(buffer, "%.5f", i / 10000); - str.str(""); - str << std::fixed << i / 10000; - fail = fail || !!strcmp(buffer, str.str().c_str()); - } - REQUIRE(!fail); - - -#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL - // brute force exp - str.setf(std::ios::scientific, std::ios::floatfield); - for (float i = -1e20; i < 1e20; i += 1e15) { - test::sprintf(buffer, "%.5f", i); - str.str(""); - str << i; - fail = fail || !!strcmp(buffer, str.str().c_str()); - } - REQUIRE(!fail); -#endif -} - - TEST_CASE("types", "[]" ) { char buffer[100]; @@ -1479,15 +1813,12 @@ TEST_CASE("ret value", "[]" ) { } -TEST_CASE("misc", "[]" ) { +TEST_CASE("misc, part 1", "[]" ) { char buffer[100]; test::sprintf(buffer, "%u%u%ctest%d %s", 5, 3000, 'a', -20, "bit"); REQUIRE(!strcmp(buffer, "53000atest-20 bit")); - test::sprintf(buffer, "%.*f", 2, 0.33333333); - REQUIRE(!strcmp(buffer, "0.33")); - test::sprintf(buffer, "%.*d", -1, 1); REQUIRE(!strcmp(buffer, "1")); @@ -1502,12 +1833,26 @@ TEST_CASE("misc", "[]" ) { test::sprintf(buffer, "%*sx", -3, "hi"); REQUIRE(!strcmp(buffer, "hi x")); +} -#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL - 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")); +using CaseSpec2 = struct { const char *fmt; int parm; double stimulus; const char *shouldBe; }; + +TEST_CASE("misc, part 2", "[]" ) { + char buffer[100]; + +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL + { + CaseSpec2 specs[] = { + { "%.*f", 2, 0.33333333, "0.33" }, + { "%.*g", 2, 0.33333333, "0.33" }, + { "%.*e", 2, 0.33333333, "3.33e-01" }, + }; + + for( CaseSpec2 spec : specs ) { + test::sprintf(buffer, spec.fmt, spec.parm, spec.stimulus); + CHECK( std::string( buffer ) == spec.shouldBe ); + } + } #endif }