Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ option(SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS "Support the I + bit size integer
option(SUPPORT_WRITEBACK_SPECIFIER "Support the length write-back specifier (%n)" ON)
option(SUPPORT_LONG_LONG "Support long long integral types (allows for the ll length modifier and affects %p)" ON)
option(USE_DOUBLE_INTERNALLY "Use the C `double` type - typically 64-bit in size - for internal floating-point arithmetic " ON)
option(AVOID_INT64_TO_FLOAT_CONVERSION "Avoid int64_t-to-float conversions when using single-precision internal floating-point arithmetic" OFF)
option(CHECK_FOR_NUL_IN_FORMAT_SPECIFIER "Be defensive in the undefined-behavior case of a format specifier not ending before the string ends" ON)

set(ALIASING_MODES NONE HARD SOFT)
Expand All @@ -41,6 +42,7 @@ foreach(opt
SUPPORT_WRITEBACK_SPECIFIER
SUPPORT_LONG_LONG
USE_DOUBLE_INTERNALLY
AVOID_INT64_TO_FLOAT_CONVERSION
ALIAS_STANDARD_FUNCTION_NAMES_SOFT
ALIAS_STANDARD_FUNCTION_NAMES_HARD
CHECK_FOR_NUL_IN_FORMAT_SPECIFIER
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ Options used both in CMake and in the library source code via a preprocessor def
| SUPPORT_WRITEBACK_SPECIFIER | YES | Support the length write-back specifier (%n) |
| SUPPORT_LONG_LONG | YES | Support long long integral types (allows for the ll length modifier and affects %p) |
| USE_DOUBLE_INTERNALLY | YES | Use the `double` for internal floating-point calculations (rather than using the single-precision `float` type |
| AVOID_INT64_TO_FLOAT_CONVERSION | NO | When using single-precision internal floating-point calculations, avoid int64_t-to-float conversions by using smaller fixed-point chunks with lower maximum precision |

In the source files themselves, these options become preprocessor definitions with a `PRINTF_` prefix; the boolean ones have value `1` OR `0` for YES or NO respectively; and the aliasing option translates into one of three definitions: `ALIAS_STANDARD_FUNCTION_NAMES_NONE`, `ALIAS_STANDARD_FUNCTION_NAMES_SOFT` or `ALIAS_STANDARD_FUNCTION_NAMES_HARD`.

Expand Down Expand Up @@ -294,4 +295,3 @@ I try to attend to issues and PRs promptly.
## License

This library is published under the terms of the [MIT license](http://www.opensource.org/licenses/MIT).

2 changes: 1 addition & 1 deletion printf_config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#define PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS @PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS@
#define PRINTF_SUPPORT_LONG_LONG @PRINTF_SUPPORT_LONG_LONG@
#define PRINTF_USE_DOUBLE_INTERNALLY @PRINTF_USE_DOUBLE_INTERNALLY@
#define PRINTF_AVOID_INT64_TO_FLOAT_CONVERSION @PRINTF_AVOID_INT64_TO_FLOAT_CONVERSION@

#ifdef PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_SOFT
#if (PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_SOFT != @PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_SOFT@)
Expand All @@ -32,4 +33,3 @@
#define PRINTF_CHECK_FOR_NUL_IN_FORMAT_SPECIFIER @PRINTF_CHECK_FOR_NUL_IN_FORMAT_SPECIFIER@

#endif /* PRINTF_CONFIG_H_ */

55 changes: 43 additions & 12 deletions src/printf/printf.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@
#define PRINTF_USE_DOUBLE_INTERNALLY 1
#endif

/*
* Use 32-bit integer chunks for single-precision floating point formatting. This avoids
* int64_t-to-float conversions on targets requiring large and slow software float routines, at the
* cost of lower maximum precision.
*/
#ifndef PRINTF_AVOID_INT64_TO_FLOAT_CONVERSION
#define PRINTF_AVOID_INT64_TO_FLOAT_CONVERSION 0
#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 -
Expand Down Expand Up @@ -280,15 +289,37 @@ typedef unsigned int printf_size_t;
* representation and manipulation of values as long doubles; the options
* are either single-precision `float` or double-precision `double`.
*/
#define NUM_DECIMAL_DIGITS_IN_INT64_T 18
#define NUM_DECIMAL_DIGITS_IN_INT32_T 10

/*
* Keep the fixed-point decimal chunks within the integer type used to hold
* them. The value is a precision, so it is one less than the number of base-10
* digits the type can safely hold for values in [0, 10^precision).
*/
#if PRINTF_USE_DOUBLE_INTERNALLY
typedef double floating_point_t;
typedef int_fast64_t fp_integral_component_t;
#define FP_TYPE_MANT_DIG DBL_MANT_DIG
#define NUM_DECIMAL_DIGITS_IN_FP_INTEGRAL_COMPONENT_T NUM_DECIMAL_DIGITS_IN_INT64_T
#else
typedef float floating_point_t;
#if PRINTF_AVOID_INT64_TO_FLOAT_CONVERSION
typedef int32_t fp_integral_component_t;
#define NUM_DECIMAL_DIGITS_IN_FP_INTEGRAL_COMPONENT_T NUM_DECIMAL_DIGITS_IN_INT32_T
#else
typedef int_fast64_t fp_integral_component_t;
#define NUM_DECIMAL_DIGITS_IN_FP_INTEGRAL_COMPONENT_T NUM_DECIMAL_DIGITS_IN_INT64_T
#endif
#define FP_TYPE_MANT_DIG FLT_MANT_DIG
#endif

#define NUM_DECIMAL_DIGITS_IN_FP_INTEGRAL_COMPONENT_T 18
/*
* Note: This value does not mean that all floating-point values printed with the
* library will be correct up to this precision; it is just an upper-bound for
* avoiding buffer overruns and such.
*/
#define PRINTF_MAX_SUPPORTED_PRECISION (NUM_DECIMAL_DIGITS_IN_FP_INTEGRAL_COMPONENT_T - 1)

#if FP_TYPE_MANT_DIG == 24

Expand Down Expand Up @@ -648,8 +679,6 @@ static void print_integer(output_gadget_t* output, printf_unsigned_value_t value

#if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS)

typedef int_fast64_t fp_integral_component_t;

/*
* Stores a fixed-precision representation of a floating-point number relative
* to a fixed precision (which cannot be determined by examining this structure)
Expand All @@ -671,14 +700,6 @@ static const floating_point_t powers_of_10[PRINTF_MAX_PRECOMPUTED_POWER_OF_10 +
#endif
};

/*
* Note: This value does not mean that all floating-point values printed with the
* library will be correct up to this precision; it is just an upper-bound for
* avoiding buffer overruns and such
*/
#define PRINTF_MAX_SUPPORTED_PRECISION (NUM_DECIMAL_DIGITS_IN_FP_INTEGRAL_COMPONENT_T - 1)


/*
* Break up a non-negative, finite, floating-point number into two integral
* parts of its decimal representation: The number up to the decimal point,
Expand Down Expand Up @@ -1173,6 +1194,17 @@ static void print_floating_point(output_gadget_t* output, floating_point_t value
return;
}

#if (!PRINTF_USE_DOUBLE_INTERNALLY && PRINTF_AVOID_INT64_TO_FLOAT_CONVERSION)
/*
* The decimal-to-exponential fallback below happens before the common precision-limiting block.
* Keep the requested precision within the 32-bit fractional chunk too.
*/
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--;
}
#endif

if (!prefer_exponential &&
((value > PRINTF_FLOAT_NOTATION_THRESHOLD) || (value < -PRINTF_FLOAT_NOTATION_THRESHOLD))) {
/*
Expand Down Expand Up @@ -1660,4 +1692,3 @@ int fctprintf(void (*out)(char c, void* extra_arg), void* extra_arg, const char*
va_end(args);
return ret;
}

21 changes: 20 additions & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,21 @@ foreach(tgt ${test_targets})
endif()
endforeach()

add_executable(avoid_int64_to_float_conversion "avoid_int64_to_float_conversion.cpp")
set_target_properties(
avoid_int64_to_float_conversion
PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO
)
target_include_directories(
avoid_int64_to_float_conversion
PRIVATE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/../src/>"
)
list(APPEND test_targets avoid_int64_to_float_conversion)

add_executable(aliasing "aliasing.c")
set_target_properties(
aliasing
Expand Down Expand Up @@ -110,5 +125,9 @@ add_test(
target_link_libraries(aliasing PRIVATE printf)
target_include_directories(aliasing PRIVATE "$<BUILD_INTERFACE:${GENERATED_INCLUDE_DIR}>")

# Not running autotest by default - it's randomized after all.
add_test(
NAME "${PROJECT_NAME}.avoid_int64_to_float_conversion"
COMMAND "avoid_int64_to_float_conversion"
)

# Not running autotest by default - it's randomized after all.
33 changes: 33 additions & 0 deletions test/avoid_int64_to_float_conversion.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#define PRINTF_VISIBILITY static
#define PRINTF_USE_DOUBLE_INTERNALLY 0
#define PRINTF_AVOID_INT64_TO_FLOAT_CONVERSION 1
#define PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 0
#define PRINTF_SUPPORT_WRITEBACK_SPECIFIER 0

#include <printf/printf.c>

static void putchar_(char c)
{
(void) c;
}

#define CATCH_CONFIG_MAIN
#include "catch.hpp"

#include <cstring>
#include <cstdint>

TEST_CASE("single precision can avoid int64 to float conversions")
{
char buffer[64];

STATIC_REQUIRE(sizeof(((struct floating_point_components*) 0)->integral) == sizeof(int32_t));
STATIC_REQUIRE(sizeof(((struct floating_point_components*) 0)->fractional) == sizeof(int32_t));
STATIC_REQUIRE(PRINTF_MAX_SUPPORTED_PRECISION == 9);

sprintf_(buffer, "%.10f", 1.25);
CHECK(std::strcmp(buffer, "1.2500000000") == 0);

sprintf_(buffer, "%.18f", -67224.546875);
CHECK(std::strcmp(buffer, "-67224.546875008000000000") == 0);
}