diff --git a/CMakeLists.txt b/CMakeLists.txt
index 650a233..b5145d3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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)
@@ -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
diff --git a/README.md b/README.md
index 70d4bfc..b669857 100644
--- a/README.md
+++ b/README.md
@@ -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`.
@@ -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).
-
diff --git a/printf_config.h.in b/printf_config.h.in
index ab47f76..7734a03 100644
--- a/printf_config.h.in
+++ b/printf_config.h.in
@@ -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@)
@@ -32,4 +33,3 @@
#define PRINTF_CHECK_FOR_NUL_IN_FORMAT_SPECIFIER @PRINTF_CHECK_FOR_NUL_IN_FORMAT_SPECIFIER@
#endif /* PRINTF_CONFIG_H_ */
-
diff --git a/src/printf/printf.c b/src/printf/printf.c
index 824e469..34cf680 100644
--- a/src/printf/printf.c
+++ b/src/printf/printf.c
@@ -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 -
@@ -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
@@ -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)
@@ -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,
@@ -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))) {
/*
@@ -1660,4 +1692,3 @@ int fctprintf(void (*out)(char c, void* extra_arg), void* extra_arg, const char*
va_end(args);
return ret;
}
-
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index b365e57..fce4c76 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -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
+ "$"
+)
+list(APPEND test_targets avoid_int64_to_float_conversion)
+
add_executable(aliasing "aliasing.c")
set_target_properties(
aliasing
@@ -110,5 +125,9 @@ add_test(
target_link_libraries(aliasing PRIVATE printf)
target_include_directories(aliasing PRIVATE "$")
-# 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.
diff --git a/test/avoid_int64_to_float_conversion.cpp b/test/avoid_int64_to_float_conversion.cpp
new file mode 100644
index 0000000..435de51
--- /dev/null
+++ b/test/avoid_int64_to_float_conversion.cpp
@@ -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
+
+static void putchar_(char c)
+{
+ (void) c;
+}
+
+#define CATCH_CONFIG_MAIN
+#include "catch.hpp"
+
+#include
+#include
+
+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);
+}