[libc-commits] [libc] [libc] Fix strfromf handling inf/nan. (PR #225805)
Alex Strelnikov via libc-commits
libc-commits at lists.llvm.org
Fri Sep 25 07:41:52 PDT 2026
https://github.com/strel-12 updated https://github.com/llvm/llvm-project/pull/225805
>From c46e4e91650f058a64eb791b4f55b6d5ba3cb826 Mon Sep 17 00:00:00 2001
From: Alex Strelnikov <strel at google.com>
Date: Wed, 23 Sep 2026 14:11:20 +0000
Subject: [PATCH 1/4] Fix str_from_util handling inf/nan, and enable
strfromf128.
---
.../printf_core/float_dec_converter.h | 85 +++++++----
.../printf_core/float_dec_converter_limited.h | 50 ++++---
.../printf_core/float_hex_converter.h | 136 ++++++++++--------
.../printf_core/float_inf_nan_converter.h | 32 +----
libc/src/stdlib/str_from_util.h | 82 +++++------
libc/src/stdlib/strfromd.cpp | 10 +-
libc/src/stdlib/strfromf.cpp | 10 +-
libc/src/stdlib/strfroml.cpp | 16 +--
libc/test/src/stdlib/strfromf_test.cpp | 3 -
9 files changed, 211 insertions(+), 213 deletions(-)
diff --git a/libc/src/__support/printf_core/float_dec_converter.h b/libc/src/__support/printf_core/float_dec_converter.h
index 89ea766e72f0a..974765a9667fe 100644
--- a/libc/src/__support/printf_core/float_dec_converter.h
+++ b/libc/src/__support/printf_core/float_dec_converter.h
@@ -489,9 +489,10 @@ FloatWriter(Writer<mode>, bool, const PaddingWriter<mode>) -> FloatWriter<mode>;
// https://doi.org/10.1145/3360595
template <typename T, OverflowMode mode,
cpp::enable_if_t<cpp::is_floating_point_v<T>, int> = 0>
-LIBC_INLINE int convert_float_decimal_typed(Writer<mode> *writer,
- const FormatSection &to_conv,
- fputil::FPBits<T> float_bits) {
+LIBC_INLINE int
+convert_finite_float_decimal_typed(Writer<mode> *writer,
+ const FormatSection &to_conv,
+ fputil::FPBits<T> float_bits) {
// signed because later we use -FRACTION_LEN
constexpr int32_t FRACTION_LEN = fputil::FPBits<T>::FRACTION_LEN;
int exponent = float_bits.get_explicit_exponent();
@@ -599,9 +600,10 @@ LIBC_INLINE int convert_float_decimal_typed(Writer<mode> *writer,
template <typename T, OverflowMode mode,
cpp::enable_if_t<cpp::is_floating_point_v<T>, int> = 0>
-LIBC_INLINE int convert_float_dec_exp_typed(Writer<mode> *writer,
- const FormatSection &to_conv,
- fputil::FPBits<T> float_bits) {
+LIBC_INLINE int
+convert_finite_float_dec_exp_typed(Writer<mode> *writer,
+ const FormatSection &to_conv,
+ fputil::FPBits<T> float_bits) {
// signed because later we use -FRACTION_LEN
constexpr int32_t FRACTION_LEN = fputil::FPBits<T>::FRACTION_LEN;
int exponent = float_bits.get_explicit_exponent();
@@ -760,9 +762,10 @@ LIBC_INLINE int convert_float_dec_exp_typed(Writer<mode> *writer,
template <typename T, OverflowMode mode,
cpp::enable_if_t<cpp::is_floating_point_v<T>, int> = 0>
-LIBC_INLINE int convert_float_dec_auto_typed(Writer<mode> *writer,
- const FormatSection &to_conv,
- fputil::FPBits<T> float_bits) {
+LIBC_INLINE int
+convert_finite_float_dec_auto_typed(Writer<mode> *writer,
+ const FormatSection &to_conv,
+ fputil::FPBits<T> float_bits) {
// signed because later we use -FRACTION_LEN
constexpr int32_t FRACTION_LEN = fputil::FPBits<T>::FRACTION_LEN;
int exponent = float_bits.get_explicit_exponent();
@@ -818,7 +821,7 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer<mode> *writer,
} else {
new_conv.precision = 0;
}
- return convert_float_decimal_typed<T>(writer, new_conv, float_bits);
+ return convert_finite_float_decimal_typed<T>(writer, new_conv, float_bits);
}
const size_t block_width = IntegerToString<intmax_t>(digits).size();
@@ -1100,7 +1103,7 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer<mode> *writer,
: trimmed_precision;
}
- return convert_float_decimal_typed<T>(writer, new_conv, float_bits);
+ return convert_finite_float_decimal_typed<T>(writer, new_conv, float_bits);
} else {
// otherwise, the conversion is with style e (or E) and precision equals
// P - 1
@@ -1120,7 +1123,7 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer<mode> *writer,
? conv_precision
: trimmed_precision;
}
- return convert_float_dec_exp_typed<T>(writer, new_conv, float_bits);
+ return convert_finite_float_dec_exp_typed<T>(writer, new_conv, float_bits);
}
}
@@ -1129,13 +1132,17 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer<mode> *writer,
template <OverflowMode mode>
LIBC_INLINE int convert_float_decimal(Writer<mode> *writer,
const FormatSection &to_conv) {
+ InfNanFPBitsProperties inf_nan_properties;
#if defined(LIBC_INTERNAL_PRINTF_CONVERT_FLOAT128)
if (to_conv.length_modifier == LengthModifier::Q) {
fputil::FPBits<float128>::StorageType float_raw = to_conv.conv_val_raw;
fputil::FPBits<float128> float_bits(float_raw);
if (!float_bits.is_inf_or_nan()) {
- return convert_float_decimal_typed<float128>(writer, to_conv, float_bits);
+ return convert_finite_float_decimal_typed<float128>(writer, to_conv,
+ float_bits);
}
+ inf_nan_properties = {.is_negative = float_bits.is_neg(),
+ .mantissa_is_zero = float_bits.get_mantissa() == 0};
} else
#endif // LIBC_INTERNAL_PRINTF_CONVERT_FLOAT128
#ifndef LIBC_TYPES_LONG_DOUBLE_IS_DOUBLE_DOUBLE
@@ -1145,9 +1152,11 @@ LIBC_INLINE int convert_float_decimal(Writer<mode> *writer,
to_conv.conv_val_raw);
fputil::FPBits<long double> float_bits(float_raw);
if (!float_bits.is_inf_or_nan()) {
- return convert_float_decimal_typed<long double>(writer, to_conv,
- float_bits);
+ return convert_finite_float_decimal_typed<long double>(writer, to_conv,
+ float_bits);
}
+ inf_nan_properties = {.is_negative = float_bits.is_neg(),
+ .mantissa_is_zero = float_bits.get_mantissa() == 0};
} else
#endif // !LIBC_TYPES_LONG_DOUBLE_IS_DOUBLE_DOUBLE
{
@@ -1155,23 +1164,30 @@ LIBC_INLINE int convert_float_decimal(Writer<mode> *writer,
static_cast<fputil::FPBits<double>::StorageType>(to_conv.conv_val_raw);
fputil::FPBits<double> float_bits(float_raw);
if (!float_bits.is_inf_or_nan()) {
- return convert_float_decimal_typed<double>(writer, to_conv, float_bits);
+ return convert_finite_float_decimal_typed<double>(writer, to_conv,
+ float_bits);
}
+ inf_nan_properties = {.is_negative = float_bits.is_neg(),
+ .mantissa_is_zero = float_bits.get_mantissa() == 0};
}
- return convert_inf_nan(writer, to_conv);
+ return convert_inf_nan(writer, inf_nan_properties, to_conv);
}
template <OverflowMode mode>
LIBC_INLINE int convert_float_dec_exp(Writer<mode> *writer,
const FormatSection &to_conv) {
+ InfNanFPBitsProperties inf_nan_properties;
#if defined(LIBC_INTERNAL_PRINTF_CONVERT_FLOAT128)
if (to_conv.length_modifier == LengthModifier::Q) {
fputil::FPBits<float128>::StorageType float_raw = to_conv.conv_val_raw;
fputil::FPBits<float128> float_bits(float_raw);
if (!float_bits.is_inf_or_nan()) {
- return convert_float_dec_exp_typed<float128>(writer, to_conv, float_bits);
+ return convert_finite_float_dec_exp_typed<float128>(writer, to_conv,
+ float_bits);
}
+ inf_nan_properties = {.is_negative = float_bits.is_neg(),
+ .mantissa_is_zero = float_bits.get_mantissa() == 0};
} else
#endif // LIBC_INTERNAL_PRINTF_CONVERT_FLOAT128
#ifndef LIBC_TYPES_LONG_DOUBLE_IS_DOUBLE_DOUBLE
@@ -1181,9 +1197,11 @@ LIBC_INLINE int convert_float_dec_exp(Writer<mode> *writer,
to_conv.conv_val_raw);
fputil::FPBits<long double> float_bits(float_raw);
if (!float_bits.is_inf_or_nan()) {
- return convert_float_dec_exp_typed<long double>(writer, to_conv,
- float_bits);
+ return convert_finite_float_dec_exp_typed<long double>(writer, to_conv,
+ float_bits);
}
+ inf_nan_properties = {.is_negative = float_bits.is_neg(),
+ .mantissa_is_zero = float_bits.get_mantissa() == 0};
} else
#endif // !LIBC_TYPES_LONG_DOUBLE_IS_DOUBLE_DOUBLE
{
@@ -1191,24 +1209,30 @@ LIBC_INLINE int convert_float_dec_exp(Writer<mode> *writer,
static_cast<fputil::FPBits<double>::StorageType>(to_conv.conv_val_raw);
fputil::FPBits<double> float_bits(float_raw);
if (!float_bits.is_inf_or_nan()) {
- return convert_float_dec_exp_typed<double>(writer, to_conv, float_bits);
+ return convert_finite_float_dec_exp_typed<double>(writer, to_conv,
+ float_bits);
}
+ inf_nan_properties = {.is_negative = float_bits.is_neg(),
+ .mantissa_is_zero = float_bits.get_mantissa() == 0};
}
- return convert_inf_nan(writer, to_conv);
+ return convert_inf_nan(writer, inf_nan_properties, to_conv);
}
template <OverflowMode mode>
LIBC_INLINE int convert_float_dec_auto(Writer<mode> *writer,
const FormatSection &to_conv) {
+ InfNanFPBitsProperties inf_nan_properties;
#if defined(LIBC_INTERNAL_PRINTF_CONVERT_FLOAT128)
if (to_conv.length_modifier == LengthModifier::Q) {
fputil::FPBits<float128>::StorageType float_raw = to_conv.conv_val_raw;
fputil::FPBits<float128> float_bits(float_raw);
if (!float_bits.is_inf_or_nan()) {
- return convert_float_dec_auto_typed<float128>(writer, to_conv,
- float_bits);
+ return convert_finite_float_dec_auto_typed<float128>(writer, to_conv,
+ float_bits);
}
+ inf_nan_properties = {.is_negative = float_bits.is_neg(),
+ .mantissa_is_zero = float_bits.get_mantissa() == 0};
} else
#endif // LIBC_INTERNAL_PRINTF_CONVERT_FLOAT128
#ifndef LIBC_TYPES_LONG_DOUBLE_IS_DOUBLE_DOUBLE
@@ -1218,9 +1242,11 @@ LIBC_INLINE int convert_float_dec_auto(Writer<mode> *writer,
to_conv.conv_val_raw);
fputil::FPBits<long double> float_bits(float_raw);
if (!float_bits.is_inf_or_nan()) {
- return convert_float_dec_auto_typed<long double>(writer, to_conv,
- float_bits);
+ return convert_finite_float_dec_auto_typed<long double>(writer, to_conv,
+ float_bits);
}
+ inf_nan_properties = {.is_negative = float_bits.is_neg(),
+ .mantissa_is_zero = float_bits.get_mantissa() == 0};
} else
#endif // !LIBC_TYPES_LONG_DOUBLE_IS_DOUBLE_DOUBLE
{
@@ -1228,11 +1254,14 @@ LIBC_INLINE int convert_float_dec_auto(Writer<mode> *writer,
static_cast<fputil::FPBits<double>::StorageType>(to_conv.conv_val_raw);
fputil::FPBits<double> float_bits(float_raw);
if (!float_bits.is_inf_or_nan()) {
- return convert_float_dec_auto_typed<double>(writer, to_conv, float_bits);
+ return convert_finite_float_dec_auto_typed<double>(writer, to_conv,
+ float_bits);
}
+ inf_nan_properties = {.is_negative = float_bits.is_neg(),
+ .mantissa_is_zero = float_bits.get_mantissa() == 0};
}
- return convert_inf_nan(writer, to_conv);
+ return convert_inf_nan(writer, inf_nan_properties, to_conv);
}
} // namespace printf_core
diff --git a/libc/src/__support/printf_core/float_dec_converter_limited.h b/libc/src/__support/printf_core/float_dec_converter_limited.h
index 7aa886db37a3d..36bdba67e9f31 100644
--- a/libc/src/__support/printf_core/float_dec_converter_limited.h
+++ b/libc/src/__support/printf_core/float_dec_converter_limited.h
@@ -375,11 +375,11 @@ DigitsOutput decimal_digits(DigitsInput input, int precision, bool e_mode) {
}
template <OverflowMode mode>
-LIBC_INLINE int convert_float_inner(Writer<mode> *writer,
- const FormatSection &to_conv,
- int32_t fraction_len, int exponent,
- AnyFloatStorageType mantissa, Sign sign,
- ConversionType ctype) {
+LIBC_INLINE int convert_finite_float_inner(Writer<mode> *writer,
+ const FormatSection &to_conv,
+ int32_t fraction_len, int exponent,
+ AnyFloatStorageType mantissa,
+ Sign sign, ConversionType ctype) {
constexpr char DECIMAL_POINT = '.';
// If to_conv doesn't specify a precision, the precision defaults to 6.
unsigned precision = to_conv.precision < 0 ? 6 : to_conv.precision;
@@ -621,26 +621,30 @@ LIBC_INLINE int convert_float_inner(Writer<mode> *writer,
template <typename T, OverflowMode mode,
cpp::enable_if_t<cpp::is_floating_point_v<T>, int> = 0>
LIBC_INLINE int
-convert_float_typed(Writer<mode> *writer, const FormatSection &to_conv,
- fputil::FPBits<T> float_bits, ConversionType ctype) {
- return convert_float_inner(writer, to_conv, float_bits.FRACTION_LEN,
- float_bits.get_explicit_exponent(),
- float_bits.get_explicit_mantissa(),
- float_bits.sign(), ctype);
+convert_finite_float_typed(Writer<mode> *writer, const FormatSection &to_conv,
+ fputil::FPBits<T> float_bits, ConversionType ctype) {
+ return convert_finite_float_inner(writer, to_conv, float_bits.FRACTION_LEN,
+ float_bits.get_explicit_exponent(),
+ float_bits.get_explicit_mantissa(),
+ float_bits.sign(), ctype);
}
template <OverflowMode mode>
LIBC_INLINE int convert_float_outer(Writer<mode> *writer,
const FormatSection &to_conv,
ConversionType ctype) {
+ InfNanFPBitsProperties inf_nan_properties;
#if defined(LIBC_INTERNAL_PRINTF_CONVERT_FLOAT128)
if (to_conv.length_modifier == LengthModifier::Q) {
fputil::FPBits<float128> float_bits(
static_cast<fputil::FPBits<float128>::StorageType>(
to_conv.conv_val_raw));
if (!float_bits.is_inf_or_nan()) {
- return convert_float_typed<float128>(writer, to_conv, float_bits, ctype);
+ return convert_finite_float_typed<float128>(writer, to_conv, float_bits,
+ ctype);
}
+ inf_nan_properties = {.is_negative = float_bits.is_neg(),
+ .mantissa_is_zero = float_bits.get_mantissa() == 0};
} else
#endif // LIBC_INTERNAL_PRINTF_CONVERT_FLOAT128
#ifndef LIBC_TYPES_LONG_DOUBLE_IS_DOUBLE_DOUBLE
@@ -649,9 +653,11 @@ LIBC_INLINE int convert_float_outer(Writer<mode> *writer,
static_cast<fputil::FPBits<long double>::StorageType>(
to_conv.conv_val_raw));
if (!float_bits.is_inf_or_nan()) {
- return convert_float_typed<long double>(writer, to_conv, float_bits,
- ctype);
+ return convert_finite_float_typed<long double>(writer, to_conv,
+ float_bits, ctype);
}
+ inf_nan_properties = {.is_negative = float_bits.is_neg(),
+ .mantissa_is_zero = float_bits.get_mantissa() == 0};
} else
#endif // !LIBC_TYPES_LONG_DOUBLE_IS_DOUBLE_DOUBLE
{
@@ -659,11 +665,14 @@ LIBC_INLINE int convert_float_outer(Writer<mode> *writer,
static_cast<fputil::FPBits<double>::StorageType>(to_conv.conv_val_raw);
fputil::FPBits<double> float_bits(float_raw);
if (!float_bits.is_inf_or_nan()) {
- return convert_float_typed<double>(writer, to_conv, float_bits, ctype);
+ return convert_finite_float_typed<double>(writer, to_conv, float_bits,
+ ctype);
}
+ inf_nan_properties = {.is_negative = float_bits.is_neg(),
+ .mantissa_is_zero = float_bits.get_mantissa() == 0};
}
- return convert_inf_nan(writer, to_conv);
+ return convert_inf_nan(writer, inf_nan_properties, to_conv);
}
template <typename T, OverflowMode mode,
@@ -671,7 +680,8 @@ template <typename T, OverflowMode mode,
LIBC_INLINE int convert_float_decimal_typed(Writer<mode> *writer,
const FormatSection &to_conv,
fputil::FPBits<T> float_bits) {
- return convert_float_typed<T>(writer, to_conv, float_bits, ConversionType::F);
+ return convert_finite_float_typed<T>(writer, to_conv, float_bits,
+ ConversionType::F);
}
template <typename T, OverflowMode mode,
@@ -679,7 +689,8 @@ template <typename T, OverflowMode mode,
LIBC_INLINE int convert_float_dec_exp_typed(Writer<mode> *writer,
const FormatSection &to_conv,
fputil::FPBits<T> float_bits) {
- return convert_float_typed<T>(writer, to_conv, float_bits, ConversionType::E);
+ return convert_finite_float_typed<T>(writer, to_conv, float_bits,
+ ConversionType::E);
}
template <typename T, OverflowMode mode,
@@ -687,7 +698,8 @@ template <typename T, OverflowMode mode,
LIBC_INLINE int convert_float_dec_auto_typed(Writer<mode> *writer,
const FormatSection &to_conv,
fputil::FPBits<T> float_bits) {
- return convert_float_typed<T>(writer, to_conv, float_bits, ConversionType::G);
+ return convert_finite_float_typed<T>(writer, to_conv, float_bits,
+ ConversionType::G);
}
template <OverflowMode mode>
diff --git a/libc/src/__support/printf_core/float_hex_converter.h b/libc/src/__support/printf_core/float_hex_converter.h
index 15f3d9f05a127..ee751901dda1e 100644
--- a/libc/src/__support/printf_core/float_hex_converter.h
+++ b/libc/src/__support/printf_core/float_hex_converter.h
@@ -26,30 +26,53 @@ namespace LIBC_NAMESPACE_DECL {
namespace printf_core {
struct FloatHexExpFPBitsProperties {
- bool is_negative;
- int exponent;
AnyFloatStorageType mantissa;
- bool is_inf_or_nan;
+ int exponent;
uint32_t fraction_bits;
+ bool is_negative;
+ bool is_inf_or_nan;
+ bool mantissa_is_zero;
};
template <typename T>
-FloatHexExpFPBitsProperties
-get_float_hex_exp_fp_bits_properties(AnyFloatStorageType float_raw) {
- fputil::FPBits<T> float_bits(
- static_cast<typename fputil::FPBits<T>::StorageType>(float_raw));
+LIBC_INLINE FloatHexExpFPBitsProperties
+get_float_hex_exp_fp_bits_properties_typed(fputil::FPBits<T> float_bits) {
return {
- .is_negative = float_bits.is_neg(),
- .exponent = float_bits.get_explicit_exponent(),
.mantissa = float_bits.get_explicit_mantissa(),
- .is_inf_or_nan = float_bits.is_inf_or_nan(),
+ .exponent = float_bits.get_explicit_exponent(),
.fraction_bits = fputil::FPBits<T>::FRACTION_LEN,
+ .is_negative = float_bits.is_neg(),
+ .is_inf_or_nan = float_bits.is_inf_or_nan(),
+ .mantissa_is_zero = float_bits.get_mantissa() == 0,
};
}
+// Returns relevant floating point properties for conversion using
+// `to_conv.length_modifier` to infer the conversion value type.
+LIBC_INLINE FloatHexExpFPBitsProperties
+get_float_hex_exp_fp_bits_properties_lm(const FormatSection &to_conv) {
+#if defined(LIBC_INTERNAL_PRINTF_CONVERT_FLOAT128)
+ if (to_conv.length_modifier == LengthModifier::Q)
+ return get_float_hex_exp_fp_bits_properties_typed<float128>(
+ fputil::FPBits<float128>(to_conv.conv_val_raw));
+#endif // LIBC_INTERNAL_PRINTF_CONVERT_FLOAT128
+#ifndef LIBC_TYPES_LONG_DOUBLE_IS_DOUBLE_DOUBLE
+ if (to_conv.length_modifier == LengthModifier::L)
+ return get_float_hex_exp_fp_bits_properties_typed<long double>(
+ fputil::FPBits<long double>(
+ static_cast<fputil::FPBits<long double>::StorageType>(
+ to_conv.conv_val_raw)));
+#endif // !LIBC_TYPES_LONG_DOUBLE_IS_DOUBLE_DOUBLE
+ return get_float_hex_exp_fp_bits_properties_typed<double>(
+ fputil::FPBits<double>(static_cast<fputil::FPBits<double>::StorageType>(
+ to_conv.conv_val_raw)));
+}
+
template <OverflowMode mode>
-LIBC_INLINE int convert_float_hex_exp(Writer<mode> *writer,
- const FormatSection &to_conv) {
+LIBC_INLINE int
+convert_finite_float_hex_exp(Writer<mode> *writer,
+ FloatHexExpFPBitsProperties fp_bits_properties,
+ const FormatSection &to_conv) {
#if defined(LIBC_INTERNAL_PRINTF_CONVERT_FLOAT128)
static constexpr uint32_t MAX_POSSIBLE_FRACTION_LEN =
fputil::FPBits<float128>::FRACTION_LEN;
@@ -67,30 +90,9 @@ LIBC_INLINE int convert_float_hex_exp(Writer<mode> *writer,
fputil::FPBits<double>::EXP_LEN;
#endif
- FloatHexExpFPBitsProperties properties;
-#if defined(LIBC_INTERNAL_PRINTF_CONVERT_FLOAT128)
- if (to_conv.length_modifier == LengthModifier::Q) {
- properties =
- get_float_hex_exp_fp_bits_properties<float128>(to_conv.conv_val_raw);
- } else
-#endif // LIBC_INTERNAL_PRINTF_CONVERT_FLOAT128
-#ifndef LIBC_TYPES_LONG_DOUBLE_IS_DOUBLE_DOUBLE
- if (to_conv.length_modifier == LengthModifier::L) {
- properties =
- get_float_hex_exp_fp_bits_properties<long double>(to_conv.conv_val_raw);
- } else
-#endif // !LIBC_TYPES_LONG_DOUBLE_IS_DOUBLE_DOUBLE
- {
- properties =
- get_float_hex_exp_fp_bits_properties<double>(to_conv.conv_val_raw);
- }
-
- if (properties.is_inf_or_nan)
- return convert_inf_nan(writer, to_conv);
-
char sign_char = 0;
- if (properties.is_negative)
+ if (fp_bits_properties.is_negative)
sign_char = '-';
else if ((to_conv.flags & FormatFlags::FORCE_SIGN) == FormatFlags::FORCE_SIGN)
sign_char = '+'; // FORCE_SIGN has precedence over SPACE_PREFIX
@@ -104,9 +106,10 @@ LIBC_INLINE int convert_float_hex_exp(Writer<mode> *writer,
// digits. This is primarily relevant for x86 80 bit long doubles, which have
// 63 bit mantissas. In the case where the mantissa is 0, however, the
// exponent should stay as 0.
- if (properties.fraction_bits % BITS_IN_HEX_DIGIT != 0 &&
- properties.mantissa > 0) {
- properties.exponent -= properties.fraction_bits % BITS_IN_HEX_DIGIT;
+ if (fp_bits_properties.fraction_bits % BITS_IN_HEX_DIGIT != 0 &&
+ fp_bits_properties.mantissa > 0) {
+ fp_bits_properties.exponent -=
+ fp_bits_properties.fraction_bits % BITS_IN_HEX_DIGIT;
}
// This is the max number of digits it can take to represent the mantissa.
@@ -117,7 +120,7 @@ LIBC_INLINE int convert_float_hex_exp(Writer<mode> *writer,
(MAX_POSSIBLE_FRACTION_LEN / BITS_IN_HEX_DIGIT) + 1;
char mant_buffer[MANT_BUFF_LEN];
- size_t mant_len = (properties.fraction_bits / BITS_IN_HEX_DIGIT) + 1;
+ size_t mant_len = (fp_bits_properties.fraction_bits / BITS_IN_HEX_DIGIT) + 1;
// Precision only tracks the number of digits after the hexadecimal point, so
// we have to add one to account for the digit before the hexadecimal point.
@@ -128,34 +131,37 @@ LIBC_INLINE int convert_float_hex_exp(Writer<mode> *writer,
(mant_len - intended_digits) * BITS_IN_HEX_DIGIT;
const AnyFloatStorageType truncated_bits =
- properties.mantissa & ((AnyFloatStorageType(1) << shift_amount) - 1);
+ fp_bits_properties.mantissa &
+ ((AnyFloatStorageType(1) << shift_amount) - 1);
const AnyFloatStorageType halfway_const = AnyFloatStorageType(1)
<< (shift_amount - 1);
- properties.mantissa >>= shift_amount;
+ fp_bits_properties.mantissa >>= shift_amount;
#ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
// Round to nearest, if it's exactly halfway then round to even.
if (truncated_bits > halfway_const)
- ++properties.mantissa;
+ ++fp_bits_properties.mantissa;
else if (truncated_bits == halfway_const)
- properties.mantissa = properties.mantissa + (properties.mantissa & 1);
+ fp_bits_properties.mantissa =
+ fp_bits_properties.mantissa + (fp_bits_properties.mantissa & 1);
#else
switch (fputil::quick_get_round()) {
case FE_TONEAREST:
// Round to nearest, if it's exactly halfway then round to even.
if (truncated_bits > halfway_const)
- ++properties.mantissa;
+ ++fp_bits_properties.mantissa;
else if (truncated_bits == halfway_const)
- properties.mantissa = properties.mantissa + (properties.mantissa & 1);
+ fp_bits_properties.mantissa =
+ fp_bits_properties.mantissa + (fp_bits_properties.mantissa & 1);
break;
case FE_DOWNWARD:
- if (truncated_bits > 0 && properties.is_negative)
- ++properties.mantissa;
+ if (truncated_bits > 0 && fp_bits_properties.is_negative)
+ ++fp_bits_properties.mantissa;
break;
case FE_UPWARD:
- if (truncated_bits > 0 && !properties.is_negative)
- ++properties.mantissa;
+ if (truncated_bits > 0 && !fp_bits_properties.is_negative)
+ ++fp_bits_properties.mantissa;
break;
case FE_TOWARDZERO:
break;
@@ -164,10 +170,10 @@ LIBC_INLINE int convert_float_hex_exp(Writer<mode> *writer,
// If the rounding caused an overflow, shift the mantissa and adjust the
// exponent to match.
- if (properties.mantissa >=
+ if (fp_bits_properties.mantissa >=
(AnyFloatStorageType(1) << (intended_digits * BITS_IN_HEX_DIGIT))) {
- properties.mantissa >>= BITS_IN_HEX_DIGIT;
- properties.exponent += BITS_IN_HEX_DIGIT;
+ fp_bits_properties.mantissa >>= BITS_IN_HEX_DIGIT;
+ fp_bits_properties.exponent += BITS_IN_HEX_DIGIT;
}
mant_len = intended_digits;
@@ -175,8 +181,8 @@ LIBC_INLINE int convert_float_hex_exp(Writer<mode> *writer,
size_t mant_cur = mant_len;
size_t first_non_zero = 1;
- for (; mant_cur > 0; --mant_cur, properties.mantissa >>= 4) {
- char mant_mod_16 = static_cast<char>(properties.mantissa % 16);
+ for (; mant_cur > 0; --mant_cur, fp_bits_properties.mantissa >>= 4) {
+ char mant_mod_16 = static_cast<char>(fp_bits_properties.mantissa % 16);
char new_digit = internal::int_to_b36_char(mant_mod_16);
if (internal::isupper(to_conv.conv_name))
new_digit = internal::toupper(new_digit);
@@ -200,15 +206,16 @@ LIBC_INLINE int convert_float_hex_exp(Writer<mode> *writer,
char exp_buffer[EXP_LEN];
bool exp_is_negative = false;
- if (properties.exponent < 0) {
+ if (fp_bits_properties.exponent < 0) {
exp_is_negative = true;
- properties.exponent = -properties.exponent;
+ fp_bits_properties.exponent = -fp_bits_properties.exponent;
}
size_t exp_cur = EXP_LEN;
- for (; properties.exponent > 0; --exp_cur, properties.exponent /= 10) {
+ for (; fp_bits_properties.exponent > 0;
+ --exp_cur, fp_bits_properties.exponent /= 10) {
exp_buffer[exp_cur - 1] =
- internal::int_to_b36_char(properties.exponent % 10);
+ internal::int_to_b36_char(fp_bits_properties.exponent % 10);
}
if (exp_cur == EXP_LEN) { // if nothing else was written, write a 0.
exp_buffer[EXP_LEN - 1] = '0';
@@ -295,6 +302,21 @@ LIBC_INLINE int convert_float_hex_exp(Writer<mode> *writer,
return WRITE_OK;
}
+template <OverflowMode mode>
+LIBC_INLINE int convert_float_hex_exp(Writer<mode> *writer,
+ const FormatSection &to_conv) {
+ FloatHexExpFPBitsProperties fp_bits_properties =
+ get_float_hex_exp_fp_bits_properties_lm(to_conv);
+ if (fp_bits_properties.is_inf_or_nan)
+ return convert_inf_nan(
+ writer,
+ InfNanFPBitsProperties{.is_negative = fp_bits_properties.is_negative,
+ .mantissa_is_zero =
+ fp_bits_properties.mantissa_is_zero},
+ to_conv);
+ return convert_finite_float_hex_exp(writer, fp_bits_properties, to_conv);
+}
+
} // namespace printf_core
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/printf_core/float_inf_nan_converter.h b/libc/src/__support/printf_core/float_inf_nan_converter.h
index 40740c701d99f..5d306a13d5f13 100644
--- a/libc/src/__support/printf_core/float_inf_nan_converter.h
+++ b/libc/src/__support/printf_core/float_inf_nan_converter.h
@@ -27,41 +27,15 @@ struct InfNanFPBitsProperties {
bool mantissa_is_zero;
};
-template <typename T>
-InfNanFPBitsProperties
-get_inf_nan_fp_bits_properties(AnyFloatStorageType float_raw) {
- fputil::FPBits<T> float_bits(
- static_cast<typename fputil::FPBits<T>::StorageType>(float_raw));
- return {
- .is_negative = float_bits.is_neg(),
- .mantissa_is_zero = float_bits.get_mantissa() == 0,
- };
-}
-
template <OverflowMode mode>
LIBC_INLINE int convert_inf_nan(Writer<mode> *writer,
+ InfNanFPBitsProperties fp_bits_properties,
const FormatSection &to_conv) {
// All of the letters will be defined relative to variable a, which will be
// the appropriate case based on the case of the conversion.
- InfNanFPBitsProperties properties;
-#if defined(LIBC_INTERNAL_PRINTF_CONVERT_FLOAT128)
- if (to_conv.length_modifier == LengthModifier::Q) {
- properties = get_inf_nan_fp_bits_properties<float128>(to_conv.conv_val_raw);
- } else
-#endif // LIBC_INTERNAL_PRINTF_CONVERT_FLOAT128
-#ifndef LIBC_TYPES_LONG_DOUBLE_IS_DOUBLE_DOUBLE
- if (to_conv.length_modifier == LengthModifier::L) {
- properties =
- get_inf_nan_fp_bits_properties<long double>(to_conv.conv_val_raw);
- } else
-#endif // !LIBC_TYPES_LONG_DOUBLE_IS_DOUBLE_DOUBLE
- {
- properties = get_inf_nan_fp_bits_properties<double>(to_conv.conv_val_raw);
- }
-
char sign_char = 0;
- if (properties.is_negative)
+ if (fp_bits_properties.is_negative)
sign_char = '-';
else if ((to_conv.flags & FormatFlags::FORCE_SIGN) == FormatFlags::FORCE_SIGN)
sign_char = '+'; // FORCE_SIGN has precedence over SPACE_PREFIX
@@ -81,7 +55,7 @@ LIBC_INLINE int convert_inf_nan(Writer<mode> *writer,
if (sign_char)
RET_IF_RESULT_NEGATIVE(writer->write(sign_char));
- if (properties.mantissa_is_zero) { // inf
+ if (fp_bits_properties.mantissa_is_zero) { // inf
RET_IF_RESULT_NEGATIVE(
writer->write(internal::islower(to_conv.conv_name) ? "inf" : "INF"));
} else { // nan
diff --git a/libc/src/stdlib/str_from_util.h b/libc/src/stdlib/str_from_util.h
index 205adcff17094..60468ec441d90 100644
--- a/libc/src/stdlib/str_from_util.h
+++ b/libc/src/stdlib/str_from_util.h
@@ -17,6 +17,7 @@
#ifndef LLVM_LIBC_SRC_STDLIB_STRFROM_UTIL_H
#define LLVM_LIBC_SRC_STDLIB_STRFROM_UTIL_H
+#include "include/llvm-libc-types/float128.h"
#include "src/__support/CPP/type_traits.h"
#include "src/__support/macros/config.h"
#include "src/__support/printf_core/converter_atlas.h"
@@ -32,22 +33,12 @@ namespace internal {
template <typename T>
using storage_type = typename fputil::FPBits<T>::StorageType;
-template <typename T>
-printf_core::FormatSection parse_format_string(const char *__restrict format,
- T fp) {
+template <typename T, printf_core::OverflowMode overflow_mode>
+LIBC_INLINE int strfromfloat_convert(printf_core::Writer<overflow_mode> *writer,
+ const char *__restrict format, T fp) {
printf_core::FormatSection section;
size_t cur_pos = 0;
- // There is no typed conversion function to convert single precision float
- // to hex exponential format, and the function convert_float_hex_exp()
- // requires a double or long double value to work correctly.
- // To work around this, we convert fp to double if it is single precision, and
- // then use that double precision value in the %{A, a} conversion specifiers.
- [[maybe_unused]] double new_fp;
- bool t_is_single_prec_type = cpp::is_same<T, float>::value;
- if (t_is_single_prec_type)
- new_fp = (double)fp;
-
if (format[cur_pos] == '%') {
section.has_conv = true;
++cur_pos;
@@ -71,68 +62,67 @@ printf_core::FormatSection parse_format_string(const char *__restrict format,
switch (format[cur_pos]) {
case 'a':
case 'A':
- if (t_is_single_prec_type)
- section.conv_val_raw = cpp::bit_cast<storage_type<double>>(new_fp);
- else
- section.conv_val_raw = cpp::bit_cast<storage_type<T>>(fp);
- break;
case 'e':
case 'E':
case 'f':
case 'F':
case 'g':
case 'G':
- section.conv_val_raw = cpp::bit_cast<storage_type<T>>(fp);
break;
default:
section.has_conv = false;
- while (format[cur_pos] != '\0')
- ++cur_pos;
break;
}
-
- if (format[cur_pos] != '\0')
- ++cur_pos;
} else {
section.has_conv = false;
- // We are looking for exactly one section, so no more '%'
- while (format[cur_pos] != '\0')
- ++cur_pos;
}
- section.raw_string = {format, cur_pos};
- return section;
-}
-
-template <typename T, printf_core::OverflowMode overflow_mode>
-int strfromfloat_convert(printf_core::Writer<overflow_mode> *writer,
- const printf_core::FormatSection §ion) {
if (!section.has_conv)
- return writer->write(section.raw_string);
-
- auto res = static_cast<storage_type<T>>(section.conv_val_raw);
+ return writer->write(format);
- fputil::FPBits<T> strfromfloat_bits(res);
+ fputil::FPBits<T> strfromfloat_bits(fp);
if (strfromfloat_bits.is_inf_or_nan())
- return convert_inf_nan(writer, section);
+ return convert_inf_nan(
+ writer,
+ printf_core::InfNanFPBitsProperties{
+ .is_negative = strfromfloat_bits.is_neg(),
+ .mantissa_is_zero = strfromfloat_bits.get_mantissa() == 0,
+ },
+ section);
switch (section.conv_name) {
case 'f':
case 'F':
- return convert_float_decimal_typed(writer, section, strfromfloat_bits);
+ return printf_core::convert_finite_float_decimal_typed(writer, section,
+ strfromfloat_bits);
case 'e':
case 'E':
- return convert_float_dec_exp_typed(writer, section, strfromfloat_bits);
+ return printf_core::convert_finite_float_dec_exp_typed(writer, section,
+ strfromfloat_bits);
case 'a':
case 'A':
- return convert_float_hex_exp(writer, section);
+ // There is no typed conversion function to convert single precision float
+ // to hex exponential format, and the convert_finite_float_hex_exp()
+ // requires a double or long double value to work correctly.
+ if constexpr (cpp::is_same_v<T, float>) {
+ return printf_core::convert_finite_float_hex_exp(
+ writer,
+ printf_core::get_float_hex_exp_fp_bits_properties_typed(
+ fputil::FPBits<double>(static_cast<double>(fp))),
+ section);
+ } else {
+ return printf_core::convert_finite_float_hex_exp(
+ writer,
+ printf_core::get_float_hex_exp_fp_bits_properties_typed(
+ strfromfloat_bits),
+ section);
+ }
case 'g':
case 'G':
- return convert_float_dec_auto_typed(writer, section, strfromfloat_bits);
- default:
- return writer->write(section.raw_string);
+ return printf_core::convert_finite_float_dec_auto_typed(writer, section,
+ strfromfloat_bits);
}
- return -1;
+ __builtin_unreachable();
}
} // namespace internal
diff --git a/libc/src/stdlib/strfromd.cpp b/libc/src/stdlib/strfromd.cpp
index 8983a0f5787d8..e54b35b22b869 100644
--- a/libc/src/stdlib/strfromd.cpp
+++ b/libc/src/stdlib/strfromd.cpp
@@ -20,17 +20,9 @@ LLVM_LIBC_FUNCTION(int, strfromd,
double fp)) {
LIBC_ASSERT(s != nullptr);
- printf_core::FormatSection section =
- internal::parse_format_string(format, fp);
printf_core::Writer writer =
printf_core::make_drop_overflow_writer(s, (n > 0 ? n - 1 : 0));
-
- int result = 0;
- if (section.has_conv)
- result = internal::strfromfloat_convert<double>(&writer, section);
- else
- result = writer.write(section.raw_string);
-
+ int result = internal::strfromfloat_convert(&writer, format, fp);
if (result < 0)
return result;
diff --git a/libc/src/stdlib/strfromf.cpp b/libc/src/stdlib/strfromf.cpp
index 417372d576af6..558cf9ff93750 100644
--- a/libc/src/stdlib/strfromf.cpp
+++ b/libc/src/stdlib/strfromf.cpp
@@ -20,17 +20,9 @@ LLVM_LIBC_FUNCTION(int, strfromf,
float fp)) {
LIBC_ASSERT(s != nullptr);
- printf_core::FormatSection section =
- internal::parse_format_string(format, fp);
printf_core::Writer writer =
printf_core::make_drop_overflow_writer(s, (n > 0 ? n - 1 : 0));
-
- int result = 0;
- if (section.has_conv)
- result = internal::strfromfloat_convert<float>(&writer, section);
- else
- result = writer.write(section.raw_string);
-
+ int result = internal::strfromfloat_convert(&writer, format, fp);
if (result < 0)
return result;
diff --git a/libc/src/stdlib/strfroml.cpp b/libc/src/stdlib/strfroml.cpp
index 2f32db0966b60..ef87d5fd7578c 100644
--- a/libc/src/stdlib/strfroml.cpp
+++ b/libc/src/stdlib/strfroml.cpp
@@ -20,21 +20,11 @@ LLVM_LIBC_FUNCTION(int, strfroml,
long double fp)) {
LIBC_ASSERT(s != nullptr);
- printf_core::FormatSection section =
- internal::parse_format_string(format, fp);
-
- // To ensure that the conversion function actually uses long double,
- // the length modifier has to be set to LengthModifier::L
- section.length_modifier = printf_core::LengthModifier::L;
-
printf_core::Writer writer =
printf_core::make_drop_overflow_writer(s, (n > 0 ? n - 1 : 0));
-
- int result = 0;
- if (section.has_conv)
- result = internal::strfromfloat_convert<long double>(&writer, section);
- else
- result = writer.write(section.raw_string);
+ int result = internal::strfromfloat_convert(&writer, format, fp);
+ if (result < 0)
+ return result;
if (result < 0)
return result;
diff --git a/libc/test/src/stdlib/strfromf_test.cpp b/libc/test/src/stdlib/strfromf_test.cpp
index 5baeb119bf150..1c4f18731b8c4 100644
--- a/libc/test/src/stdlib/strfromf_test.cpp
+++ b/libc/test/src/stdlib/strfromf_test.cpp
@@ -88,8 +88,6 @@ TEST_F(LlvmLibcStrfromfTest, InsufficientBufferSize) {
ASSERT_STREQ(buff, "1.05"); // Make sure that buff has not changed
}
-// TODO: fix https://github.com/llvm/llvm-project/issues/217708.
-#if 0
TEST_F(LlvmLibcStrfromfTest, InfNanValues) {
char buff[15];
int result;
@@ -109,7 +107,6 @@ TEST_F(LlvmLibcStrfromfTest, InfNanValues) {
result = LIBC_NAMESPACE::strfromf(buff, 10, "%A", -nan);
EXPECT_STREQ_LEN(result, buff, "-NAN");
}
-#endif
// https://github.com/llvm/llvm-project/issues/166795
TEST_F(LlvmLibcStrfromfTest, ResultOverflow) {
>From 1e6e2a53e5ed2be91bcd2978e0b9a45a86268ec7 Mon Sep 17 00:00:00 2001
From: Alex Strelnikov <strel at google.com>
Date: Wed, 23 Sep 2026 15:58:46 +0000
Subject: [PATCH 2/4] Update some missed function spellings
---
.../printf_core/float_dec_converter_limited.h | 21 +++++++++++--------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/libc/src/__support/printf_core/float_dec_converter_limited.h b/libc/src/__support/printf_core/float_dec_converter_limited.h
index 36bdba67e9f31..afdfd68a51379 100644
--- a/libc/src/__support/printf_core/float_dec_converter_limited.h
+++ b/libc/src/__support/printf_core/float_dec_converter_limited.h
@@ -677,27 +677,30 @@ LIBC_INLINE int convert_float_outer(Writer<mode> *writer,
template <typename T, OverflowMode mode,
cpp::enable_if_t<cpp::is_floating_point_v<T>, int> = 0>
-LIBC_INLINE int convert_float_decimal_typed(Writer<mode> *writer,
- const FormatSection &to_conv,
- fputil::FPBits<T> float_bits) {
+LIBC_INLINE int
+convert_finite_float_decimal_typed(Writer<mode> *writer,
+ const FormatSection &to_conv,
+ fputil::FPBits<T> float_bits) {
return convert_finite_float_typed<T>(writer, to_conv, float_bits,
ConversionType::F);
}
template <typename T, OverflowMode mode,
cpp::enable_if_t<cpp::is_floating_point_v<T>, int> = 0>
-LIBC_INLINE int convert_float_dec_exp_typed(Writer<mode> *writer,
- const FormatSection &to_conv,
- fputil::FPBits<T> float_bits) {
+LIBC_INLINE int
+convert_finite_float_dec_exp_typed(Writer<mode> *writer,
+ const FormatSection &to_conv,
+ fputil::FPBits<T> float_bits) {
return convert_finite_float_typed<T>(writer, to_conv, float_bits,
ConversionType::E);
}
template <typename T, OverflowMode mode,
cpp::enable_if_t<cpp::is_floating_point_v<T>, int> = 0>
-LIBC_INLINE int convert_float_dec_auto_typed(Writer<mode> *writer,
- const FormatSection &to_conv,
- fputil::FPBits<T> float_bits) {
+LIBC_INLINE int
+convert_finite_float_dec_auto_typed(Writer<mode> *writer,
+ const FormatSection &to_conv,
+ fputil::FPBits<T> float_bits) {
return convert_finite_float_typed<T>(writer, to_conv, float_bits,
ConversionType::G);
}
>From f2ad12bd29e637c8ecbda4de20e63f0b0ebe61c5 Mon Sep 17 00:00:00 2001
From: Alex Strelnikov <strel at google.com>
Date: Fri, 25 Sep 2026 14:32:38 +0000
Subject: [PATCH 3/4] Remove unnecessary header include
---
libc/src/stdlib/str_from_util.h | 1 -
1 file changed, 1 deletion(-)
diff --git a/libc/src/stdlib/str_from_util.h b/libc/src/stdlib/str_from_util.h
index 60468ec441d90..0c485a7cb315d 100644
--- a/libc/src/stdlib/str_from_util.h
+++ b/libc/src/stdlib/str_from_util.h
@@ -17,7 +17,6 @@
#ifndef LLVM_LIBC_SRC_STDLIB_STRFROM_UTIL_H
#define LLVM_LIBC_SRC_STDLIB_STRFROM_UTIL_H
-#include "include/llvm-libc-types/float128.h"
#include "src/__support/CPP/type_traits.h"
#include "src/__support/macros/config.h"
#include "src/__support/printf_core/converter_atlas.h"
>From 2c8b55267656ca3f9b8a9f16bae38ce77831fa4b Mon Sep 17 00:00:00 2001
From: Alex Strelnikov <strel at google.com>
Date: Fri, 25 Sep 2026 14:41:28 +0000
Subject: [PATCH 4/4] Value-init FormatSection to reduce brittleness of
uniitialized members.
---
libc/src/stdlib/str_from_util.h | 25 +++++--------------------
1 file changed, 5 insertions(+), 20 deletions(-)
diff --git a/libc/src/stdlib/str_from_util.h b/libc/src/stdlib/str_from_util.h
index 0c485a7cb315d..1b0d45d539537 100644
--- a/libc/src/stdlib/str_from_util.h
+++ b/libc/src/stdlib/str_from_util.h
@@ -35,15 +35,13 @@ using storage_type = typename fputil::FPBits<T>::StorageType;
template <typename T, printf_core::OverflowMode overflow_mode>
LIBC_INLINE int strfromfloat_convert(printf_core::Writer<overflow_mode> *writer,
const char *__restrict format, T fp) {
- printf_core::FormatSection section;
+ printf_core::FormatSection section = {};
size_t cur_pos = 0;
if (format[cur_pos] == '%') {
- section.has_conv = true;
++cur_pos;
// handle precision
- section.precision = -1;
if (format[cur_pos] == '.') {
++cur_pos;
section.precision = 0;
@@ -57,23 +55,10 @@ LIBC_INLINE int strfromfloat_convert(printf_core::Writer<overflow_mode> *writer,
}
}
- section.conv_name = format[cur_pos];
- switch (format[cur_pos]) {
- case 'a':
- case 'A':
- case 'e':
- case 'E':
- case 'f':
- case 'F':
- case 'g':
- case 'G':
- break;
- default:
- section.has_conv = false;
- break;
- }
- } else {
- section.has_conv = false;
+ char n = format[cur_pos];
+ section.conv_name = n;
+ section.has_conv = n == 'f' || n == 'F' || n == 'e' || n == 'E' ||
+ n == 'a' || n == 'A' || n == 'g' || n == 'G';
}
if (!section.has_conv)
More information about the libc-commits
mailing list