From b14c02e44fe7c9d5bb0576b2577f262f60f2fd95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 15 May 2023 17:29:27 +0100 Subject: [PATCH] Replace nanoprintf with es-printf - Fully replace nanoprintf implementation. - Adjust several System_Number_Format_ handlers. --- .../CorLib/corlib_native_System_Number.cpp | 182 +- src/CLR/Helpers/nanoprintf/nanoprintf.c | 2432 +++++++---------- src/CLR/Helpers/nanoprintf/nanoprintf.h | 442 +-- 3 files changed, 1427 insertions(+), 1629 deletions(-) diff --git a/src/CLR/CorLib/corlib_native_System_Number.cpp b/src/CLR/CorLib/corlib_native_System_Number.cpp index aaf6cc2832..c430110921 100644 --- a/src/CLR/CorLib/corlib_native_System_Number.cpp +++ b/src/CLR/CorLib/corlib_native_System_Number.cpp @@ -131,8 +131,8 @@ int Library_corlib_native_System_Number::DoPrintfOnDataType(char *buffer, char * const char *Library_corlib_native_System_Number::GetPrintfLengthModifier(CLR_DataType dataType) { - const char *ret = (dataType == DATATYPE_I1 || dataType == DATATYPE_U1) ? "hh" - : (dataType == DATATYPE_I2 || dataType == DATATYPE_U2) ? "h" + const char *ret = (dataType == DATATYPE_I1 || dataType == DATATYPE_U1) ? "" + : (dataType == DATATYPE_I2 || dataType == DATATYPE_U2) ? "" : (dataType == DATATYPE_I4 || dataType == DATATYPE_U4) ? "" : (dataType == DATATYPE_I8 || dataType == DATATYPE_U8) ? "ll" : ""; @@ -328,9 +328,6 @@ int Library_corlib_native_System_Number::Format_G( bool isIntegerDataType = IsIntegerDataType(dataType); - // flag to indicate if we're forcing extra precision for the conversion - bool forcedPrecision = true; - // set default precision for the conversion int defaultPrecision = 0; switch (dataType) @@ -354,37 +351,43 @@ int Library_corlib_native_System_Number::Format_G( defaultPrecision = 20; break; case DATATYPE_R4: - defaultPrecision = 7; + // from .NET documentation: + // When used with a Single value, the "G9" format specifier ensures that the original Single value + // successfully round-trips. + defaultPrecision = 9; break; case DATATYPE_R8: - defaultPrecision = 15; + // from .NET documentation: + // When used with a Double value, the "G17" format specifier ensures that the original Double value + // successfully round-trips. + defaultPrecision = 17; break; default: - forcedPrecision = false; break; } + int requestedPrecision = precision; + if (precision == -1) { // no precision specified, use default precision = defaultPrecision; - - // set flag to indicate that we're forcing a precision - forcedPrecision = true; } - else + + int precisionForConversion = precision; + + if (!isIntegerDataType) { - forcedPrecision = false; + precisionForConversion += 2; } if (precision > 0) { + // compose format string char nonIntegerPrecStr[FORMAT_FMTSTR_BUFFER_SIZE]; if (!isIntegerDataType) { - // value of incoming precision would be more than enough - // see diff between printf and ToString precision meaning below - snprintf(nonIntegerPrecStr, FORMAT_FMTSTR_BUFFER_SIZE, "0.%d", precision); + snprintf(nonIntegerPrecStr, FORMAT_FMTSTR_BUFFER_SIZE, "0.%d", precisionForConversion); } char formatStr[FORMAT_FMTSTR_BUFFER_SIZE]; @@ -400,8 +403,7 @@ int Library_corlib_native_System_Number::Format_G( ret = DoPrintfOnDataType(buffer, formatStr, value); - // this extra processing is only required for integer types - if (isIntegerDataType && ret > 0) + if (ret > 0) { // printf and ToString differs on precision numbers: // printf("%.05d", 123.4567890) returns "123.45679" @@ -412,6 +414,25 @@ int Library_corlib_native_System_Number::Format_G( bool isNegative = (buffer[0] == '-'); int offsetBecauseOfNegativeSign = (isNegative ? 1 : 0); int savedResultLength = ret; + int exponent = 0; + + // find the exponent character, start with lower case + char *e = strchr(buffer, 'e'); + + if (!e) + { + // try upper case + e = strchr(buffer, 'E'); + } + + if (e) + { + // move past the exponent character + e++; + + // convert exponent to digits + exponent = atoi(e); + } if (ret > (precision + offsetBecauseOfNegativeSign)) { @@ -419,8 +440,8 @@ int Library_corlib_native_System_Number::Format_G( int numDigits = 0; - // leave just the required amount of digits, if precision was forced - if (forcedPrecision || precision < defaultPrecision) + // leave just the required amount of digits + if (requestedPrecision <= defaultPrecision) { // find the first digit after the dot for (int i = 0; i < ret; i++) @@ -434,12 +455,33 @@ int Library_corlib_native_System_Number::Format_G( ret = i + 1; char first_lost_digit = buffer[ret]; + // handle various situation, like rounding, exponent, rounding errors if (first_lost_digit == '.' && (ret + 1) < savedResultLength) { first_lost_digit = buffer[ret + 1]; + buffer[ret] = 0; + } + else if (first_lost_digit == 'E' || first_lost_digit == 'e') + { + first_lost_digit = buffer[ret - 1]; + buffer[ret] = 0; + ret--; + } + else + { + buffer[ret] = 0; } - buffer[ret] = 0; + if (!isIntegerDataType && buffer[ret - 1] == '0') + { + // drop last digit in case it's a rounding digit + memmove(&buffer[ret], &buffer[ret + 1], savedResultLength - ret); + + buffer[ret] = 0; + ret--; + + break; + } if (first_lost_digit >= '5') { @@ -479,11 +521,11 @@ int Library_corlib_native_System_Number::Format_G( } } - if ((dotIndex == -1) || (dotIndex > (precision + offsetBecauseOfNegativeSign))) + if ((dotIndex == -1) || (dotIndex > (requestedPrecision + offsetBecauseOfNegativeSign))) { // insert '.', only if request precision requires it - // this is: precision is specified and is more than 1 (taking into account the sign) - if (precision > 0 && (ret - offsetBecauseOfNegativeSign) > 1) + // this is: requestedPrecision is specified and is more than 1 (taking into account the sign) + if (requestedPrecision > 0 && (ret - offsetBecauseOfNegativeSign) > 1) { memmove( &buffer[2 + offsetBecauseOfNegativeSign], @@ -494,16 +536,20 @@ int Library_corlib_native_System_Number::Format_G( ret++; } - // append 'E+exp' - int exponent = (dotIndex == -1) ? savedResultLength - 1 : dotIndex - 1; - exponent -= offsetBecauseOfNegativeSign; + // deal with 'E+exp' + if (exponent == 0) + { + exponent = (dotIndex == -1) ? savedResultLength - 1 : dotIndex - 1; + exponent -= offsetBecauseOfNegativeSign; + } + if (formatChar == 'g') { - ret += snprintf(&buffer[ret], FORMAT_RESULT_BUFFER_SIZE - ret, "e+%02d", exponent); + ret += snprintf(&buffer[ret], FORMAT_RESULT_BUFFER_SIZE - ret, "e%+.2d", exponent); } else { - ret += snprintf(&buffer[ret], FORMAT_RESULT_BUFFER_SIZE - ret, "E+%02d", exponent); + ret += snprintf(&buffer[ret], FORMAT_RESULT_BUFFER_SIZE - ret, "E%+.2d", exponent); } } } @@ -712,7 +758,7 @@ int Library_corlib_native_System_Number::Format_E(char *buffer, CLR_RT_HeapBlock int requestedPrecision = precision; // force extra precision to account for rounding errors - precision = requestedPrecision + 4; + precision = requestedPrecision + 1; CLR_DataType dataType = value->DataType(); @@ -762,19 +808,7 @@ int Library_corlib_native_System_Number::Format_E(char *buffer, CLR_RT_HeapBlock if (ret > 0) { - int dotIndex = GetDotIndex(buffer, ret); - - // if there is no dot, set the index to the end of the string - if (dotIndex == -1) - { - dotIndex = ret; - } - - // remove extra precision - memmove(&buffer[dotIndex + requestedPrecision + 1], &buffer[dotIndex + precision + 1], ret); - - // adjust the string length - ret -= precision + 1 - requestedPrecision; + int exponent = 0; // find the exponent character, start with lower case char *e = strchr(buffer, 'e'); @@ -791,10 +825,66 @@ int Library_corlib_native_System_Number::Format_E(char *buffer, CLR_RT_HeapBlock e++; // convert exponent to digits - int expo = atoi(e); + exponent = atoi(e); + } + + int numDigits = 0; + int savedResultLength = ret; + + int dotIndex = GetDotIndex(buffer, ret); + + // if there is no dot, set the index to the end of the string + if (dotIndex == -1) + { + dotIndex = ret; + } - // print the exponent with the correct sign and size - snprintf(e, FORMAT_RESULT_BUFFER_SIZE, "%+.3d", expo); + // leave just the required amount of digits + if (requestedPrecision <= precision) + { + // find the first digit after the dot + for (int i = 0; i < ret; i++) + { + if (buffer[i] >= '0' && buffer[i] <= '9') + { + numDigits++; + + if (numDigits == precision) + { + ret = i + 1; + char first_lost_digit = buffer[ret]; + + if (first_lost_digit == '.' && (ret + 1) < savedResultLength) + { + first_lost_digit = buffer[ret + 1]; + } + + buffer[ret] = 0; + + if (first_lost_digit >= '5') + { + int savedRet = ret; + RoundUpNumStr(buffer, &ret); + + if (savedRet < ret) + { + dotIndex++; + } + } + break; + } + } + } + } + + // now the exponent + if (formatChar == 'e') + { + snprintf(&buffer[ret], FORMAT_RESULT_BUFFER_SIZE, "e%+.3d", exponent); + } + else + { + snprintf(&buffer[ret], FORMAT_RESULT_BUFFER_SIZE, "E%+.3d", exponent); } } diff --git a/src/CLR/Helpers/nanoprintf/nanoprintf.c b/src/CLR/Helpers/nanoprintf/nanoprintf.c index b435e05061..e003fe5f03 100644 --- a/src/CLR/Helpers/nanoprintf/nanoprintf.c +++ b/src/CLR/Helpers/nanoprintf/nanoprintf.c @@ -1,1633 +1,1243 @@ // // Copyright (c) .NET Foundation and Contributors -// Portions Copyright (c) 2014-2019 Marco Paland (info@paland.com). All rights reserved. -// Portions Copyright (c) 2021-2022 Eyal Rozenberg . All rights reserved. +// Portions Copyright (c) 2006 - 2021 Skirrid Systems. All rights reserved. // See LICENSE file in the project root for full license information. // -#include "nanoprintf.h" - -#ifdef __cplusplus -#include -#include -#else -#include -#include -#include -#endif // __cplusplus - -#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES -#define printf_ printf -#define sprintf_ sprintf -#define vsprintf_ vsprintf -#define snprintf_ snprintf -#define vsnprintf_ vsnprintf -#define vprintf_ vprintf -#endif - -// 'ntoa' conversion buffer size, this must be big enough to hold one converted -// numeric number including padded zeros (dynamically created on stack) -#ifndef PRINTF_INTEGER_BUFFER_SIZE -#define PRINTF_INTEGER_BUFFER_SIZE 32 -#endif - -// size of the fixed (on-stack) buffer for printing individual decimal numbers. -// this must be big enough to hold one converted floating-point value including -// padded zeros. -#ifndef PRINTF_DECIMAL_BUFFER_SIZE -#define PRINTF_DECIMAL_BUFFER_SIZE 32 -#endif - -// Support for the decimal notation floating point conversion specifiers (%f, %F) -#ifndef PRINTF_SUPPORT_DECIMAL_SPECIFIERS -#define PRINTF_SUPPORT_DECIMAL_SPECIFIERS 1 -#endif - -// Support for the exponential notation floating point conversion specifiers (%e, %g, %E, %G) -#ifndef PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS -#define PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 1 -#endif - -// Support for the length write-back specifier (%n) -#ifndef PRINTF_SUPPORT_WRITEBACK_SPECIFIER -#define PRINTF_SUPPORT_WRITEBACK_SPECIFIER 1 -#endif - -// Default precision for the floating point conversion specifiers (the C standard sets this at 6) -#ifndef PRINTF_DEFAULT_FLOAT_PRECISION -#define PRINTF_DEFAULT_FLOAT_PRECISION 6 -#endif - -// According to the C languages standard, printf() and related functions must be able to print any -// integral number in floating-point notation, regardless of length, when using the %f specifier - -// possibly hundreds of characters, potentially overflowing your buffers. In this implementation, -// all values beyond this threshold are switched to exponential notation. -#ifndef PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL -#define PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL 9 -#endif +// clang-format off -// Support for the long long integral types (with the ll, z and t length modifiers for specifiers -// %d,%i,%o,%x,%X,%u, and with the %p specifier). Note: 'L' (long double) is not supported. -#ifndef PRINTF_SUPPORT_LONG_LONG -#define PRINTF_SUPPORT_LONG_LONG 1 -#endif +// System and config header files are included by the parent stub file. +#include +#include +#include "nanoprintf.h" +#include -// The number of terms in a Taylor series expansion of log_10(x) to -// use for approximation - including the power-zero term (i.e. the -// value at the point of expansion). -#ifndef PRINTF_LOG10_TAYLOR_TERMS -#define PRINTF_LOG10_TAYLOR_TERMS 4 +/* Define default macro to access the format string using a pointer. */ +#ifndef GET_FORMAT + #define GET_FORMAT(p) (*(p)) #endif -#if PRINTF_LOG10_TAYLOR_TERMS <= 1 -#error "At least one non-constant Taylor expansion is necessary for the log10() calculation" +/* Define default function for printf output. */ +#ifndef PUTCHAR_FUNC + #define PUTCHAR_FUNC putchar #endif -// Be extra-safe, and don't assume format specifiers are completed correctly -// before the format string end. -#ifndef PRINTF_CHECK_FOR_NUL_IN_FORMAT_SPECIFIER -#define PRINTF_CHECK_FOR_NUL_IN_FORMAT_SPECIFIER 1 +/* Renames the functions if they have been defined as macros in printf.h */ +#ifdef printf + #undef printf + #define printf _prntf #endif -#define PRINTF_PREFER_DECIMAL false -#define PRINTF_PREFER_EXPONENTIAL true - -/////////////////////////////////////////////////////////////////////////////// - -// The following will convert the number-of-digits into an exponential-notation literal -#define PRINTF_CONCATENATE(s1, s2) s1##s2 -#define PRINTF_EXPAND_THEN_CONCATENATE(s1, s2) PRINTF_CONCATENATE(s1, s2) -#define PRINTF_FLOAT_NOTATION_THRESHOLD PRINTF_EXPAND_THEN_CONCATENATE(1e, PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL) - -// 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_INT (1U << 8U) -// Only used with PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS -#define FLAGS_LONG (1U << 9U) -#define FLAGS_LONG_LONG (1U << 10U) -#define FLAGS_PRECISION (1U << 11U) -#define FLAGS_ADAPT_EXP (1U << 12U) -#define FLAGS_POINTER (1U << 13U) -// Note: Similar, but not identical, effect as FLAGS_HASH -#define FLAGS_SIGNED (1U << 14U) -// Only used with PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS - -#ifdef PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS - -#define FLAGS_INT8 FLAGS_CHAR - -#if (SHRT_MAX == 32767LL) -#define FLAGS_INT16 FLAGS_SHORT -#elif (INT_MAX == 32767LL) -#define FLAGS_INT16 FLAGS_INT -#elif (LONG_MAX == 32767LL) -#define FLAGS_INT16 FLAGS_LONG -#elif (LLONG_MAX == 32767LL) -#define FLAGS_INT16 FLAGS_LONG_LONG -#else -#error "No basic integer type has a size of 16 bits exactly" +#ifdef sprintf + #undef sprintf + #define sprintf _sprntf #endif -#if (SHRT_MAX == 2147483647LL) -#define FLAGS_INT32 FLAGS_SHORT -#elif (INT_MAX == 2147483647LL) -#define FLAGS_INT32 FLAGS_INT -#elif (LONG_MAX == 2147483647LL) -#define FLAGS_INT32 FLAGS_LONG -#elif (LLONG_MAX == 2147483647LL) -#define FLAGS_INT32 FLAGS_LONG_LONG +// Macro used to check presence of a feature flag. +// These are defined in print_cfg.h +#define FEATURE(flag) ((FEATURE_FLAGS) & (flag)) + +// Size of buffer for formatting numbers into. +// Use the smallest buffer we can get away with to conserve RAM. +#if FEATURE(USE_BINARY) + #if FEATURE(USE_LONG) + #define BUFMAX 32 + #elif FEATURE(USE_FLOAT) + #define BUFMAX 30 + #else + #define BUFMAX 16 + #endif #else -#error "No basic integer type has a size of 32 bits exactly" + #if FEATURE(USE_FLOAT) + #define BUFMAX 30 + #elif FEATURE(USE_LONG_LONG) + #define BUFMAX 22 + #else + #define BUFMAX 16 + #endif #endif -#if (SHRT_MAX == 9223372036854775807LL) -#define FLAGS_INT64 FLAGS_SHORT -#elif (INT_MAX == 9223372036854775807LL) -#define FLAGS_INT64 FLAGS_INT -#elif (LONG_MAX == 9223372036854775807LL) -#define FLAGS_INT64 FLAGS_LONG -#elif (LLONG_MAX == 9223372036854775807LL) -#define FLAGS_INT64 FLAGS_LONG_LONG +// Bit definitions in the flags variable (integer and general) +#if FEATURE(USE_LEFT_JUST) + #define FL_LEFT_JUST (1<<0) #else -#error "No basic integer type has a size of 64 bits exactly" + #define FL_LEFT_JUST 0 #endif - -#endif // PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS - -typedef unsigned int printf_flags_t; - -#define BASE_BINARY 2 -#define BASE_OCTAL 8 -#define BASE_DECIMAL 10 -#define BASE_HEX 16 - -typedef uint8_t numeric_base_t; - -#if PRINTF_SUPPORT_LONG_LONG -typedef unsigned long long printf_unsigned_value_t; -typedef long long printf_signed_value_t; +#define FL_ZERO_PAD (1<<1) +#define FL_SPECIAL (1<<2) +#define FL_PLUS (1<<3) +#define FL_SPACE (1<<4) +#define FL_NEG (1<<5) +#define FL_LONG (1<<6) +#define FL_FSTR (1<<7) + +// Bit definitions in the fflags variable (mostly floating point) +#define FF_UCASE (1<<0) +#define FF_ECVT (1<<1) +#define FF_FCVT (1<<2) +#define FF_GCVT (1<<3) +#define FF_NRND (1<<4) +#define FF_XLONG (1<<5) + +// Check whether integer or octal support is needed. +#define HEX_CONVERT_ONLY !(FEATURE(USE_SIGNED) || FEATURE(USE_SIGNED_I) || FEATURE(USE_UNSIGNED) || \ + FEATURE(USE_OCTAL) || FEATURE(USE_BINARY)) + +/***************************************************************************** +Floating point +******************************************************************************/ +#if FEATURE(USE_FLOAT) + +// Most compilers can support double precision +#ifndef NO_DOUBLE_PRECISION + #define DP_LIMIT 310 + #define MAX_POWER 256 +// [NF_CHANGE] + #define FLOAT_DIGITS 18 +// [END_NF_CHANGE] #else -typedef unsigned long printf_unsigned_value_t; -typedef long printf_signed_value_t; + #define DP_LIMIT 40 + #define MAX_POWER 32 + #define FLOAT_DIGITS 8 #endif -// The printf()-family functions return an `int`; it is therefore -// unnecessary/inappropriate to use size_t - often larger than int -// in practice - for non-negative related values, such as widths, -// precisions, offsets into buffers used for printing and the sizes -// of these buffers. instead, we use: -typedef unsigned int printf_size_t; -#define PRINTF_MAX_POSSIBLE_BUFFER_SIZE INT_MAX -// If we were to nitpick, this would actually be INT_MAX + 1, -// since INT_MAX is the maximum return value, which excludes the -// trailing '\0'. - -#if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) -#include -#if FLT_RADIX != 2 -#error "Non-binary-radix floating-point types are unsupported." +#if !FEATURE(USE_SMALL_FLOAT) + // Floating point normalisation tables for fast normalisation. + // smalltable[] is used for value < 1.0 + static const double smalltable[] = { + #ifndef NO_DOUBLE_PRECISION + 1e-256, 1e-128, 1e-64, + #endif + 1e-32, 1e-16, 1e-8, 1e-4, 1e-2, 1e-1, 1.0 + }; + // large table[] is used for value >= 10.0 + static const double largetable[] = { + #ifndef NO_DOUBLE_PRECISION + 1e+256, 1e+128, 1e+64, + #endif + 1e+32, 1e+16, 1e+8, 1e+4, 1e+2, 1e+1 + }; #endif -#if DBL_MANT_DIG == 24 - -#define DOUBLE_SIZE_IN_BITS 32 -typedef uint32_t double_uint_t; -#define DOUBLE_EXPONENT_MASK 0xFFU -#define DOUBLE_BASE_EXPONENT 127 -#define DOUBLE_MAX_SUBNORMAL_EXPONENT_OF_10 -38 -#define DOUBLE_MAX_SUBNORMAL_POWER_OF_10 1e-38 - -#elif DBL_MANT_DIG == 53 - -#define DOUBLE_SIZE_IN_BITS 64 -typedef uint64_t double_uint_t; -#define DOUBLE_EXPONENT_MASK 0x7FFU -#define DOUBLE_BASE_EXPONENT 1023 -#define DOUBLE_MAX_SUBNORMAL_EXPONENT_OF_10 -308 -#define DOUBLE_MAX_SUBNORMAL_POWER_OF_10 1e-308 - +#ifdef NO_DOUBLE_PRECISION + // Double precision not supported by compiler. + // Single precision numbers up to 10^38 require only 8-bit exponent. + typedef signed char flt_width_t; #else -#error "Unsupported double type configuration" + // Double precision numbers up to 10^308 require 16-bit exponents. + typedef signed short flt_width_t; #endif -#define DOUBLE_STORED_MANTISSA_BITS (DBL_MANT_DIG - 1) - -typedef union { - double_uint_t U; - double F; -} double_with_bit_access; - -void putchar_(char character) -{ - (void)character; -} - -// This is unnecessary in C99, since compound initializers can be used, -// but: -// 1. Some compilers are finicky about this; -// 2. Some people may want to convert this to C89; -// 3. If you try to use it as C++, only C++20 supports compound literals -static inline double_with_bit_access get_bit_access(double x) -{ - double_with_bit_access dwba; - dwba.F = x; - return dwba; -} -static inline int get_sign_bit(double x) -{ - // The sign is stored in the highest bit - return (int)(get_bit_access(x).U >> (DOUBLE_SIZE_IN_BITS - 1)); -} +// These define the maximum number of digits that can be output into the buffer. +// %f requires space for sign, point and rounding digit. +#define DIGITS_MAX_F (BUFMAX-3) +// %e requires space for sign, point and up to 3 digit exponent (E+123). +// Rounding happens in the place where the exponent will go. +#define DIGITS_MAX_E (BUFMAX-7) -static inline int get_exp2(double_with_bit_access x) +// Function to trim trailing zeros and DP in 'g' mode. +// Return pointer to new string terminator position. +static char *trim_zeros(char *p) { - // The exponent in an IEEE-754 floating-point number occupies a contiguous - // sequence of bits (e.g. 52..62 for 64-bit doubles), but with a non-trivial representation: An - // unsigned offset from some negative value (with the extremal offset values reserved for - // special use). - return (int)((x.U >> DOUBLE_STORED_MANTISSA_BITS) & DOUBLE_EXPONENT_MASK) - DOUBLE_BASE_EXPONENT; + // Trim trailing 0's in 'g' mode. + while (*p == '0') + p--; + // If last non-zero is DP, trim that as well. + if (*p != '.') ++p; + return p; } -#define PRINTF_ABS(_x) ((_x) > 0 ? (_x) : -(_x)) - -#endif // (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) - -// Note in particular the behavior here on LONG_MIN or LLONG_MIN; it is valid -// and well-defined, but if you're not careful you can easily trigger undefined -// behavior with -LONG_MIN or -LLONG_MIN -#define ABS_FOR_PRINTING(_x) ((printf_unsigned_value_t)((_x) > 0 ? (_x) : -((printf_signed_value_t)_x))) - -// wrapper (used as buffer) for output function type -// -// One of the following must hold: -// 1. max_chars is 0 -// 2. buffer is non-null -// 3. function is non-null -// -// ... otherwise bad things will happen. -typedef struct -{ - void (*function)(char c, void *extra_arg); - void *extra_function_arg; - char *buffer; - printf_size_t pos; - printf_size_t max_chars; -} output_gadget_t; -// Note: This function currently assumes it is not passed a '\0' c, -// or alternatively, that '\0' can be passed to the function in the output -// gadget. The former assumption holds within the printf library. It also -// assumes that the output gadget has been properly initialized. -static inline void putchar_via_gadget(output_gadget_t *gadget, char c) +/* --------------------------------------------------------------------------- +Function: format_float() +Called from the main doprnt function to handle formatting of floating point +values. The general process is: +1. Check for non-numbers and just display the error code. +2. Convert number to positive form. +3. Normalise the number to lie in the range 1.0 <= number < 10.0 +4. Work out where the DP lies and optimum format. +5. Output the digits in reverse order. +--------------------------------------------------------------------------- */ +static char *format_float(double number, flt_width_t ndigits, flt_width_t width, + unsigned char flags, unsigned char fflags, char *buf) { - printf_size_t write_pos = gadget->pos++; - // We're _always_ increasing pos, so as to count how may characters - // _would_ have been written if not for the max_chars limitation - if (write_pos >= gadget->max_chars) - { - return; - } - if (gadget->function != NULL) + flt_width_t decpt; + flt_width_t nzero; + unsigned char i; + char *p = buf + 2; + char *pend; + +#ifndef NO_ISNAN_ISINF + // Handle special values which need no formatting + if (isinf(number)) { - // No check for c == '\0' . - gadget->function(c, gadget->extra_function_arg); + buf[0] = 'I'; + buf[1] = 'n'; + buf[2] = 'f'; + buf[3] = '\0'; + return buf; } - else + if (isnan(number)) { - // it must be the case that gadget->buffer != NULL , due to the constraint - // on output_gadget_t ; and note we're relying on write_pos being non-negative. - gadget->buffer[write_pos] = c; + buf[0] = 'N'; + buf[1] = 'a'; + buf[2] = 'N'; + buf[3] = '\0'; + return buf; } -} +#endif -// Possibly-write the string-terminating '\0' character -static inline void append_termination_with_gadget(output_gadget_t *gadget) -{ - if (gadget->function != NULL || gadget->max_chars == 0) - { - return; - } - if (gadget->buffer == NULL) + // Handle all numbers as if they were positive. + if (number < 0) { - return; + number = -number; + flags |= FL_NEG; } - printf_size_t null_char_pos = gadget->pos < gadget->max_chars ? gadget->pos : gadget->max_chars - 1; - gadget->buffer[null_char_pos] = '\0'; -} - -// We can't use putchar_ as is, since our output gadget -// only takes pointers to functions with an extra argument -static inline void putchar_wrapper(char c, void *unused) -{ - (void)unused; - putchar_(c); -} - -static inline output_gadget_t discarding_gadget(void) -{ - output_gadget_t gadget; - gadget.function = NULL; - gadget.extra_function_arg = NULL; - gadget.buffer = NULL; - gadget.pos = 0; - gadget.max_chars = 0; - return gadget; -} -static inline output_gadget_t buffer_gadget(char *buffer, size_t buffer_size) -{ - printf_size_t usable_buffer_size = - (buffer_size > PRINTF_MAX_POSSIBLE_BUFFER_SIZE) ? PRINTF_MAX_POSSIBLE_BUFFER_SIZE : (printf_size_t)buffer_size; - output_gadget_t result = discarding_gadget(); - if (buffer != NULL) + if (fflags & FF_FCVT) { - result.buffer = buffer; - result.max_chars = usable_buffer_size; + // Number of DP cannot be negative. + if (ndigits < 0) + ndigits = 0; } - return result; -} - -static inline output_gadget_t function_gadget(void (*function)(char, void *), void *extra_arg) -{ - output_gadget_t result = discarding_gadget(); - result.function = function; - result.extra_function_arg = extra_arg; - result.max_chars = PRINTF_MAX_POSSIBLE_BUFFER_SIZE; - return result; -} - -static inline output_gadget_t extern_putchar_gadget(void) -{ - return function_gadget(putchar_wrapper, NULL); -} - -// internal secure strlen -// @return The length of the string (excluding the terminating 0) limited by 'maxsize' -// @note strlen uses size_t, but wes only use this function with printf_size_t -// variables - hence the signature. -static inline printf_size_t strnlen_s_(const char *str, printf_size_t maxsize) -{ - const char *s; - for (s = str; *s && maxsize--; ++s) - ; - return (printf_size_t)(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 printf_size_t conversion -static printf_size_t atou_(const char **str) -{ - printf_size_t i = 0U; - while (is_digit_(**str)) + else { - i = i * 10U + (printf_size_t)(*((*str)++) - '0'); + // Significant digits must be at least 1. + if (ndigits < 1) + ndigits = 1; } - return i; -} - -// output the specified string in reverse, taking care of any zero-padding -static void out_rev_( - output_gadget_t *output, - const char *buf, - printf_size_t len, - printf_size_t width, - printf_flags_t flags) -{ - const printf_size_t start_pos = output->pos; - // pad spaces up to given width - if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) - { - for (printf_size_t i = len; i < width; i++) - { - putchar_via_gadget(output, ' '); - } - } + // Prefill buffer with zeros. + for (i = 0; i < BUFMAX; i++) + buf[i] = '0'; - // reverse string - while (len) + if (number == 0) { - putchar_via_gadget(output, buf[--len]); + // Special case to correct number of decimals, significant figures, + // and avoid printing 0E-00. + decpt = 1; } - - // append pad spaces up to given width - if (flags & FLAGS_LEFT) + else { - while (output->pos - start_pos < width) + // Normalise the number such that it lies in the range 1 <= n < 10. +#if FEATURE(USE_SMALL_FLOAT) + /* Normalise using a linear search. This code is simple and uses + * least code space. It also eliminates the lookup tables which may + * otherwise take up RAM space. However, it can take many more operations + * to execute for the full range of floating point values, and may + * introduce rounding errors. Use only when space is critical. + */ + // First make small numbers bigger. + decpt = 1; + while (number < 1.0) { - putchar_via_gadget(output, ' '); + number *= 10.0; + --decpt; } - } -} - -// Invoked by print_integer after the actual number has been printed, performing necessary -// work on the number's prefix (as the number is initially printed in reverse order) -static void print_integer_finalization( - output_gadget_t *output, - char *buf, - printf_size_t len, - bool negative, - numeric_base_t base, - printf_size_t precision, - printf_size_t width, - printf_flags_t flags) -{ - printf_size_t unpadded_len = len; - - // pad with leading zeros - { - if (!(flags & FLAGS_LEFT)) + // Then make big numbers smaller. + while (number >= 10.0) { - if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) + number /= 10.0; + ++decpt; +#ifdef NO_ISNAN_ISINF + // Avoid this loop hanging on infinity. + if (decpt > DP_LIMIT) { - width--; - } - while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_INTEGER_BUFFER_SIZE)) - { - buf[len++] = '0'; + buf[0] = 'I'; + buf[1] = 'n'; + buf[2] = 'f'; + buf[3] = '\0'; + return buf; } +#endif } +#else + flt_width_t power10 = MAX_POWER; + decpt = 1; - while ((len < precision) && (len < PRINTF_INTEGER_BUFFER_SIZE)) + if(number == DBL_MAX) { - buf[len++] = '0'; + number = 1.7976931348623157; + decpt = 309; } - - if (base == BASE_OCTAL && (len > unpadded_len)) + else if(number == DBL_MIN) { - // Since we've written some zeros, we've satisfied the alternative format leading space requirement - flags &= ~FLAGS_HASH; - } - } - - // handle hash - if (flags & (FLAGS_HASH | FLAGS_POINTER)) - { - if (!(flags & FLAGS_PRECISION) && len && ((len == precision) || (len == width))) + number = -1.7976931348623157; + decpt = -309; + } + else { - // Let's take back some padding digits to fit in what will eventually - // be the format-specific prefix - if (unpadded_len < len) + /* Normalise using a binary search, making the largest possible + * adjustment first and getting progressively smaller. This gets + * to the answer in the fastest time, with the minimum number of + * operations to introduce rounding errors. + */ + // First make small numbers bigger. + + i = 0; + while (number < 1.0) { - len--; // This should suffice for BASE_OCTAL + while (number < smalltable[i + 1]) + { + number /= smalltable[i]; + decpt -= power10; + } + power10 >>= 1; + i++; } - if (len && (base == BASE_HEX || base == BASE_BINARY) && (unpadded_len < len)) + // Then make big numbers smaller. + power10 = MAX_POWER; + i = 0; + while (number >= 10.0) { - len--; // ... and an extra one for 0x or 0b + while (number >= largetable[i]) + { + number /= largetable[i]; + decpt += power10; +#ifdef NO_ISNAN_ISINF + // Avoid this loop hanging on infinity. + if (decpt > DP_LIMIT) + { + buf[0] = 'I'; + buf[1] = 'n'; + buf[2] = 'f'; + buf[3] = '\0'; + return buf; + } +#endif + } + power10 >>= 1; + i++; } } - if ((base == BASE_HEX) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_INTEGER_BUFFER_SIZE)) - { - buf[len++] = 'x'; - } - else if ((base == BASE_HEX) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_INTEGER_BUFFER_SIZE)) - { - buf[len++] = 'X'; - } - else if ((base == BASE_BINARY) && (len < PRINTF_INTEGER_BUFFER_SIZE)) - { - buf[len++] = 'b'; - } - if (len < PRINTF_INTEGER_BUFFER_SIZE) - { - buf[len++] = '0'; - } +#endif } - - if (len < PRINTF_INTEGER_BUFFER_SIZE) + + // For g conversions determine whether to use e or f mode. + if (fflags & FF_GCVT) { - if (negative) - { - buf[len++] = '-'; - } - else if (flags & FLAGS_PLUS) + /* 'g' format uses 'f' notation where it can and + * 'e' notation where the exponent is more extreme. + * Some references indicate that it uses the more + * compact form but the ANSI standard give an explict + * definition: Use 'e' when the exponent is < -4 + * or where the exponent is >= ndigits. + * The exponent is equal to decpt - 1 so this translates + * to decpt <= -4 || decpt > ndigits + * http://www.acm.uiuc.edu/webmonkeys/book/c_guide/2.12.html#printf + * http://www.mkssoftware.com/docs/man3/printf.3.asp + */ + if (decpt > -4 && decpt <= ndigits) + fflags |= FF_FCVT; + } + + // Sanitise ndigits making sure it fits buffer space. + if (fflags & FF_FCVT) + { + if (!(fflags & FF_GCVT)) { - buf[len++] = '+'; // ignore the space if the '+' exists + // For fcvt operation the number of digits is used to + // refer to decimal places rather than significant digits. + ndigits += decpt; + // When there are no significant digits, + // avoid printing too many 0's. + if (ndigits < 0) + { + decpt -= ndigits; + } } - else if (flags & FLAGS_SPACE) + /* For 'f' conversions with positive DP value that would overflow + * the buffer space, there is no way to display this so fall back + * to 'e' format. 'f' conversion needs space for sign, point and + * rounding digit. The point may be forced using the special flag + * and the rounding digit may be included in the case of maximum + * overflow. + */ + if (decpt > DIGITS_MAX_F) { - buf[len++] = ' '; + fflags &= ~FF_FCVT; } } - out_rev_(output, buf, len, width, flags); -} - -// An internal itoa-like function -static void print_integer( - output_gadget_t *output, - printf_unsigned_value_t value, - bool negative, - numeric_base_t base, - printf_size_t precision, - printf_size_t width, - printf_flags_t flags) -{ - char buf[PRINTF_INTEGER_BUFFER_SIZE]; - printf_size_t len = 0U; - - if (!value) + nzero = 0; + if (fflags & FF_FCVT) { - if (!(flags & FLAGS_PRECISION)) + if (decpt < 1) { - buf[len++] = '0'; - flags &= ~FLAGS_HASH; - // We drop this flag this since either the alternative and regular modes of the specifier - // don't differ on 0 values, or (in the case of octal) we've already provided the special - // handling for this mode. + // Values < 1 require leading zeros before the real digits. + nzero = 1 - decpt; } - else if (base == BASE_HEX) + // Check for buffer fit. Zeros take precedence. + if (nzero > DIGITS_MAX_F) { - flags &= ~FLAGS_HASH; - // We drop this flag this since either the alternative and regular modes of the specifier - // don't differ on 0 values + nzero = DIGITS_MAX_F; + ndigits = -1; } - } - else - { - do + if (ndigits < 0) { - const char digit = (char)(value % base); - buf[len++] = (char)(digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10); - value /= base; - } while (value && (len < PRINTF_INTEGER_BUFFER_SIZE)); - } - - print_integer_finalization(output, buf, len, negative, base, precision, width, flags); -} - -#if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) - -// Stores a fixed-precision representation of a double relative -// to a fixed precision (which cannot be determined by examining this structure) -struct double_components -{ - int_fast64_t integral; - int_fast64_t fractional; - // ... truncation of the actual fractional part of the double value, scaled - // by the precision value - bool is_negative; -}; - -#define NUM_DECIMAL_DIGITS_IN_INT64_T 18 -#define PRINTF_MAX_PRECOMPUTED_POWER_OF_10 NUM_DECIMAL_DIGITS_IN_INT64_T -static const double powers_of_10[NUM_DECIMAL_DIGITS_IN_INT64_T] = - {1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17}; - -#define PRINTF_MAX_SUPPORTED_PRECISION NUM_DECIMAL_DIGITS_IN_INT64_T - 1 - -// Break up a double number - which is known to be a finite non-negative number - -// into its base-10 parts: integral - before the decimal point, and fractional - after it. -// Taken the precision into account, but does not change it even internally. -static struct double_components get_components(double number, printf_size_t precision) -{ - struct double_components number_; - number_.is_negative = get_sign_bit(number); - double abs_number = (number_.is_negative) ? -number : number; - number_.integral = (int_fast64_t)abs_number; - double remainder = (abs_number - (double)number_.integral) * powers_of_10[precision]; - number_.fractional = (int_fast64_t)remainder; - - remainder -= (double)number_.fractional; - - if (remainder > 0.5) - { - ++number_.fractional; - // handle rollover, e.g. case 0.99 with precision 1 is 1.0 - if ((double)number_.fractional >= powers_of_10[precision]) - { - number_.fractional = 0; - ++number_.integral; + // First significant digit is below rounding range. + fflags |= FF_NRND; + ndigits = 0; } - } - else if ((remainder == 0.5) && ((number_.fractional == 0U) || (number_.fractional & 1U))) - { - // if halfway, round up if odd OR if last digit is 0 - ++number_.fractional; - } - - if (precision == 0U) - { - remainder = abs_number - (double)number_.integral; - if ((!(remainder < 0.5) || (remainder > 0.5)) && (number_.integral & 1)) + else if (ndigits + nzero > DIGITS_MAX_F) { - // exactly 0.5 and ODD, then round up - // 1.5 -> 2, but 2.5 -> 2 - ++number_.integral; + ndigits = DIGITS_MAX_F - nzero; } + p += nzero; } - return number_; -} - -#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS -struct scaling_factor -{ - double raw_factor; - bool multiply; // if true, need to multiply by raw_factor; otherwise need to divide by it -}; - -static double apply_scaling(double num, struct scaling_factor normalization) -{ - return normalization.multiply ? num * normalization.raw_factor : num / normalization.raw_factor; -} - -static double unapply_scaling(double normalized, struct scaling_factor normalization) -{ -#ifdef __GNUC__ -// accounting for a static analysis bug in GCC 6.x and earlier -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#endif - return normalization.multiply ? normalized / normalization.raw_factor : normalized * normalization.raw_factor; -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif -} - -static struct scaling_factor update_normalization(struct scaling_factor sf, double extra_multiplicative_factor) -{ - struct scaling_factor result; - if (sf.multiply) + else { - result.multiply = true; - result.raw_factor = sf.raw_factor * extra_multiplicative_factor; + // Allow space for sign, point exponent. + if (ndigits > DIGITS_MAX_E) + ndigits = DIGITS_MAX_E; } - else + + // Format digits one-by-one into the output string. + // One extra digit is required for rounding. + for (i = 0; i <= ndigits; i++) { - int factor_exp2 = get_exp2(get_bit_access(sf.raw_factor)); - int extra_factor_exp2 = get_exp2(get_bit_access(extra_multiplicative_factor)); - - // Divide the larger-exponent raw raw_factor by the smaller - if (PRINTF_ABS(factor_exp2) > PRINTF_ABS(extra_factor_exp2)) + // Ignore digits beyond the supported precision. + if (i >= FLOAT_DIGITS) { - result.multiply = false; - result.raw_factor = sf.raw_factor / extra_multiplicative_factor; + *p++ = '0'; } else { - result.multiply = true; - result.raw_factor = extra_multiplicative_factor / sf.raw_factor; + // number is normalised to a positive value between 0 and 9. + int n = number; + *p++ = n + '0'; + number = (number - n) * 10; } } - return result; -} - -static struct double_components get_normalized_components( - bool negative, - printf_size_t precision, - double non_normalized, - struct scaling_factor normalization, - int floored_exp10) -{ - struct double_components components; - components.is_negative = negative; - double scaled = apply_scaling(non_normalized, normalization); - - bool close_to_representation_extremum = ((-floored_exp10 + (int)precision) >= DBL_MAX_10_EXP - 1); - if (close_to_representation_extremum) - { - // We can't have a normalization factor which also accounts for the precision, i.e. moves - // some decimal digits into the mantissa, since it's unrepresentable, or nearly unrepresentable. - // So, we'll give up early on getting extra precision... - return get_components(negative ? -scaled : scaled, precision); - } - components.integral = (int_fast64_t)scaled; - double remainder = non_normalized - unapply_scaling((double)components.integral, normalization); - double prec_power_of_10 = powers_of_10[precision]; - struct scaling_factor account_for_precision = update_normalization(normalization, prec_power_of_10); - double scaled_remainder = apply_scaling(remainder, account_for_precision); - double rounding_threshold = 0.5; - - components.fractional = (int_fast64_t)scaled_remainder; // when precision == 0, the assigned value should be 0 - scaled_remainder -= (double)components.fractional; // when precision == 0, this will not change scaled_remainder - - components.fractional += (scaled_remainder >= rounding_threshold); - if (scaled_remainder == rounding_threshold) - { - // banker's rounding: Round towards the even number (making the mean error 0) - components.fractional &= ~((int_fast64_t)0x1); - } - // handle rollover, e.g. the case of 0.99 with precision 1 becoming (0,100), - // and must then be corrected into (1, 0). - // Note: for precision = 0, this will "translate" the rounding effect from - // the fractional part to the integral part where it should actually be - // felt (as prec_power_of_10 is 1) - if ((double)components.fractional >= prec_power_of_10) + // Store a pointer to the last (rounding) digit. + pend = --p; + // Round the result directly in the string buffer. + // Only use the calculated digits, not trailing 0's. + if (i > FLOAT_DIGITS) { - components.fractional = 0; - ++components.integral; + p -= (i - FLOAT_DIGITS); } - return components; -} -#endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS - -static void print_broken_up_decimal( - struct double_components number_, - output_gadget_t *output, - printf_size_t precision, - printf_size_t width, - printf_flags_t flags, - char *buf, - printf_size_t len) -{ - if (precision != 0U) + if (!(fflags & FF_NRND) && *p >= '5') { - // do fractional part, as an unsigned number - - printf_size_t count = precision; - - // %g/%G mandates we skip the trailing 0 digits... - if ((flags & FLAGS_ADAPT_EXP) && !(flags & FLAGS_HASH) && (number_.fractional > 0)) + for (;;) { - while (true) + if (i == 0) { - int_fast64_t digit = number_.fractional % 10U; - if (digit != 0) + // The rounding has rippled all the way through to + // the first digit. i.e. 9.999..9 -> 10.0 + // Just replace the first 0 with a 1 and shift the DP. + *p = '1'; + ++decpt; + // This increases the displayed digits for 'f' only. + if ((fflags & (FF_FCVT|FF_GCVT)) == FF_FCVT) { - break; + ++ndigits; + ++pend; } - --count; - number_.fractional /= 10U; + break; + } + // Previous digit was a rollover + *p-- = '0'; + // Increment next digit and break out unless there is a rollover. + if (*p != '9') + { + (*p)++; + break; } - // ... and even the decimal point if there are no - // non-zero fractional part digits (see below) } + } - if (number_.fractional > 0 || !(flags & FLAGS_ADAPT_EXP) || (flags & FLAGS_HASH)) + // Insert the decimal point + if (fflags & FF_FCVT) + { + flt_width_t num; + num = (decpt > 1) ? decpt : 1; + p = buf + 1; + for (i = 0; i < num; i++) { - while (len < PRINTF_DECIMAL_BUFFER_SIZE) - { - --count; - buf[len++] = (char)('0' + number_.fractional % 10U); - if (!(number_.fractional /= 10U)) - { - break; - } - } - // add extra 0s - while ((len < PRINTF_DECIMAL_BUFFER_SIZE) && (count > 0U)) - { - buf[len++] = '0'; - --count; - } - if (len < PRINTF_DECIMAL_BUFFER_SIZE) + *p = *(p+1); + ++p; + } + if (p + 1 < pend || (flags & FL_SPECIAL)) + { + // There are digits after the DP or DP is forced. + *p = '.'; + // Trim trailing 0's in 'g' mode. + if ((fflags & FF_GCVT) && !(flags & FL_SPECIAL)) { - buf[len++] = '.'; + pend = trim_zeros(pend - 1); } } - } - else - { - if ((flags & FLAGS_HASH) && (len < PRINTF_DECIMAL_BUFFER_SIZE)) + else { - buf[len++] = '.'; + // DP at end is not required. + --pend; } + // Leave p pointing to start of digit string. + p = buf + 1; } - - // Write the integer part of the number (it comes after the fractional - // since the character order is reversed) - while (len < PRINTF_DECIMAL_BUFFER_SIZE) + else { - buf[len++] = (char)('0' + (number_.integral % 10)); - if (!(number_.integral /= 10)) + // Leave p pointing to start of digit string. + if (ndigits > 1 || (flags & FL_SPECIAL)) { - break; + // Decimal point is always after first digit. + // Shift digit and insert point. + buf[1] = buf[2]; + buf[2] = '.'; + p = buf + 1; } - } - - // pad leading zeros - if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) - { - if (width && (number_.is_negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) + else { - width--; + // No decimal point needed. + p = buf + 2; } - while ((len < width) && (len < PRINTF_DECIMAL_BUFFER_SIZE)) + // Trim trailing 0's in 'g' mode. + if ((fflags & FF_GCVT) && !(flags & FL_SPECIAL)) { - buf[len++] = '0'; + pend = trim_zeros(pend - 1); } - } - - if (len < PRINTF_DECIMAL_BUFFER_SIZE) - { - if (number_.is_negative) + // Add exponent + *pend++ = (fflags & FF_UCASE) ? 'E' : 'e'; + if (--decpt < 0) { - buf[len++] = '-'; + *pend++ = '-'; + decpt = -decpt; } - else if (flags & FLAGS_PLUS) + else { - buf[len++] = '+'; // ignore the space if the '+' exists + *pend++ = '+'; } - else if (flags & FLAGS_SPACE) +#ifndef EXP_3_DIGIT + // Optional 3rd digit of exponent + if (decpt > 99) +#endif { - buf[len++] = ' '; + *pend++ = decpt / 100 + '0'; + decpt %= 100; } + // Always print at least 2 digits of exponent. + *pend++ = decpt / 10 + '0'; + *pend++ = decpt % 10 + '0'; } + // Add the sign prefix. + if (flags & FL_NEG) *--p = '-'; +#if FEATURE(USE_PLUS_SIGN) + else if (flags & FL_PLUS) *--p = '+'; +#endif +#if FEATURE(USE_SPACE_SIGN) + else if (flags & FL_SPACE) *--p = ' '; +#endif - out_rev_(output, buf, len, width, flags); -} - -// internal ftoa for fixed decimal floating point -static void print_decimal_number( - output_gadget_t *output, - double number, - printf_size_t precision, - printf_size_t width, - printf_flags_t flags, - char *buf, - printf_size_t len) -{ - struct double_components value_ = get_components(number, precision); - print_broken_up_decimal(value_, output, precision, width, flags, buf, len); -} - -#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS - -// A floor function - but one which only works for numbers whose -// floor value is representable by an int. -static int bastardized_floor(double x) -{ - if (x >= 0) + *pend = '\0'; // Add null terminator + +#if FEATURE(USE_ZERO_PAD) + if (flags & FL_ZERO_PAD) { - return (int)x; + /* Implement leading zero padding by shifting the formatted buffer. + * This is not the most efficient but at any other point it is hard + * to know what the exact buffer length will be. The processing time + * pales into insignificance next to the floating point operations. + */ + flt_width_t fwidth = pend - p; + if (width > BUFMAX) + width = BUFMAX; + if (fwidth < width) + { + // Set pointer to location after the new end point. + // It will be predecremented. + char *pnew = pend + width - fwidth + 1; + ++pend; + // Do not shift the sign/space if used. + if (flags & (FL_NEG | FL_PLUS | FL_SPACE)) + ++p; + // p now points to the last character to move. + // Shift the buffer rightwards + do *--pnew = *--pend; + while (pend != p); + // Fill the remainder with 0s. + do *--pnew = '0'; + while (pnew != p); + // Restore the former value of p. + if (flags & (FL_NEG | FL_PLUS | FL_SPACE)) + --p; + } } - int n = (int)x; - return (((double)n) == x) ? n : n - 1; -} - -// Computes the base-10 logarithm of the input number - which must be an actual -// positive number (not infinity or NaN, nor a sub-normal) -static double log10_of_positive(double positive_number) -{ - // The implementation follows David Gay (https://www.ampl.com/netlib/fp/dtoa.c). - // - // Since log_10 ( M * 2^x ) = log_10(M) + x , we can separate the components of - // our input number, and need only solve log_10(M) for M between 1 and 2 (as - // the base-2 mantissa is always 1-point-something). In that limited range, a - // Taylor series expansion of log10(x) should serve us well enough; and we'll - // take the mid-point, 1.5, as the point of expansion. +#endif - double_with_bit_access dwba = get_bit_access(positive_number); - // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) - int exp2 = get_exp2(dwba); - // drop the exponent, so dwba.F comes into the range [1,2) - dwba.U = (dwba.U & (((double_uint_t)(1) << DOUBLE_STORED_MANTISSA_BITS) - 1U)) | - ((double_uint_t)DOUBLE_BASE_EXPONENT << DOUBLE_STORED_MANTISSA_BITS); - double z = (dwba.F - 1.5); - return ( - // Taylor expansion around 1.5: - 0.1760912590556812420 // Expansion term 0: ln(1.5) / ln(10) - + z * 0.2895296546021678851 // Expansion term 1: (M - 1.5) * 2/3 / ln(10) -#if PRINTF_LOG10_TAYLOR_TERMS > 2 - - z * z * 0.0965098848673892950 // Expansion term 2: (M - 1.5)^2 * 2/9 / ln(10) -#if PRINTF_LOG10_TAYLOR_TERMS > 3 - + z * z * z * 0.0428932821632841311 // Expansion term 2: (M - 1.5)^3 * 8/81 / ln(10) -#endif -#endif - // exact log_2 of the exponent x, with logarithm base change - + exp2 * 0.30102999566398119521 // = exp2 * log_10(2) = exp2 * ln(2)/ln(10) - ); + return p; // Start of string } - -static double pow10_of_int(int floored_exp10) +#endif // End of floating point section + +/***************************************************************************** +Integer, character and string +******************************************************************************/ + +#if FEATURE(USE_SPACE_PAD) +/* --------------------------------------------------------------------------- +Function: p_len() +Helper function to find length of string. +This offers a small space saving over strlen and allows for reading strings +from flash where the micro uses different semantics to access program memory. +This is used on the AVR processor. +--------------------------------------------------------------------------- */ +#if FEATURE(USE_FSTRING) +static width_t p_len(char *p, unsigned char flags) +#else +static width_t p_len(char *p) +#endif { - // A crude hack for avoiding undesired behavior with barely-normal or slightly-subnormal values. - if (floored_exp10 == DOUBLE_MAX_SUBNORMAL_EXPONENT_OF_10) + width_t len = 0; +#if FEATURE(USE_FSTRING) + if (flags & FL_FSTR) { - return DOUBLE_MAX_SUBNORMAL_POWER_OF_10; + while (GET_FORMAT(p)) + { + ++p; + ++len; + } } - // Compute 10^(floored_exp10) but (try to) make sure that doesn't overflow - double_with_bit_access dwba; - int exp2 = bastardized_floor(floored_exp10 * 3.321928094887362 + 0.5); - const double z = floored_exp10 * 2.302585092994046 - exp2 * 0.6931471805599453; - const double z2 = z * z; - dwba.U = ((double_uint_t)(exp2) + DOUBLE_BASE_EXPONENT) << DOUBLE_STORED_MANTISSA_BITS; - // compute exp(z) using continued fractions, - // see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex - dwba.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); - return dwba.F; -} - -static void print_exponential_number( - output_gadget_t *output, - double number, - printf_size_t precision, - printf_size_t width, - printf_flags_t flags, - char *buf, - printf_size_t len) -{ - const bool negative = get_sign_bit(number); - // This number will decrease gradually (by factors of 10) as we "extract" the exponent out of it - double abs_number = negative ? -number : number; - - int floored_exp10; - bool abs_exp10_covered_by_powers_table; - struct scaling_factor normalization; - - // Determine the decimal exponent - if (abs_number == 0.0) + else +#endif { - // TODO: This is a special-case for 0.0 (and -0.0); but proper handling is required for denormals more - // generally. - floored_exp10 = 0; // ... and no need to set a normalization factor or check the powers table - } - else - { - double exp10 = log10_of_positive(abs_number); - floored_exp10 = bastardized_floor(exp10); - double p10 = pow10_of_int(floored_exp10); - // correct for rounding errors - if (abs_number < p10) - { - floored_exp10--; - p10 /= 10; - } - abs_exp10_covered_by_powers_table = PRINTF_ABS(floored_exp10) < PRINTF_MAX_PRECOMPUTED_POWER_OF_10; - normalization.raw_factor = abs_exp10_covered_by_powers_table ? powers_of_10[PRINTF_ABS(floored_exp10)] : p10; - } - - // We now begin accounting for the widths of the two parts of our printed field: - // the decimal part after decimal exponent extraction, and the base-10 exponent part. - // For both of these, the value of 0 has a special meaning, but not the same one: - // a 0 exponent-part width means "don't print the exponent"; a 0 decimal-part width - // means "use as many characters as necessary". - - bool fall_back_to_decimal_only_mode = false; - if (flags & FLAGS_ADAPT_EXP) - { - int required_significant_digits = (precision == 0) ? 1 : (int)precision; - // Should we want to fall-back to "%f" mode, and only print the decimal part? - fall_back_to_decimal_only_mode = (floored_exp10 >= -4 && floored_exp10 < required_significant_digits); - // Now, let's adjust the precision - // This also decided how we adjust the precision value - as in "%g" mode, - // "precision" is the number of _significant digits_, and this is when we "translate" - // the precision value to an actual number of decimal digits. - int precision_ = fall_back_to_decimal_only_mode - ? (int)precision - 1 - floored_exp10 - : (int)precision - 1; // the presence of the exponent ensures only one significant digit - // comes before the decimal point - precision = (precision_ > 0 ? (unsigned)precision_ : 0U); - flags |= FLAGS_PRECISION; // make sure print_broken_up_decimal respects our choice above - } - - normalization.multiply = (floored_exp10 < 0 && abs_exp10_covered_by_powers_table); - bool should_skip_normalization = (fall_back_to_decimal_only_mode || floored_exp10 == 0); - struct double_components decimal_part_components = - should_skip_normalization - ? get_components(negative ? -abs_number : abs_number, precision) - : get_normalized_components(negative, precision, abs_number, normalization, floored_exp10); - - // Account for roll-over, e.g. rounding from 9.99 to 100.0 - which effects - // the exponent and may require additional tweaking of the parts - if (fall_back_to_decimal_only_mode) - { - if ((flags & FLAGS_ADAPT_EXP) && floored_exp10 >= -1 && - decimal_part_components.integral == powers_of_10[floored_exp10 + 1]) - { - floored_exp10++; // Not strictly necessary, since floored_exp10 is no longer really used - precision--; - // ... and it should already be the case that decimal_part_components.fractional == 0 - } - // TODO: What about rollover strictly within the fractional part? - } - else - { - if (decimal_part_components.integral >= 10) - { - floored_exp10++; - decimal_part_components.integral = 1; - decimal_part_components.fractional = 0; - } - } - - // the floored_exp10 format is "E%+03d" and largest possible floored_exp10 value for a 64-bit double - // is "307" (for 2^1023), so we set aside 4-5 characters overall - printf_size_t exp10_part_width = fall_back_to_decimal_only_mode ? 0U : (PRINTF_ABS(floored_exp10) < 100) ? 4U : 5U; - - printf_size_t decimal_part_width = - ((flags & FLAGS_LEFT) && exp10_part_width) - ? - // We're padding on the right, so the width constraint is the exponent part's - // problem, not the decimal part's, so we'll use as many characters as we need: - 0U - : - // We're padding on the left; so the width constraint is the decimal part's - // problem. Well, can both the decimal part and the exponent part fit within our overall width? - ((width > exp10_part_width) ? - // Yes, so we limit our decimal part's width. - // (Note this is trivially valid even if we've fallen back to "%f" mode) - width - exp10_part_width - : - // No; we just give up on any restriction on the decimal part and use as many - // characters as we need - 0U); - - const printf_size_t printed_exponential_start_pos = output->pos; - print_broken_up_decimal(decimal_part_components, output, precision, decimal_part_width, flags, buf, len); - - if (!fall_back_to_decimal_only_mode) - { - putchar_via_gadget(output, (flags & FLAGS_UPPERCASE) ? 'E' : 'e'); - print_integer( - output, - ABS_FOR_PRINTING(floored_exp10), - floored_exp10 < 0, - 10, - 0, - exp10_part_width - 1, - FLAGS_ZEROPAD | FLAGS_PLUS); - if (flags & FLAGS_LEFT) - { - // We need to right-pad with spaces to meet the width requirement - while (output->pos - printed_exponential_start_pos < width) - { - putchar_via_gadget(output, ' '); - } - } + while (*p++) ++len; } + return len; } -#endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS - -static void print_floating_point( - output_gadget_t *output, - double value, - printf_size_t precision, - printf_size_t width, - printf_flags_t flags, - bool prefer_exponential) -{ - char buf[PRINTF_DECIMAL_BUFFER_SIZE]; - printf_size_t len = 0U; - - // test for special values - if (value != value) - { - out_rev_(output, "nan", 3, width, flags); - return; - } - if (value < -DBL_MAX) - { - out_rev_(output, "fni-", 4, width, flags); - return; - } - if (value > DBL_MAX) - { - out_rev_(output, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags); - return; - } - - if (!prefer_exponential && - ((value > PRINTF_FLOAT_NOTATION_THRESHOLD) || (value < -PRINTF_FLOAT_NOTATION_THRESHOLD))) - { - // The required behavior of standard printf is to print _every_ integral-part digit -- which could mean - // printing hundreds of characters, overflowing any fixed internal buffer and necessitating a more complicated - // implementation. -#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS - print_exponential_number(output, value, precision, width, flags, buf, len); #endif - return; - } - - // set default precision, if not set explicitly - if (!(flags & FLAGS_PRECISION)) - { - precision = PRINTF_DEFAULT_FLOAT_PRECISION; - } - - // limit precision so that our integer holding the fractional part does not overflow - while ((len < PRINTF_DECIMAL_BUFFER_SIZE) && (precision > PRINTF_MAX_SUPPORTED_PRECISION)) - { - buf[len++] = '0'; // This respects the precision in terms of result length only - precision--; - } -#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS - if (prefer_exponential) - print_exponential_number(output, value, precision, width, flags, buf, len); - else +#ifdef __GNUC__ +#pragma GCC diagnostic push +// -Wimplicit-fallthrough option was added in GCC 7 +#if (__GNUC__ >= 7) +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#endif #endif - print_decimal_number(output, value, precision, width, flags, buf, len); -} - -#endif // (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) - -// Advances the format pointer past the flags, and returns the parsed flags -// due to the characters passed -static printf_flags_t parse_flags(const char **format) -{ - printf_flags_t flags = 0U; - do - { - switch (**format) - { - case '0': - flags |= FLAGS_ZEROPAD; - (*format)++; - break; - case '-': - flags |= FLAGS_LEFT; - (*format)++; - break; - case '+': - flags |= FLAGS_PLUS; - (*format)++; - break; - case ' ': - flags |= FLAGS_SPACE; - (*format)++; - break; - case '#': - flags |= FLAGS_HASH; - (*format)++; - break; - default: - return flags; - } - } while (true); -} -static inline void format_string_loop(output_gadget_t *output, const char *format, va_list args) +/* --------------------------------------------------------------------------- +Function: doprnt() +This is the main worker function which does all the formatting. +The output function must always be provided. +Unless BASIC_PRINTF is defined it also needs the context variable, +which tells the output function where to write. +--------------------------------------------------------------------------- */ +#ifdef BASIC_PRINTF_ONLY +static printf_t doprnt(void (*func)(char c), const char *fmt, va_list ap) +#else +static printf_t doprnt(void *context, void (*func)(char c, void *context), size_t n, const char *fmt, va_list ap) +#endif { -#if PRINTF_CHECK_FOR_NUL_IN_FORMAT_SPECIFIER -#define ADVANCE_IN_FORMAT_STRING(cptr_) \ - do \ - { \ - (cptr_)++; \ - if (!*(cptr_)) \ - return; \ - } while (0) +#if FEATURE(USE_LONG_LONG) + unsigned long long uvalue; +#elif FEATURE(USE_LONG) + unsigned long uvalue; #else -#define ADVANCE_IN_FORMAT_STRING(cptr_) (cptr_)++ + unsigned uvalue; +#endif +#if !HEX_CONVERT_ONLY + unsigned base; +#endif +#if FEATURE(USE_SPACE_PAD) || FEATURE(USE_ZERO_PAD) || FEATURE(USE_FLOAT) + width_t width; + width_t fwidth; +#endif +#if FEATURE(USE_PRECISION) || FEATURE(USE_FLOAT) + width_t precision; +#else + #define precision -1 +#endif + char convert, c; + char *p; + char buffer[BUFMAX+1]; +#ifdef PRINTF_T + printf_t count = 0; +#endif + unsigned char flags; +#if FEATURE(USE_FLOAT) || FEATURE(USE_LONG_LONG) + unsigned char fflags; +#endif +#if FEATURE(USE_FLOAT) + double fvalue; #endif - while (*format) + buffer[BUFMAX] = '\0'; + + for (;;) { - if (*format != '%') + convert = GET_FORMAT(fmt); + if (convert == 0) break; + if (convert == '%') { - // A regular content character - putchar_via_gadget(output, *format); - format++; - continue; - } - // We're parsing a format specifier: %[flags][width][.precision][length] - ADVANCE_IN_FORMAT_STRING(format); - - printf_flags_t flags = parse_flags(&format); + p = buffer + BUFMAX; +#if FEATURE(USE_PRECISION) || FEATURE(USE_FLOAT) + precision = -1; +#endif +#if FEATURE(USE_SPACE_PAD) || FEATURE(USE_ZERO_PAD) || FEATURE(USE_FLOAT) + width = 0; +#endif +#if FEATURE(USE_FLOAT) || FEATURE(USE_LONG_LONG) + fflags = 0; +#endif + flags = 0; - // evaluate width field - printf_size_t width = 0U; - if (is_digit_(*format)) - { - width = (printf_size_t)atou_(&format); - } - else if (*format == '*') - { - const int w = va_arg(args, int); - if (w < 0) + // Extract flag chars + for (;;) { - flags |= FLAGS_LEFT; // reverse padding - width = (printf_size_t)-w; - } - else - { - width = (printf_size_t)w; + convert = GET_FORMAT(++fmt); +#if FEATURE(USE_ZERO_PAD) + if (convert == '0') + { + flags |= FL_ZERO_PAD; + } + else +#endif +#if FEATURE(USE_PLUS_SIGN) + if (convert == '+') + { + flags |= FL_PLUS; + } + else +#endif +#if FEATURE(USE_LEFT_JUST) + if (convert == '-') + { + flags |= FL_LEFT_JUST; + } + else +#endif +#if FEATURE(USE_SPACE_SIGN) + if (convert == ' ') + { + flags |= FL_SPACE; + } + else +#endif +#if FEATURE(USE_SPECIAL) + if (convert == '#') + { + flags |= FL_SPECIAL; + } + else +#endif + break; } - ADVANCE_IN_FORMAT_STRING(format); - } - - // evaluate precision field - printf_size_t precision = 0U; - if (*format == '.') - { - flags |= FLAGS_PRECISION; - ADVANCE_IN_FORMAT_STRING(format); - if (is_digit_(*format)) +#if FEATURE(USE_SPACE_PAD) || FEATURE(USE_ZERO_PAD) + // Extract width + #if FEATURE(USE_INDIRECT) + if (convert == '*') { - precision = (printf_size_t)atou_(&format); + width = va_arg(ap, int); + convert = GET_FORMAT(++fmt); } - else if (*format == '*') + else + #endif + // cppcheck-suppress knownConditionTrueFalse // False positive + while (convert >= '0' && convert <= '9') { - const int precision_ = va_arg(args, int); - precision = precision_ > 0 ? (printf_size_t)precision_ : 0U; - ADVANCE_IN_FORMAT_STRING(format); + width = width * 10 + convert - '0'; + convert = GET_FORMAT(++fmt); } - } - - // evaluate length field - switch (*format) - { -#ifdef PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS - case 'I': +#endif +#if FEATURE(USE_PRECISION) + // Extract precision + if (convert == '.') { - ADVANCE_IN_FORMAT_STRING(format); - // Greedily parse for size in bits: 8, 16, 32 or 64 - switch (*format) + precision = 0; + convert = GET_FORMAT(++fmt); + #if FEATURE(USE_INDIRECT) + if (convert == '*') { - case '8': - flags |= FLAGS_INT8; - ADVANCE_IN_FORMAT_STRING(format); - break; - case '1': - ADVANCE_IN_FORMAT_STRING(format); - if (*format == '6') - { - format++; - flags |= FLAGS_INT16; - } - break; - case '3': - ADVANCE_IN_FORMAT_STRING(format); - if (*format == '2') - { - ADVANCE_IN_FORMAT_STRING(format); - flags |= FLAGS_INT32; - } - break; - case '6': - ADVANCE_IN_FORMAT_STRING(format); - if (*format == '4') - { - ADVANCE_IN_FORMAT_STRING(format); - flags |= FLAGS_INT64; - } - break; - default: - break; + precision = va_arg(ap, int); + convert = GET_FORMAT(++fmt); } - break; - } -#endif - case 'l': - flags |= FLAGS_LONG; - ADVANCE_IN_FORMAT_STRING(format); - if (*format == 'l') + else + #endif + while (convert >= '0' && convert <= '9') { - flags |= FLAGS_LONG_LONG; - ADVANCE_IN_FORMAT_STRING(format); + precision = precision * 10 + convert - '0'; + convert = GET_FORMAT(++fmt); } - break; - case 'h': - flags |= FLAGS_SHORT; - ADVANCE_IN_FORMAT_STRING(format); - if (*format == 'h') + } +#endif +#if FEATURE(USE_LONG) + // Extract length modifier + if (convert == 'l') + { + convert = GET_FORMAT(++fmt); + #if FEATURE(USE_LONG_LONG) + if (convert == 'l') { - flags |= FLAGS_CHAR; - ADVANCE_IN_FORMAT_STRING(format); + fflags |= FF_XLONG; + convert = GET_FORMAT(++fmt); } + else + #endif + flags |= FL_LONG; + } +#endif + switch (convert) + { +#if FEATURE(USE_CHAR) + case 'c': + #if FEATURE(USE_SPACE_PAD) + width = 0; + #endif + *--p = (char) va_arg(ap, int); break; - case 't': - flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); - ADVANCE_IN_FORMAT_STRING(format); - break; - case 'j': - flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); - ADVANCE_IN_FORMAT_STRING(format); - break; - case 'z': - flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); - ADVANCE_IN_FORMAT_STRING(format); - break; - default: - break; - } - - // evaluate specifier - switch (*format) - { +#endif +#if FEATURE(USE_SIGNED) case 'd': +#endif +#if FEATURE(USE_SIGNED_I) case 'i': +#endif +#if FEATURE(USE_SIGNED) || FEATURE(USE_SIGNED_I) + flags |= FL_NEG; // Flag possible negative value, to be determined later + base = 10; + goto number; +#endif +#if FEATURE(USE_UNSIGNED) case 'u': - case 'x': - case 'X': + base = 10; + goto number; +#endif +#if FEATURE(USE_OCTAL) case 'o': + base = 8; + goto number; +#endif +#if FEATURE(USE_BINARY) case 'b': - { - - if (*format == 'd' || *format == 'i') - { - flags |= FLAGS_SIGNED; - } - - numeric_base_t base; - if (*format == 'x' || *format == 'X') - { - base = BASE_HEX; - } - else if (*format == 'o') - { - base = BASE_OCTAL; - } - else if (*format == 'b') - { - base = BASE_BINARY; - } + base = 2; + goto number; +#endif +#if FEATURE(USE_HEX_LOWER) + case 'x': +#endif +#if FEATURE(USE_HEX_UPPER) + case 'X': +#endif +#if FEATURE(USE_HEX_LOWER) || FEATURE(USE_HEX_UPPER) + #if !HEX_CONVERT_ONLY + base = 16; + #endif +#endif +#if !HEX_CONVERT_ONLY + number: +#endif + /* Using separate va_arg() calls for signed and unsigned types is expensive. + Instead, values are read as unsigned, regardless of signed/unsigned type. + Signed values then need to be sign-extended + and this is fixed after the check for negative numbers. + */ +#if FEATURE(USE_LONG) + #if FEATURE(USE_LONG_LONG) + if (fflags & FF_XLONG) + uvalue = va_arg(ap, unsigned long long); else + #endif + if (flags & FL_LONG) + uvalue = va_arg(ap, unsigned long); + else +#endif + uvalue = va_arg(ap, unsigned int); +#if FEATURE(USE_SIGNED) || FEATURE(USE_SIGNED_I) + // FL_NEG was used temporarily to indicate signed type + if (flags & FL_NEG) { - base = BASE_DECIMAL; - flags &= ~FLAGS_HASH; // decimal integers have no alternative presentation - } - - if (*format == 'X') - { - flags |= FLAGS_UPPERCASE; - } - - format++; - // ignore '0' flag when precision is given - if (flags & FLAGS_PRECISION) - { - flags &= ~FLAGS_ZEROPAD; - } - - if (flags & FLAGS_SIGNED) - { - // A signed specifier: d, i or possibly I + bit size if enabled - - if (flags & FLAGS_LONG_LONG) + // Values may need to be sign extended if not the widest type. + #if FEATURE(USE_LONG) + #if FEATURE(USE_LONG_LONG) + if (!(fflags & FF_XLONG)) { -#if PRINTF_SUPPORT_LONG_LONG - const long long value = va_arg(args, long long); - print_integer(output, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags); -#endif + if (!(flags & FL_LONG)) + uvalue = (int) uvalue; + else + uvalue = (long) uvalue; } - else if (flags & FLAGS_LONG) + #else + if (!(flags & FL_LONG)) + uvalue = (int) uvalue; + #endif + #endif + // Check whether this is a negative value + #if FEATURE(USE_LONG) + #if FEATURE(USE_LONG_LONG) + if ((long long) uvalue < 0) + #else + if ((long) uvalue < 0) + #endif + #else + if ((int) uvalue < 0) + #endif { - const long value = va_arg(args, long); - print_integer(output, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags); + uvalue = -uvalue; // Yes, it's negative } else { - // We never try to interpret the argument as something potentially-smaller than int, - // due to integer promotion rules: Even if the user passed a short int, short unsigned - // etc. - these will come in after promotion, as int's (or unsigned for the case of - // short unsigned when it has the same size as int) - const int value = (flags & FLAGS_CHAR) ? (signed char)va_arg(args, int) - : (flags & FLAGS_SHORT) ? (short int)va_arg(args, int) - : va_arg(args, int); - print_integer(output, ABS_FOR_PRINTING(value), value < 0, base, precision, width, flags); + flags &= ~FL_NEG; // No, it's positive } } - else - { - // An unsigned specifier: u, x, X, o, b - - flags &= ~(FLAGS_PLUS | FLAGS_SPACE); - - if (flags & FLAGS_LONG_LONG) - { -#if PRINTF_SUPPORT_LONG_LONG - print_integer( - output, - (printf_unsigned_value_t)va_arg(args, unsigned long long), - false, - base, - precision, - width, - flags); #endif - } - else if (flags & FLAGS_LONG) - { - print_integer( - output, - (printf_unsigned_value_t)va_arg(args, unsigned long), - false, - base, - precision, - width, - flags); - } - else - { - const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(args, unsigned int) - : (flags & FLAGS_SHORT) - ? (unsigned short int)va_arg(args, unsigned int) - : va_arg(args, unsigned int); - print_integer(output, (printf_unsigned_value_t)value, false, base, precision, width, flags); - } +#if FEATURE(USE_PRECISION) + // Set default precision + if (precision == -1) precision = 1; +#endif + // Make sure options are valid. +#if HEX_CONVERT_ONLY + #if FEATURE(USE_PLUS_SIGN) || FEATURE(USE_SPACE_SIGN) + flags &= ~(FL_PLUS|FL_SPACE); + #endif +#else + if (base != 10) flags &= ~(FL_PLUS|FL_NEG|FL_SPACE); + #if FEATURE(USE_SPECIAL) + else flags &= ~FL_SPECIAL; + #endif +#endif + // Generate the number without any prefix yet. +#if FEATURE(USE_ZERO_PAD) + fwidth = width; + // Avoid formatting buffer overflow. + if (fwidth > BUFMAX) fwidth = BUFMAX; +#endif +#if FEATURE(USE_LONG_LONG) && FEATURE(USE_BINARY) + // 64-bit binary output is impractical for reading and requires a huge buffer. + // Restrict to 32 bits in binary mode. + if ((base == 2) && (fflags & FF_XLONG)) + { + uvalue = (unsigned long) uvalue; } - break; - } -#if PRINTF_SUPPORT_DECIMAL_SPECIFIERS - case 'f': - case 'F': - if (*format == 'F') - flags |= FLAGS_UPPERCASE; - print_floating_point(output, va_arg(args, double), precision, width, flags, PRINTF_PREFER_DECIMAL); - format++; - break; #endif -#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS - 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; - print_floating_point(output, va_arg(args, double), precision, width, flags, PRINTF_PREFER_EXPONENTIAL); - format++; - break; -#endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS - case 'c': - { - printf_size_t l = 1U; - // pre padding - if (!(flags & FLAGS_LEFT)) +#if FEATURE(USE_PRECISION) + while (uvalue || precision > 0) +#else + if (uvalue == 0) { - while (l++ < width) - { - putchar_via_gadget(output, ' '); - } + // Avoid printing 0 as ' ' + *--p = '0'; + #if FEATURE(USE_ZERO_PAD) + --fwidth; + #endif } - // char output - putchar_via_gadget(output, (char)va_arg(args, int)); - // post padding - if (flags & FLAGS_LEFT) + while (uvalue) +#endif { - while (l++ < width) +#if HEX_CONVERT_ONLY + c = (char) ((uvalue & 0x0f) + '0'); +#else + c = (char) ((uvalue % base) + '0'); +#endif +#if FEATURE(USE_HEX_LOWER) || FEATURE(USE_HEX_UPPER) + if (c > '9') { - putchar_via_gadget(output, ' '); + // Hex digits + #if FEATURE(USE_HEX_LOWER) && FEATURE(USE_HEX_UPPER) + if (convert == 'X') c += 'A' - '0' - 10; + else c += 'a' - '0' - 10; + #elif FEATURE(USE_HEX_UPPER) || FEATURE(USE_HEX_UPPER_L) + c += 'A' - '0' - 10; + #else + c += 'a' - '0' - 10; + #endif } +#endif + *--p = c; +#if HEX_CONVERT_ONLY + uvalue >>= 4; +#else + uvalue /= base; +#endif +#if FEATURE(USE_ZERO_PAD) + --fwidth; +#endif +#if FEATURE(USE_PRECISION) + --precision; +#endif } - format++; - break; - } - - case 's': - { - const char *p = va_arg(args, char *); - if (p == NULL) +#if FEATURE(USE_ZERO_PAD) + // Allocate space for the sign bit. + if (flags & (FL_PLUS|FL_NEG|FL_SPACE)) --fwidth; + #if FEATURE(USE_SPECIAL) + // Allocate space for special chars if required. + if (flags & FL_SPECIAL) { - out_rev_(output, ")llun(", 6, width, flags); + if (convert == 'o') fwidth -= 1; + else fwidth -= 2; } - else + #endif + // Add leading zero padding if required. + if ((flags & FL_ZERO_PAD) && !(flags & FL_LEFT_JUST)) { - printf_size_t l = strnlen_s_(p, precision ? precision : PRINTF_MAX_POSSIBLE_BUFFER_SIZE); - // pre padding - if (flags & FLAGS_PRECISION) - { - l = (l < precision ? l : precision); - } - if (!(flags & FLAGS_LEFT)) - { - while (l++ < width) - { - putchar_via_gadget(output, ' '); - } - } - // string output - while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision)) - { - putchar_via_gadget(output, *(p++)); - --precision; - } - // post padding - if (flags & FLAGS_LEFT) + while (fwidth > 0) { - while (l++ < width) - { - putchar_via_gadget(output, ' '); - } + *--p = '0'; + --fwidth; } } - format++; +#endif +#if FEATURE(USE_SPECIAL) + // Add special prefix if required. + if (flags & FL_SPECIAL) + { + if (convert != 'o') *--p = convert; + *--p = '0'; + } +#endif + // Add the sign prefix + if (flags & FL_NEG) *--p = '-'; +#if FEATURE(USE_PLUS_SIGN) + else if (flags & FL_PLUS) *--p = '+'; +#endif +#if FEATURE(USE_SPACE_SIGN) + else if (flags & FL_SPACE) *--p = ' '; +#endif +#if FEATURE(USE_PRECISION) + // Precision is not used to limit number output. + precision = -1; +#endif break; - } - - case 'p': - { - width = sizeof(void *) * 2U + 2; // 2 hex chars per byte + the "0x" prefix - flags |= FLAGS_ZEROPAD | FLAGS_POINTER; - uintptr_t value = (uintptr_t)va_arg(args, void *); - (value == (uintptr_t)NULL) - ? out_rev_(output, ")lin(", 5, width, flags) - : print_integer(output, (printf_unsigned_value_t)value, false, BASE_HEX, precision, width, flags); - format++; +#if FEATURE(USE_FLOAT) + case 'f': + fflags = FF_FCVT; + goto fp_number; + case 'E': + fflags = FF_UCASE; + case 'e': + fflags |= FF_ECVT; + goto fp_number; + case 'G': + fflags = FF_UCASE; + case 'g': + fflags |= FF_GCVT; + fp_number: + // Set default precision + if (precision == -1) precision = 6; + // Need one extra digit precision in E mode + if (fflags & FF_ECVT) ++precision; + fvalue = va_arg(ap, double); + p = format_float(fvalue, precision, width, flags, fflags, buffer); + // Precision is not used to limit number output. + precision = -1; break; - } - - case '%': - putchar_via_gadget(output, '%'); - format++; +#endif +#if FEATURE(USE_STRING) + #if FEATURE(USE_FSTRING) + case 'S': + flags |= FL_FSTR; + // fall through + #endif + case 's': + p = va_arg(ap, char *); break; +#endif + default: + *--p = convert; + break; + } - // Many people prefer to disable support for %n, as it lets the caller - // engineer a write to an arbitrary location, of a value the caller - // effectively controls - which could be a security concern in some cases. -#if PRINTF_SUPPORT_WRITEBACK_SPECIFIER - case 'n': +#if FEATURE(USE_SPACE_PAD) + // Check width of formatted text. + #if FEATURE(USE_FSTRING) + fwidth = p_len(p, flags); + #else + fwidth = p_len(p); + #endif + // Copy formatted text with leading or trailing space. + // A positive value for precision will limit the length of p used. + for (;;) { - if (flags & FLAGS_CHAR) - *(va_arg(args, char *)) = (char)output->pos; - else if (flags & FLAGS_SHORT) - *(va_arg(args, short *)) = (short)output->pos; - else if (flags & FLAGS_LONG) - *(va_arg(args, long *)) = (long)output->pos; -#if PRINTF_SUPPORT_LONG_LONG - else if (flags & FLAGS_LONG_LONG) - *(va_arg(args, long long *)) = (long long int)output->pos; -#endif // PRINTF_SUPPORT_LONG_LONG + #if FEATURE(USE_FSTRING) + if (flags & FL_FSTR) + c = GET_FORMAT(p); else - *(va_arg(args, int *)) = (int)output->pos; - format++; - break; - } -#endif // PRINTF_SUPPORT_WRITEBACK_SPECIFIER + #endif + c = *p; + if ((c == '\0' || precision == 0) && width <= 0) break; + if (((flags & FL_LEFT_JUST) || width <= fwidth) && c && precision != 0) ++p; + else c = ' '; + // for loop continues after #endif +#else + for (;;) + { + #if FEATURE(USE_FSTRING) + if (flags & FL_FSTR) + c = GET_FORMAT(p); + else + #endif + c = *p; + #if FEATURE(USE_PRECISION) + // A positive value for precision will limit the length of p used. + if (c == '\0' || precision == 0) break; + #else + if (c == '\0') break; + #endif + ++p; + // for loop continues after #endif +#endif + // for loop continues here from either of the USE_SPACE_PAD cases. +#ifdef BASIC_PRINTF_ONLY + func(c); // Basic output function. +#else + func(c, context); // Output function. +#endif +#ifdef PRINTF_T + ++count; - default: - putchar_via_gadget(output, *format); - format++; - break; +// [NF_CHANGE] + if (count >= n) + { + break; + } +// [END NF_CHANGE] + +#endif +#if FEATURE(USE_SPACE_PAD) + --width; +#endif +#if FEATURE(USE_PRECISION) + if (precision > 0) --precision; +#endif + } + } + else + { +#ifdef BASIC_PRINTF_ONLY + func(convert); // Basic output function. +#else + func(convert, context); // Output function. +#endif +#ifdef PRINTF_T + ++count; +#endif } + ++fmt; } -} - -// internal vsnprintf - used for implementing _all library functions -static int vsnprintf_impl(output_gadget_t *output, const char *format, va_list args) -{ - // Note: The library only calls vsnprintf_impl() with output->pos being 0. However, it is - // possible to call this function with a non-zero pos value for some "remedial printing". - format_string_loop(output, format, args); - // termination - append_termination_with_gadget(output); - - // return written chars without terminating \0 - return (int)output->pos; +#ifdef PRINTF_T + return count; +#endif } -/////////////////////////////////////////////////////////////////////////////// +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif -int vprintf_(const char *format, va_list arg) +/* --------------------------------------------------------------------------- +Function: putout() +This is the output function used for printf. +The context is normally required, but is not used at present. It could be +extended to include streams or to avoid output mixing in multi-threaded use. +If using BASIC_PRINTF, context is not supported. +--------------------------------------------------------------------------- */ +#ifdef BASIC_PRINTF_ONLY +static void putout(char c) { - output_gadget_t gadget = extern_putchar_gadget(); - return vsnprintf_impl(&gadget, format, arg); -} - -int vsnprintf_(char *s, size_t n, const char *format, va_list arg) +#else +static void putout(char c, void *context) { - output_gadget_t gadget = buffer_gadget(s, n); - return vsnprintf_impl(&gadget, format, arg); + (void) context; // Suppress compiler warning about unused argument. +#endif + PUTCHAR_FUNC(c); } -int vsprintf_(char *s, const char *format, va_list arg) +/* --------------------------------------------------------------------------- +Function: printf() +Replacement for library printf - writes to output (normally serial) +It uses the output function putout() to update the serial output. +If PRINTF_T is defined then the number of characters generated is returned. +--------------------------------------------------------------------------- */ +printf_t printf_(const char *fmt, ...) { - return vsnprintf_(s, PRINTF_MAX_POSSIBLE_BUFFER_SIZE, format, arg); -} + va_list ap; +#ifdef PRINTF_T + int Count; +#endif -int vfctprintf(void (*out)(char c, void *extra_arg), void *extra_arg, const char *format, va_list arg) -{ - output_gadget_t gadget = function_gadget(out, extra_arg); - return vsnprintf_impl(&gadget, format, arg); + va_start(ap, fmt); +#ifdef PRINTF_T + #ifdef BASIC_PRINTF_ONLY + Count = doprnt(putout, fmt, ap); + #else + Count = doprnt((void *)0, putout, BUFMAX, fmt, ap); + #endif +#else + #ifdef BASIC_PRINTF_ONLY + doprnt(putout, fmt, ap); + #else + doprnt((void *)0, putout, fmt, ap); + #endif +#endif + va_end(ap); + +#ifdef PRINTF_T + return Count; +#endif } -int printf_(const char *format, ...) +#ifndef BASIC_PRINTF_ONLY +/* --------------------------------------------------------------------------- +Function: putbuf() +This is the output function used for sprintf. +Here the context is a pointer to a pointer to the buffer. +Double indirection allows the function to increment the buffer pointer. +--------------------------------------------------------------------------- */ +static void putbuf(char c, void *context) { - va_list args; - va_start(args, format); - const int ret = vprintf_(format, args); - va_end(args); - return ret; + char *buf = *((char **) context); + *buf++ = c; + *((char **) context) = buf; } -int sprintf_(char *s, const char *format, ...) +/* --------------------------------------------------------------------------- +Function: sprintf() +Replacement for library sprintf - writes into buffer supplied. +Normally it uses the output function putout() to update the buffer. +sprintf is not supported when using BASIC_PRINTF +If PRINTF_T is defined then the number of characters generated is returned. +--------------------------------------------------------------------------- */ +printf_t sprintf_(char *buf, const char *fmt, ... ) { - va_list args; - va_start(args, format); - const int ret = vsprintf_(s, format, args); - va_end(args); - return ret; + va_list ap; +#ifdef PRINTF_T + int Count; +#endif + + va_start(ap, fmt); +#ifdef PRINTF_T + Count = doprnt(&buf, putbuf, BUFMAX, fmt, ap); +#else + doprnt(&buf, putbuf, fmt, ap); +#endif + va_end(ap); + // Append null terminator. + *buf = '\0'; + +#ifdef PRINTF_T + return Count; +#endif } +#endif -int snprintf_(char *s, size_t n, const char *format, ...) +// [NF_CHANGE] +printf_t snprintf_(char *buf, size_t n,const char *fmt, ... ) { - va_list args; - va_start(args, format); - const int ret = vsnprintf_(s, n, format, args); - va_end(args); - return ret; + va_list ap; + int Count; + + va_start(ap, fmt); + Count = doprnt(&buf, putbuf, n, fmt, ap); + va_end(ap); + // Append null terminator. + *buf = '\0'; + + return Count; } +// [END_NF_CHANGE] -int fctprintf(void (*out)(char c, void *extra_arg), void *extra_arg, const char *format, ...) +// [NF_CHANGE] +void putchar_(char character) { - va_list args; - va_start(args, format); - const int ret = vfctprintf(out, extra_arg, format, args); - va_end(args); - return ret; + (void)character; } +// [END_NF_CHANGE] + +// clang-format on diff --git a/src/CLR/Helpers/nanoprintf/nanoprintf.h b/src/CLR/Helpers/nanoprintf/nanoprintf.h index 2294dcceb4..e4ba735966 100644 --- a/src/CLR/Helpers/nanoprintf/nanoprintf.h +++ b/src/CLR/Helpers/nanoprintf/nanoprintf.h @@ -1,7 +1,6 @@ // // Copyright (c) .NET Foundation and Contributors -// Portions Copyright (c) 2014-2019 Marco Paland (info@paland.com). All rights reserved. -// Portions Copyright (c) 2021-2022 Eyal Rozenberg . All rights reserved. +// Portions Copyright (c) 2006 - 2021 Skirrid Systems. All rights reserved. // See LICENSE file in the project root for full license information. // @@ -9,188 +8,287 @@ #define NANOPRINTF_H #include -#include - -#define PRINTF_SUPPORT_DECIMAL_SPECIFIERS 1 -#define PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 1 -#define PRINTF_SUPPORT_WRITEBACK_SPECIFIER 1 -#define PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS 0 -#define PRINTF_SUPPORT_LONG_LONG 1 -#define PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL 15 -#define PRINTF_CHECK_FOR_NUL_IN_FORMAT_SPECIFIER 0 -#define PRINTF_ALIAS_STANDARD_FUNCTION_NAMES 1 -#define PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_SOFT 1 -#define PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_HARD 0 + +// clang-format off + +/************************************************************************* +Number of chars output + +Traditionally printf returns the number of chars output. If you are not +interested in that value you can leave PRINTF_T undefined. +On a small micro you can define the return type as unsigned char if you +are sure the total output width will never exceed 255, or unsigned short. +*************************************************************************/ + +#define PRINTF_T size_t + +/************************************************************************* +Memory access definitions + +Some micros such as the AVR can only support storing strings in flash +memory by wrapping the string in a macro. To make this transparent we can +define the printf function itself as a macro which performs the wrap and +calls a renamed version of printf with an _ suffix and no i. +*************************************************************************/ + +/* +Example for AVR micros using GCC toolchain from WinAVR or Atmel Studio + +#define sprintf(buf, format, args...) _sprntf(buf, PSTR(format), ## args) +#define printf(format, args...) _prntf(PSTR(format), ## args) + +extern printf_t _sprntf(char *, const char *, ...); +extern printf_t _prntf(const char *, ...); +*/ + +/************************************************************************* +End of customisations - Stop Editing! + +The remainder of this file contains the function declarations. +*************************************************************************/ + +// Create a type definition for the return value +#ifndef PRINTF_T +typedef void printf_t; +#else +typedef PRINTF_T printf_t; +#endif #ifdef __cplusplus -#include -#include extern "C" { -#else -#include -#include #endif -#ifdef __GNUC__ -#if ((__GNUC__ == 4 && __GNUC_MINOR__ >= 4) || __GNUC__ > 4) -#define ATTR_PRINTF(one_based_format_index, first_arg) \ - __attribute__((format(gnu_printf, (one_based_format_index), (first_arg)))) -#else -#define ATTR_PRINTF(one_based_format_index, first_arg) \ - __attribute__((format(printf, (one_based_format_index), (first_arg)))) -#endif -#define ATTR_VPRINTF(one_based_format_index) ATTR_PRINTF((one_based_format_index), 0) -#else -#define ATTR_PRINTF(one_based_format_index, first_arg) -#define ATTR_VPRINTF(one_based_format_index) -#endif +// Function declarations, unless macros have been defined above +printf_t printf_(const char *, ...); +printf_t sprintf_(char *, const char *, ...); +printf_t snprintf_(char *, size_t n, const char *, ...); -#ifndef PRINTF_ALIAS_STANDARD_FUNCTION_NAMES -#define PRINTF_ALIAS_STANDARD_FUNCTION_NAMES 0 +#ifdef __cplusplus +} #endif -#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_HARD -#define printf_ printf -#define sprintf_ sprintf -#define vsprintf_ vsprintf -#define snprintf_ snprintf -#define vsnprintf_ vsnprintf -#define vprintf_ vprintf -#endif + // from config -// If you want to include this implementation file directly rather than -// link against, this will let you control the functions' visibility, -// e.g. make them static so as not to clash with other objects also -// using them. -#ifndef PRINTF_VISIBILITY -#define PRINTF_VISIBILITY -#endif +/************************************************************************* +Basic printf only - /** - * Prints/send a single character to some opaque output entity - * - * @note This function is not implemented by the library, only declared; you must provide an - * implementation if you wish to use the @ref printf / @ref vprintf function (and possibly - * for linking against the library, if your toolchain does not support discarding unused functions) - * - * @note The output could be as simple as a wrapper for the `write()` system call on a Unix-like - * system, or even libc's @ref putchar , for replicating actual functionality of libc's @ref printf - * function; but on an embedded system it may involve interaction with a special output device, - * like a UART, etc. - * - * @note in libc's @ref putchar, the parameter type is an int; this was intended to support the - * representation of either a proper character or EOF in a variable - but this is really not - * meaningful to pass into @ref putchar and is discouraged today. See further discussion in: - * @link https://stackoverflow.com/q/17452847/1593077 - * - * @param c the single character to print - */ - PRINTF_VISIBILITY - void putchar_(char c); - - /** - * An implementation of the C standard's printf/vprintf - * - * @note you must implement a @ref putchar_ function for using this function - it invokes @ref putchar_ - * rather than directly performing any I/O (which insulates it from any dependence on the operating system - * and external libraries). - * - * @param format A string specifying the format of the output, with %-marked specifiers of how to interpret - * additional arguments. - * @param arg Additional arguments to the function, one for each %-specifier in @p format string - * @return The number of characters written into @p s, not counting the terminating null character - */ - ///@{ - PRINTF_VISIBILITY - int printf_(const char *format, ...) ATTR_PRINTF(1, 2); - PRINTF_VISIBILITY - int vprintf_(const char *format, va_list arg) ATTR_VPRINTF(1); - ///@} - - /** - * An implementation of the C standard's sprintf/vsprintf - * - * @note For security considerations (the potential for exceeding the buffer bounds), please consider using - * the size-constrained variant, @ref snprintf / @ref vsnprintf , instead. - * - * @param s An array in which to store the formatted string. It must be large enough to fit the formatted - * output! - * @param format A string specifying the format of the output, with %-marked specifiers of how to interpret - * additional arguments. - * @param arg Additional arguments to the function, one for each specifier in @p format - * @return The number of characters written into @p s, not counting the terminating null character - */ - ///@{ - PRINTF_VISIBILITY - int sprintf_(char *s, const char *format, ...) ATTR_PRINTF(2, 3); - PRINTF_VISIBILITY - int vsprintf_(char *s, const char *format, va_list arg) ATTR_VPRINTF(2); - ///@} - - /** - * An implementation of the C standard's snprintf/vsnprintf - * - * @param s An array in which to store the formatted string. It must be large enough to fit either the - * entire formatted output, or at least @p n characters. Alternatively, it can be NULL, in which case - * nothing will be printed, and only the number of characters which _could_ have been printed is - * tallied and returned. - * @param n The maximum number of characters to write to the array, including a terminating null character - * @param format A string specifying the format of the output, with %-marked specifiers of how to interpret - * additional arguments. - * @param arg Additional arguments to the function, one for each specifier in @p format - * @return The number of characters that COULD have been written into @p s, not counting the terminating - * null character. A value equal or larger than @p n indicates truncation. Only when the returned value - * is non-negative and less than @p n, the null-terminated string has been fully and successfully printed. - */ - ///@{ - PRINTF_VISIBILITY - int snprintf_(char *s, size_t count, const char *format, ...) ATTR_PRINTF(3, 4); - PRINTF_VISIBILITY - int vsnprintf_(char *s, size_t count, const char *format, va_list arg) ATTR_VPRINTF(3); - ///@} - - /** - * printf/vprintf with user-specified output function - * - * An alternative to @ref printf_, in which the output function is specified dynamically - * (rather than @ref putchar_ being used) - * - * @param out An output function which takes one character and a type-erased additional parameters - * @param extra_arg The type-erased argument to pass to the output function @p out with each call - * @param format A string specifying the format of the output, with %-marked specifiers of how to interpret - * additional arguments. - * @param arg Additional arguments to the function, one for each specifier in @p format - * @return The number of characters for which the output f unction was invoked, not counting the terminating null - * character - * - */ - PRINTF_VISIBILITY - int fctprintf(void (*out)(char c, void *extra_arg), void *extra_arg, const char *format, ...) ATTR_PRINTF(3, 4); - PRINTF_VISIBILITY - int vfctprintf(void (*out)(char c, void *extra_arg), void *extra_arg, const char *format, va_list arg) - ATTR_VPRINTF(3); +The code is designed to support a variety of printf-related functions. +If simple serial output is all you want then you can save some space by +defining BASIC_PRINTF_ONLY which allows the internal API to be simplified. +Note that sprintf will not be supported in this case. +*************************************************************************/ -#ifdef __cplusplus -} // extern "C" -#endif +// #define BASIC_PRINTF_ONLY -#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_HARD -#undef printf_ -#undef sprintf_ -#undef vsprintf_ -#undef snprintf_ -#undef vsnprintf_ -#undef vprintf_ -#else -#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_SOFT -#define printf printf_ -#define sprintf sprintf_ -#define vsprintf vsprintf_ -#define snprintf snprintf_ -#define vsnprintf vsnprintf_ -#define vprintf vprintf_ -#endif -#endif +/************************************************************************* +Memory access definitions + +Some micros such as the AVR can only support storing and accessing strings +in flash memory using special macros and functions. This section can be +used to specify those methods. You may also need to modify printf.h +to get the compiler to place the format strings in flash memory. + +The GET_FORMAT(ptr) macro is used to access a character in the printf +format string. By default this does a normal memory pointer access, but +you can configure it to access flash memory if needed. +*************************************************************************/ + +/* +Example for AVR micros using GCC toolchain from WinAVR or Atmel Studio + +#include +#include +#define GET_FORMAT(p) pgm_read_byte(p) +*/ + +/************************************************************************* +Output configuration + +By default printf will use the putchar function. If this is not defined +in your system you can set your own function here by defining +PUTCHAR_FUNC to be the name of that function. +*************************************************************************/ + +void putchar_(char character); +#define PUTCHAR_FUNC putchar_ + +/************************************************************************* +Compiler capability configuration + +Set some options that the C pre-processor will not tell us about. +*************************************************************************/ + +// Does the compiler support double precision or silently degrade to single? +//#define NO_DOUBLE_PRECISION + +// Does the compiler support isnan and isinf floating point functions? +// #define NO_ISNAN_ISINF + +/************************************************************************* +Formatted item width + +Since it is extremely unlikely that you will ever want to use a formatted +width for a single item of more than 127 chars (i.e. the expanded and +padded size of a single % expression), the width variables can normally +be restricted to 8 bits. On small micros this saves a lot of code and +variable space. On a 32-bit RISC it may increase code size due to type +conversions. Choose the variable type to suit your CPU. +Note that a signed type is required. +*************************************************************************/ + +typedef signed char width_t; + +/************************************************************************* +Feature configuration + +This section defines the individual feature flags. +These are combined as needed to produce the FEATURE_FLAGS macro. +*************************************************************************/ + +// Include floating point number support +#define USE_FLOAT (1 << 0) + +// Include support for long integers +#define USE_LONG (1 << 1) + +// Include support for octal formatting +#define USE_OCTAL (1 << 2) + +// Include support for %d decimal formatting +#define USE_SIGNED (1 << 3) + +// Include support for the %i synonym for %d +#define USE_SIGNED_I (1 << 4) + +// Include support for the %u unsigned decimal specifier +#define USE_UNSIGNED (1 << 5) + +// Include support for the %x hex specifier (lowercase output) +#define USE_HEX_LOWER (1 << 6) + +// Include support for the %X hex specifier (uppercase output) +#define USE_HEX_UPPER (1 << 7) + +// Force uppercase output with %x. +// Used in conjunction with USE_HEX_LOWER. +// Ignored if USE_HEX_UPPER is also set. +#define USE_HEX_UPPER_L (1 << 8) + +// Include support for %c single character +#define USE_CHAR (1 << 9) + +// Include support for %s string +#define USE_STRING (1 << 10) + +// Include support for %S string in flash memory +// Only needed for architectures which cannot access program memory using normal pointers. +// If you have not defined the GET_FORMAT() macro above then you don't need this option. +#define USE_FSTRING (1 << 11) + +// Include support for %b binary specifier +#define USE_BINARY (1 << 12) + +// Include precision support when floating point is not present. +// Precision is automatically enabled when floating point support is used. +#define USE_PRECISION (1UL << 16) + +// Allow use of leading zero padding e.g. "%03d" +#define USE_ZERO_PAD (1UL << 17) + +// Allow use of space padding e.g. "%3d" or "%12s" +#define USE_SPACE_PAD (1UL << 18) + +// Include indirect width/precision support e.g. "%*d" +#define USE_INDIRECT (1UL << 19) + +// Allow forcing a leading plus sign e.g. "%+3d" +#define USE_PLUS_SIGN (1UL << 20) + +// Allow forcing a leading space (instead of + or -) in front of zero e.g. "% 3d" +#define USE_SPACE_SIGN (1UL << 21) + +// Include support for the left-justify '-' flag. +#define USE_LEFT_JUST (1UL << 22) + +// Include support for the special '#' flag. +#define USE_SPECIAL (1UL << 23) + +// Use smaller but less efficient floating point normalisation. +// This is not recommended unless code space is critically low. +#define USE_SMALL_FLOAT (1UL << 24) + +// Include support for 64-bit integers e.g. "%lld" +#define USE_LONG_LONG (1UL << 25) + +/************************************************************************* +Pre-defined feature sets + +This section provides some commonly used combinations of features. +*************************************************************************/ + +// Lowercase hex integers only. This really is the bare minimum. +#define HEX_INT (USE_HEX_LOWER) + +// Decimal and lowercase hex only. +#define MINIMAL_INT (USE_SIGNED | USE_HEX_LOWER) + +// Signed and unsigned decimal, lower case hex, zero & space padding, plus char and string +#define BASIC_INT (USE_CHAR | USE_STRING | USE_SIGNED | USE_UNSIGNED | USE_HEX_LOWER | USE_ZERO_PAD | USE_SPACE_PAD) + +// All short integer features except octal, binary, %i, indirection and specials. +#define SHORT_INT \ + (USE_CHAR | USE_STRING | USE_SIGNED | USE_UNSIGNED | USE_HEX_LOWER | USE_HEX_UPPER | USE_PRECISION | \ + USE_ZERO_PAD | USE_SPACE_PAD | USE_PLUS_SIGN | USE_SPACE_SIGN | USE_LEFT_JUST) + +// As above, but also supports long integers. +#define LONG_INT (USE_LONG | SHORT_INT) + +// As above, but also supports long-long integers. +#define LONG_LONG_INT (USE_LONG_LONG | LONG_INT) + +// All possible integer features. +#define FULL_INT (USE_BINARY | USE_OCTAL | USE_SIGNED_I | USE_INDIRECT | USE_SPECIAL | LONG_LONG_INT) + +// All available features including floating point. +#define FULL_FLOAT (USE_FLOAT | FULL_INT) + +/************************************************************************* +Features included in your build of printf. Use only the features you need +to keep code size and execution time to a minimum. + +You can use the custom set, with anything you don't want commented out, +or you can use one of the pre-defined sets. + +Examples: + +#define FEATURE_FLAGS CUSTOM_SET +#define FEATURE_FLAGS SHORT_INT + +Features and pre-defined sets are set out in the following sections. +*************************************************************************/ + +// Custom feature set. Comment out features you don't want. +#define CUSTOM_SET \ + (0 | USE_FLOAT | USE_LONG | USE_BINARY | USE_OCTAL | USE_SIGNED | USE_SIGNED_I | USE_UNSIGNED | USE_HEX_LOWER | \ + USE_HEX_UPPER | USE_HEX_UPPER_L | USE_CHAR | USE_STRING | USE_FSTRING | USE_PRECISION | USE_ZERO_PAD | \ + USE_SPACE_PAD | USE_INDIRECT | USE_PLUS_SIGN | USE_SPACE_SIGN | USE_LEFT_JUST | USE_SPECIAL | USE_SMALL_FLOAT | \ + USE_LONG_LONG) + +#define FEATURE_FLAGS \ + (0 | USE_FLOAT | USE_LONG | USE_SIGNED | USE_UNSIGNED | USE_HEX_LOWER | \ + USE_HEX_UPPER | USE_HEX_UPPER_L | USE_CHAR | USE_STRING | USE_PRECISION | USE_ZERO_PAD | \ + USE_SPACE_PAD | USE_INDIRECT | USE_PLUS_SIGN | \ + USE_LONG_LONG) + +/************************************************************************* +End of customisations - Stop Editing! +*************************************************************************/ + +#define printf printf_ +#define sprintf sprintf_ +#define snprintf snprintf_ #endif // NANOPRINTF_H + +// clang-format on