summaryrefslogtreecommitdiff
path: root/absl/strings/internal/str_format
diff options
context:
space:
mode:
Diffstat (limited to 'absl/strings/internal/str_format')
-rw-r--r--absl/strings/internal/str_format/arg.cc358
-rw-r--r--absl/strings/internal/str_format/arg.h200
-rw-r--r--absl/strings/internal/str_format/arg_test.cc15
-rw-r--r--absl/strings/internal/str_format/bind.cc2
-rw-r--r--absl/strings/internal/str_format/bind.h35
-rw-r--r--absl/strings/internal/str_format/checker.h21
-rw-r--r--absl/strings/internal/str_format/checker_test.cc14
-rw-r--r--absl/strings/internal/str_format/convert_test.cc671
-rw-r--r--absl/strings/internal/str_format/extension.cc38
-rw-r--r--absl/strings/internal/str_format/extension.h282
-rw-r--r--absl/strings/internal/str_format/extension_test.cc34
-rw-r--r--absl/strings/internal/str_format/float_conversion.cc1086
-rw-r--r--absl/strings/internal/str_format/float_conversion.h6
-rw-r--r--absl/strings/internal/str_format/output.h16
-rw-r--r--absl/strings/internal/str_format/output_test.cc8
-rw-r--r--absl/strings/internal/str_format/parser.cc14
-rw-r--r--absl/strings/internal/str_format/parser.h24
-rw-r--r--absl/strings/internal/str_format/parser_test.cc35
18 files changed, 2289 insertions, 570 deletions
diff --git a/absl/strings/internal/str_format/arg.cc b/absl/strings/internal/str_format/arg.cc
index 4d0604e0..9feb2248 100644
--- a/absl/strings/internal/str_format/arg.cc
+++ b/absl/strings/internal/str_format/arg.cc
@@ -12,14 +12,13 @@
#include "absl/base/port.h"
#include "absl/strings/internal/str_format/float_conversion.h"
+#include "absl/strings/numbers.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace str_format_internal {
namespace {
-const char kDigit[2][32] = { "0123456789abcdef", "0123456789ABCDEF" };
-
// Reduce *capacity by s.size(), clipped to a 0 minimum.
void ReducePadding(string_view s, size_t *capacity) {
*capacity = Excess(s.size(), *capacity);
@@ -48,125 +47,179 @@ struct IsSigned<absl::int128> : std::true_type {};
template <>
struct IsSigned<absl::uint128> : std::false_type {};
-class ConvertedIntInfo {
+// Integral digit printer.
+// Call one of the PrintAs* routines after construction once.
+// Use with_neg_and_zero/without_neg_or_zero/is_negative to access the results.
+class IntDigits {
public:
+ // Print the unsigned integer as octal.
+ // Supports unsigned integral types and uint128.
+ template <typename T>
+ void PrintAsOct(T v) {
+ static_assert(!IsSigned<T>::value, "");
+ char *p = storage_ + sizeof(storage_);
+ do {
+ *--p = static_cast<char>('0' + (static_cast<size_t>(v) & 7));
+ v >>= 3;
+ } while (v);
+ start_ = p;
+ size_ = storage_ + sizeof(storage_) - p;
+ }
+
+ // Print the signed or unsigned integer as decimal.
+ // Supports all integral types.
template <typename T>
- ConvertedIntInfo(T v, ConversionChar conv) {
- using Unsigned = typename MakeUnsigned<T>::type;
- auto u = static_cast<Unsigned>(v);
- if (IsNeg(v)) {
- is_neg_ = true;
- u = Unsigned{} - u;
- } else {
- is_neg_ = false;
+ void PrintAsDec(T v) {
+ static_assert(std::is_integral<T>::value, "");
+ start_ = storage_;
+ size_ = numbers_internal::FastIntToBuffer(v, storage_) - storage_;
+ }
+
+ void PrintAsDec(int128 v) {
+ auto u = static_cast<uint128>(v);
+ bool add_neg = false;
+ if (v < 0) {
+ add_neg = true;
+ u = uint128{} - u;
}
- UnsignedToStringRight(u, conv);
+ PrintAsDec(u, add_neg);
}
- string_view digits() const {
- return {end() - size_, static_cast<size_t>(size_)};
+ void PrintAsDec(uint128 v, bool add_neg = false) {
+ // This function can be sped up if needed. We can call FastIntToBuffer
+ // twice, or fix FastIntToBuffer to support uint128.
+ char *p = storage_ + sizeof(storage_);
+ do {
+ p -= 2;
+ numbers_internal::PutTwoDigits(static_cast<size_t>(v % 100), p);
+ v /= 100;
+ } while (v);
+ if (p[0] == '0') {
+ // We printed one too many hexits.
+ ++p;
+ }
+ if (add_neg) {
+ *--p = '-';
+ }
+ size_ = storage_ + sizeof(storage_) - p;
+ start_ = p;
}
- bool is_neg() const { return is_neg_; }
- private:
- template <typename T, bool IsSigned>
- struct IsNegImpl {
- static bool Eval(T v) { return v < 0; }
- };
+ // Print the unsigned integer as hex using lowercase.
+ // Supports unsigned integral types and uint128.
template <typename T>
- struct IsNegImpl<T, false> {
- static bool Eval(T) {
- return false;
+ void PrintAsHexLower(T v) {
+ static_assert(!IsSigned<T>::value, "");
+ char *p = storage_ + sizeof(storage_);
+
+ do {
+ p -= 2;
+ constexpr const char* table = numbers_internal::kHexTable;
+ std::memcpy(p, table + 2 * (static_cast<size_t>(v) & 0xFF), 2);
+ if (sizeof(T) == 1) break;
+ v >>= 8;
+ } while (v);
+ if (p[0] == '0') {
+ // We printed one too many digits.
+ ++p;
}
- };
+ start_ = p;
+ size_ = storage_ + sizeof(storage_) - p;
+ }
+ // Print the unsigned integer as hex using uppercase.
+ // Supports unsigned integral types and uint128.
template <typename T>
- bool IsNeg(T v) {
- return IsNegImpl<T, IsSigned<T>::value>::Eval(v);
+ void PrintAsHexUpper(T v) {
+ static_assert(!IsSigned<T>::value, "");
+ char *p = storage_ + sizeof(storage_);
+
+ // kHexTable is only lowercase, so do it manually for uppercase.
+ do {
+ *--p = "0123456789ABCDEF"[static_cast<size_t>(v) & 15];
+ v >>= 4;
+ } while (v);
+ start_ = p;
+ size_ = storage_ + sizeof(storage_) - p;
}
- template <typename T>
- void UnsignedToStringRight(T u, ConversionChar conv) {
- char *p = end();
- switch (FormatConversionCharRadix(conv)) {
- default:
- case 10:
- for (; u; u /= 10)
- *--p = static_cast<char>('0' + static_cast<size_t>(u % 10));
- break;
- case 8:
- for (; u; u /= 8)
- *--p = static_cast<char>('0' + static_cast<size_t>(u % 8));
- break;
- case 16: {
- const char *digits = kDigit[FormatConversionCharIsUpper(conv) ? 1 : 0];
- for (; u; u /= 16) *--p = digits[static_cast<size_t>(u % 16)];
- break;
- }
- }
- size_ = static_cast<int>(end() - p);
+ // The printed value including the '-' sign if available.
+ // For inputs of value `0`, this will return "0"
+ string_view with_neg_and_zero() const { return {start_, size_}; }
+
+ // The printed value not including the '-' sign.
+ // For inputs of value `0`, this will return "".
+ string_view without_neg_or_zero() const {
+ static_assert('-' < '0', "The check below verifies both.");
+ size_t advance = start_[0] <= '0' ? 1 : 0;
+ return {start_ + advance, size_ - advance};
}
- const char *end() const { return storage_ + sizeof(storage_); }
- char *end() { return storage_ + sizeof(storage_); }
+ bool is_negative() const { return start_[0] == '-'; }
- bool is_neg_;
- int size_;
- // Max size: 128 bit value as octal -> 43 digits
- char storage_[128 / 3 + 1];
+ private:
+ const char *start_;
+ size_t size_;
+ // Max size: 128 bit value as octal -> 43 digits, plus sign char
+ char storage_[128 / 3 + 1 + 1];
};
// Note: 'o' conversions do not have a base indicator, it's just that
// the '#' flag is specified to modify the precision for 'o' conversions.
-string_view BaseIndicator(const ConvertedIntInfo &info,
- const ConversionSpec conv) {
- bool alt = conv.flags().alt;
- int radix = FormatConversionCharRadix(conv.conv());
- if (conv.conv() == ConversionChar::p) alt = true; // always show 0x for %p.
+string_view BaseIndicator(const IntDigits &as_digits,
+ const FormatConversionSpecImpl conv) {
+ // always show 0x for %p.
+ bool alt = conv.has_alt_flag() ||
+ conv.conversion_char() == FormatConversionCharInternal::p;
+ bool hex = (conv.conversion_char() == FormatConversionCharInternal::x ||
+ conv.conversion_char() == FormatConversionCharInternal::X ||
+ conv.conversion_char() == FormatConversionCharInternal::p);
// From the POSIX description of '#' flag:
// "For x or X conversion specifiers, a non-zero result shall have
// 0x (or 0X) prefixed to it."
- if (alt && radix == 16 && !info.digits().empty()) {
- if (FormatConversionCharIsUpper(conv.conv())) return "0X";
- return "0x";
+ if (alt && hex && !as_digits.without_neg_or_zero().empty()) {
+ return conv.conversion_char() == FormatConversionCharInternal::X ? "0X"
+ : "0x";
}
return {};
}
-string_view SignColumn(bool neg, const ConversionSpec conv) {
- if (FormatConversionCharIsSigned(conv.conv())) {
+string_view SignColumn(bool neg, const FormatConversionSpecImpl conv) {
+ if (conv.conversion_char() == FormatConversionCharInternal::d ||
+ conv.conversion_char() == FormatConversionCharInternal::i) {
if (neg) return "-";
- if (conv.flags().show_pos) return "+";
- if (conv.flags().sign_col) return " ";
+ if (conv.has_show_pos_flag()) return "+";
+ if (conv.has_sign_col_flag()) return " ";
}
return {};
}
-bool ConvertCharImpl(unsigned char v, const ConversionSpec conv,
+bool ConvertCharImpl(unsigned char v, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
size_t fill = 0;
if (conv.width() >= 0) fill = conv.width();
ReducePadding(1, &fill);
- if (!conv.flags().left) sink->Append(fill, ' ');
+ if (!conv.has_left_flag()) sink->Append(fill, ' ');
sink->Append(1, v);
- if (conv.flags().left) sink->Append(fill, ' ');
+ if (conv.has_left_flag()) sink->Append(fill, ' ');
return true;
}
-bool ConvertIntImplInner(const ConvertedIntInfo &info,
- const ConversionSpec conv, FormatSinkImpl *sink) {
+bool ConvertIntImplInnerSlow(const IntDigits &as_digits,
+ const FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink) {
// Print as a sequence of Substrings:
// [left_spaces][sign][base_indicator][zeroes][formatted][right_spaces]
size_t fill = 0;
if (conv.width() >= 0) fill = conv.width();
- string_view formatted = info.digits();
+ string_view formatted = as_digits.without_neg_or_zero();
ReducePadding(formatted, &fill);
- string_view sign = SignColumn(info.is_neg(), conv);
+ string_view sign = SignColumn(as_digits.is_negative(), conv);
ReducePadding(sign, &fill);
- string_view base_indicator = BaseIndicator(info, conv);
+ string_view base_indicator = BaseIndicator(as_digits, conv);
ReducePadding(base_indicator, &fill);
int precision = conv.precision();
@@ -174,7 +227,8 @@ bool ConvertIntImplInner(const ConvertedIntInfo &info,
if (!precision_specified)
precision = 1;
- if (conv.flags().alt && conv.conv() == ConversionChar::o) {
+ if (conv.has_alt_flag() &&
+ conv.conversion_char() == FormatConversionCharInternal::o) {
// From POSIX description of the '#' (alt) flag:
// "For o conversion, it increases the precision (if necessary) to
// force the first digit of the result to be zero."
@@ -187,13 +241,13 @@ bool ConvertIntImplInner(const ConvertedIntInfo &info,
size_t num_zeroes = Excess(formatted.size(), precision);
ReducePadding(num_zeroes, &fill);
- size_t num_left_spaces = !conv.flags().left ? fill : 0;
- size_t num_right_spaces = conv.flags().left ? fill : 0;
+ size_t num_left_spaces = !conv.has_left_flag() ? fill : 0;
+ size_t num_right_spaces = conv.has_left_flag() ? fill : 0;
// From POSIX description of the '0' (zero) flag:
// "For d, i, o, u, x, and X conversion specifiers, if a precision
// is specified, the '0' flag is ignored."
- if (!precision_specified && conv.flags().zero) {
+ if (!precision_specified && conv.has_zero_flag()) {
num_zeroes += num_left_spaces;
num_left_spaces = 0;
}
@@ -208,71 +262,97 @@ bool ConvertIntImplInner(const ConvertedIntInfo &info,
}
template <typename T>
-bool ConvertIntImplInner(T v, const ConversionSpec conv, FormatSinkImpl *sink) {
- ConvertedIntInfo info(v, conv.conv());
- if (conv.flags().basic && (conv.conv() != ConversionChar::p)) {
- if (info.is_neg()) sink->Append(1, '-');
- if (info.digits().empty()) {
- sink->Append(1, '0');
- } else {
- sink->Append(info.digits());
- }
- return true;
+bool ConvertIntArg(T v, const FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink) {
+ using U = typename MakeUnsigned<T>::type;
+ IntDigits as_digits;
+
+ // This odd casting is due to a bug in -Wswitch behavior in gcc49 which causes
+ // it to complain about a switch/case type mismatch, even though both are
+ // FormatConverionChar. Likely this is because at this point
+ // FormatConversionChar is declared, but not defined.
+ switch (static_cast<uint8_t>(conv.conversion_char())) {
+ case static_cast<uint8_t>(FormatConversionCharInternal::c):
+ return ConvertCharImpl(static_cast<unsigned char>(v), conv, sink);
+
+ case static_cast<uint8_t>(FormatConversionCharInternal::o):
+ as_digits.PrintAsOct(static_cast<U>(v));
+ break;
+
+ case static_cast<uint8_t>(FormatConversionCharInternal::x):
+ as_digits.PrintAsHexLower(static_cast<U>(v));
+ break;
+ case static_cast<uint8_t>(FormatConversionCharInternal::X):
+ as_digits.PrintAsHexUpper(static_cast<U>(v));
+ break;
+
+ case static_cast<uint8_t>(FormatConversionCharInternal::u):
+ as_digits.PrintAsDec(static_cast<U>(v));
+ break;
+
+ case static_cast<uint8_t>(FormatConversionCharInternal::d):
+ case static_cast<uint8_t>(FormatConversionCharInternal::i):
+ as_digits.PrintAsDec(v);
+ break;
+
+ case static_cast<uint8_t>(FormatConversionCharInternal::a):
+ case static_cast<uint8_t>(FormatConversionCharInternal::e):
+ case static_cast<uint8_t>(FormatConversionCharInternal::f):
+ case static_cast<uint8_t>(FormatConversionCharInternal::g):
+ case static_cast<uint8_t>(FormatConversionCharInternal::A):
+ case static_cast<uint8_t>(FormatConversionCharInternal::E):
+ case static_cast<uint8_t>(FormatConversionCharInternal::F):
+ case static_cast<uint8_t>(FormatConversionCharInternal::G):
+ return ConvertFloatImpl(static_cast<double>(v), conv, sink);
+
+ default:
+ ABSL_INTERNAL_ASSUME(false);
}
- return ConvertIntImplInner(info, conv, sink);
-}
-template <typename T>
-bool ConvertIntArg(T v, const ConversionSpec conv, FormatSinkImpl *sink) {
- if (FormatConversionCharIsFloat(conv.conv())) {
- return FormatConvertImpl(static_cast<double>(v), conv, sink).value;
- }
- if (conv.conv() == ConversionChar::c)
- return ConvertCharImpl(static_cast<unsigned char>(v), conv, sink);
- if (!FormatConversionCharIsIntegral(conv.conv())) return false;
- if (!FormatConversionCharIsSigned(conv.conv()) && IsSigned<T>::value) {
- using U = typename MakeUnsigned<T>::type;
- return FormatConvertImpl(static_cast<U>(v), conv, sink).value;
+ if (conv.is_basic()) {
+ sink->Append(as_digits.with_neg_and_zero());
+ return true;
}
- return ConvertIntImplInner(v, conv, sink);
+ return ConvertIntImplInnerSlow(as_digits, conv, sink);
}
template <typename T>
-bool ConvertFloatArg(T v, const ConversionSpec conv, FormatSinkImpl *sink) {
- return FormatConversionCharIsFloat(conv.conv()) &&
+bool ConvertFloatArg(T v, const FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink) {
+ return FormatConversionCharIsFloat(conv.conversion_char()) &&
ConvertFloatImpl(v, conv, sink);
}
-inline bool ConvertStringArg(string_view v, const ConversionSpec conv,
+inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
- if (conv.conv() != ConversionChar::s) return false;
- if (conv.flags().basic) {
+ if (conv.is_basic()) {
sink->Append(v);
return true;
}
return sink->PutPaddedString(v, conv.width(), conv.precision(),
- conv.flags().left);
+ conv.has_left_flag());
}
} // namespace
// ==================== Strings ====================
-ConvertResult<Conv::s> FormatConvertImpl(const std::string &v,
- const ConversionSpec conv,
- FormatSinkImpl *sink) {
+StringConvertResult FormatConvertImpl(const std::string &v,
+ const FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink) {
return {ConvertStringArg(v, conv, sink)};
}
-ConvertResult<Conv::s> FormatConvertImpl(string_view v,
- const ConversionSpec conv,
- FormatSinkImpl *sink) {
+StringConvertResult FormatConvertImpl(string_view v,
+ const FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink) {
return {ConvertStringArg(v, conv, sink)};
}
-ConvertResult<Conv::s | Conv::p> FormatConvertImpl(const char *v,
- const ConversionSpec conv,
- FormatSinkImpl *sink) {
- if (conv.conv() == ConversionChar::p)
+ArgConvertResult<FormatConversionCharSetUnion(
+ FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::p)>
+FormatConvertImpl(const char *v, const FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink) {
+ if (conv.conversion_char() == FormatConversionCharInternal::p)
return {FormatConvertImpl(VoidPtr(v), conv, sink).value};
size_t len;
if (v == nullptr) {
@@ -287,93 +367,99 @@ ConvertResult<Conv::s | Conv::p> FormatConvertImpl(const char *v,
}
// ==================== Raw pointers ====================
-ConvertResult<Conv::p> FormatConvertImpl(VoidPtr v, const ConversionSpec conv,
- FormatSinkImpl *sink) {
- if (conv.conv() != ConversionChar::p) return {false};
+ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl(
+ VoidPtr v, const FormatConversionSpecImpl conv, FormatSinkImpl *sink) {
if (!v.value) {
sink->Append("(nil)");
return {true};
}
- return {ConvertIntImplInner(v.value, conv, sink)};
+ IntDigits as_digits;
+ as_digits.PrintAsHexLower(v.value);
+ return {ConvertIntImplInnerSlow(as_digits, conv, sink)};
}
// ==================== Floats ====================
-FloatingConvertResult FormatConvertImpl(float v, const ConversionSpec conv,
+FloatingConvertResult FormatConvertImpl(float v,
+ const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return {ConvertFloatArg(v, conv, sink)};
}
-FloatingConvertResult FormatConvertImpl(double v, const ConversionSpec conv,
+FloatingConvertResult FormatConvertImpl(double v,
+ const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return {ConvertFloatArg(v, conv, sink)};
}
FloatingConvertResult FormatConvertImpl(long double v,
- const ConversionSpec conv,
+ const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return {ConvertFloatArg(v, conv, sink)};
}
// ==================== Chars ====================
-IntegralConvertResult FormatConvertImpl(char v, const ConversionSpec conv,
+IntegralConvertResult FormatConvertImpl(char v,
+ const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(signed char v,
- const ConversionSpec conv,
+ const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(unsigned char v,
- const ConversionSpec conv,
+ const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)};
}
// ==================== Ints ====================
IntegralConvertResult FormatConvertImpl(short v, // NOLINT
- const ConversionSpec conv,
+ const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(unsigned short v, // NOLINT
- const ConversionSpec conv,
+ const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)};
}
-IntegralConvertResult FormatConvertImpl(int v, const ConversionSpec conv,
+IntegralConvertResult FormatConvertImpl(int v,
+ const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)};
}
-IntegralConvertResult FormatConvertImpl(unsigned v, const ConversionSpec conv,
+IntegralConvertResult FormatConvertImpl(unsigned v,
+ const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(long v, // NOLINT
- const ConversionSpec conv,
+ const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(unsigned long v, // NOLINT
- const ConversionSpec conv,
+ const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(long long v, // NOLINT
- const ConversionSpec conv,
+ const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(unsigned long long v, // NOLINT
- const ConversionSpec conv,
+ const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(absl::int128 v,
- const ConversionSpec conv,
+ const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)};
}
IntegralConvertResult FormatConvertImpl(absl::uint128 v,
- const ConversionSpec conv,
+ const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)};
}
diff --git a/absl/strings/internal/str_format/arg.h b/absl/strings/internal/str_format/arg.h
index 7a937563..3dbc1526 100644
--- a/absl/strings/internal/str_format/arg.h
+++ b/absl/strings/internal/str_format/arg.h
@@ -25,16 +25,37 @@ class Cord;
class FormatCountCapture;
class FormatSink;
+template <absl::FormatConversionCharSet C>
+struct FormatConvertResult;
+class FormatConversionSpec;
+
namespace str_format_internal {
template <typename T, typename = void>
struct HasUserDefinedConvert : std::false_type {};
template <typename T>
-struct HasUserDefinedConvert<
- T, void_t<decltype(AbslFormatConvert(
- std::declval<const T&>(), std::declval<ConversionSpec>(),
- std::declval<FormatSink*>()))>> : std::true_type {};
+struct HasUserDefinedConvert<T, void_t<decltype(AbslFormatConvert(
+ std::declval<const T&>(),
+ std::declval<const FormatConversionSpec&>(),
+ std::declval<FormatSink*>()))>>
+ : std::true_type {};
+
+void AbslFormatConvert(); // Stops the lexical name lookup
+template <typename T>
+auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink)
+ -> decltype(AbslFormatConvert(v,
+ std::declval<const FormatConversionSpec&>(),
+ std::declval<FormatSink*>())) {
+ using FormatConversionSpecT =
+ absl::enable_if_t<sizeof(const T& (*)()) != 0, FormatConversionSpec>;
+ using FormatSinkT =
+ absl::enable_if_t<sizeof(const T& (*)()) != 0, FormatSink>;
+ auto fcs = conv.Wrap<FormatConversionSpecT>();
+ auto fs = sink->Wrap<FormatSinkT>();
+ return AbslFormatConvert(v, fcs, &fs);
+}
template <typename T>
class StreamedWrapper;
@@ -43,6 +64,13 @@ class StreamedWrapper;
// then convert it, appending to `sink` and return `true`.
// Otherwise fail and return `false`.
+// AbslFormatConvert(v, conv, sink) is intended to be found by ADL on 'v'
+// as an extension mechanism. These FormatConvertImpl functions are the default
+// implementations.
+// The ADL search is augmented via the 'Sink*' parameter, which also
+// serves as a disambiguator to reject possible unintended 'AbslFormatConvert'
+// functions in the namespaces associated with 'v'.
+
// Raw pointers.
struct VoidPtr {
VoidPtr() = default;
@@ -52,27 +80,45 @@ struct VoidPtr {
: value(ptr ? reinterpret_cast<uintptr_t>(ptr) : 0) {}
uintptr_t value;
};
-ConvertResult<Conv::p> FormatConvertImpl(VoidPtr v, ConversionSpec conv,
- FormatSinkImpl* sink);
+
+template <FormatConversionCharSet C>
+struct ArgConvertResult {
+ bool value;
+};
+
+template <FormatConversionCharSet C>
+constexpr FormatConversionCharSet ExtractCharSet(FormatConvertResult<C>) {
+ return C;
+}
+
+template <FormatConversionCharSet C>
+constexpr FormatConversionCharSet ExtractCharSet(ArgConvertResult<C>) {
+ return C;
+}
+
+using StringConvertResult =
+ ArgConvertResult<FormatConversionCharSetInternal::s>;
+ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl(
+ VoidPtr v, FormatConversionSpecImpl conv, FormatSinkImpl* sink);
// Strings.
-ConvertResult<Conv::s> FormatConvertImpl(const std::string& v,
- ConversionSpec conv,
- FormatSinkImpl* sink);
-ConvertResult<Conv::s> FormatConvertImpl(string_view v, ConversionSpec conv,
- FormatSinkImpl* sink);
-ConvertResult<Conv::s | Conv::p> FormatConvertImpl(const char* v,
- ConversionSpec conv,
- FormatSinkImpl* sink);
-template <class AbslCord,
- typename std::enable_if<
- std::is_same<AbslCord, absl::Cord>::value>::type* = nullptr>
-ConvertResult<Conv::s> FormatConvertImpl(const AbslCord& value,
- ConversionSpec conv,
- FormatSinkImpl* sink) {
- if (conv.conv() != ConversionChar::s) return {false};
-
- bool is_left = conv.flags().left;
+StringConvertResult FormatConvertImpl(const std::string& v,
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
+StringConvertResult FormatConvertImpl(string_view v,
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
+ArgConvertResult<FormatConversionCharSetUnion(
+ FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::p)>
+FormatConvertImpl(const char* v, const FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
+
+template <class AbslCord, typename std::enable_if<std::is_same<
+ AbslCord, absl::Cord>::value>::type* = nullptr>
+StringConvertResult FormatConvertImpl(const AbslCord& value,
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink) {
+ bool is_left = conv.has_left_flag();
size_t space_remaining = 0;
int width = conv.width();
@@ -105,55 +151,63 @@ ConvertResult<Conv::s> FormatConvertImpl(const AbslCord& value,
return {true};
}
-using IntegralConvertResult =
- ConvertResult<Conv::c | Conv::numeric | Conv::star>;
-using FloatingConvertResult = ConvertResult<Conv::floating>;
+using IntegralConvertResult = ArgConvertResult<FormatConversionCharSetUnion(
+ FormatConversionCharSetInternal::c,
+ FormatConversionCharSetInternal::kNumeric,
+ FormatConversionCharSetInternal::kStar)>;
+using FloatingConvertResult =
+ ArgConvertResult<FormatConversionCharSetInternal::kFloating>;
// Floats.
-FloatingConvertResult FormatConvertImpl(float v, ConversionSpec conv,
+FloatingConvertResult FormatConvertImpl(float v, FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
-FloatingConvertResult FormatConvertImpl(double v, ConversionSpec conv,
+FloatingConvertResult FormatConvertImpl(double v, FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
-FloatingConvertResult FormatConvertImpl(long double v, ConversionSpec conv,
+FloatingConvertResult FormatConvertImpl(long double v,
+ FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
// Chars.
-IntegralConvertResult FormatConvertImpl(char v, ConversionSpec conv,
+IntegralConvertResult FormatConvertImpl(char v, FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
-IntegralConvertResult FormatConvertImpl(signed char v, ConversionSpec conv,
+IntegralConvertResult FormatConvertImpl(signed char v,
+ FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
-IntegralConvertResult FormatConvertImpl(unsigned char v, ConversionSpec conv,
+IntegralConvertResult FormatConvertImpl(unsigned char v,
+ FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
// Ints.
IntegralConvertResult FormatConvertImpl(short v, // NOLINT
- ConversionSpec conv,
+ FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
IntegralConvertResult FormatConvertImpl(unsigned short v, // NOLINT
- ConversionSpec conv,
+ FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
-IntegralConvertResult FormatConvertImpl(int v, ConversionSpec conv,
+IntegralConvertResult FormatConvertImpl(int v, FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
-IntegralConvertResult FormatConvertImpl(unsigned v, ConversionSpec conv,
+IntegralConvertResult FormatConvertImpl(unsigned v,
+ FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
IntegralConvertResult FormatConvertImpl(long v, // NOLINT
- ConversionSpec conv,
+ FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
IntegralConvertResult FormatConvertImpl(unsigned long v, // NOLINT
- ConversionSpec conv,
+ FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
IntegralConvertResult FormatConvertImpl(long long v, // NOLINT
- ConversionSpec conv,
+ FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
IntegralConvertResult FormatConvertImpl(unsigned long long v, // NOLINT
- ConversionSpec conv,
+ FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
-IntegralConvertResult FormatConvertImpl(int128 v, ConversionSpec conv,
+IntegralConvertResult FormatConvertImpl(int128 v, FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
-IntegralConvertResult FormatConvertImpl(uint128 v, ConversionSpec conv,
+IntegralConvertResult FormatConvertImpl(uint128 v,
+ FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
template <typename T, enable_if_t<std::is_same<T, bool>::value, int> = 0>
-IntegralConvertResult FormatConvertImpl(T v, ConversionSpec conv,
+IntegralConvertResult FormatConvertImpl(T v, FormatConversionSpecImpl conv,
FormatSinkImpl* sink) {
return FormatConvertImpl(static_cast<int>(v), conv, sink);
}
@@ -164,12 +218,12 @@ template <typename T>
typename std::enable_if<std::is_enum<T>::value &&
!HasUserDefinedConvert<T>::value,
IntegralConvertResult>::type
-FormatConvertImpl(T v, ConversionSpec conv, FormatSinkImpl* sink);
+FormatConvertImpl(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink);
template <typename T>
-ConvertResult<Conv::s> FormatConvertImpl(const StreamedWrapper<T>& v,
- ConversionSpec conv,
- FormatSinkImpl* out) {
+StringConvertResult FormatConvertImpl(const StreamedWrapper<T>& v,
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl* out) {
std::ostringstream oss;
oss << v.v_;
if (!oss) return {false};
@@ -180,21 +234,24 @@ ConvertResult<Conv::s> FormatConvertImpl(const StreamedWrapper<T>& v,
// until after FormatCountCapture is fully defined.
struct FormatCountCaptureHelper {
template <class T = int>
- static ConvertResult<Conv::n> ConvertHelper(const FormatCountCapture& v,
- ConversionSpec conv,
- FormatSinkImpl* sink) {
+ static ArgConvertResult<FormatConversionCharSetInternal::n> ConvertHelper(
+ const FormatCountCapture& v, FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink) {
const absl::enable_if_t<sizeof(T) != 0, FormatCountCapture>& v2 = v;
- if (conv.conv() != str_format_internal::ConversionChar::n) return {false};
+ if (conv.conversion_char() !=
+ str_format_internal::FormatConversionCharInternal::n) {
+ return {false};
+ }
*v2.p_ = static_cast<int>(sink->size());
return {true};
}
};
template <class T = int>
-ConvertResult<Conv::n> FormatConvertImpl(const FormatCountCapture& v,
- ConversionSpec conv,
- FormatSinkImpl* sink) {
+ArgConvertResult<FormatConversionCharSetInternal::n> FormatConvertImpl(
+ const FormatCountCapture& v, FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink) {
return FormatCountCaptureHelper::ConvertHelper(v, conv, sink);
}
@@ -203,13 +260,13 @@ ConvertResult<Conv::n> FormatConvertImpl(const FormatCountCapture& v,
struct FormatArgImplFriend {
template <typename Arg>
static bool ToInt(Arg arg, int* out) {
- // A value initialized ConversionSpec has a `none` conv, which tells the
- // dispatcher to run the `int` conversion.
+ // A value initialized FormatConversionSpecImpl has a `none` conv, which
+ // tells the dispatcher to run the `int` conversion.
return arg.dispatcher_(arg.data_, {}, out);
}
template <typename Arg>
- static bool Convert(Arg arg, str_format_internal::ConversionSpec conv,
+ static bool Convert(Arg arg, FormatConversionSpecImpl conv,
FormatSinkImpl* out) {
return arg.dispatcher_(arg.data_, conv, out);
}
@@ -220,6 +277,15 @@ struct FormatArgImplFriend {
}
};
+template <typename Arg>
+constexpr FormatConversionCharSet ArgumentToConv() {
+ return absl::str_format_internal::ExtractCharSet(
+ decltype(str_format_internal::FormatConvertImpl(
+ std::declval<const Arg&>(),
+ std::declval<const FormatConversionSpecImpl&>(),
+ std::declval<FormatSinkImpl*>())){});
+}
+
// A type-erased handle to a format argument.
class FormatArgImpl {
private:
@@ -233,7 +299,7 @@ class FormatArgImpl {
char buf[kInlinedSpace];
};
- using Dispatcher = bool (*)(Data, ConversionSpec, void* out);
+ using Dispatcher = bool (*)(Data, FormatConversionSpecImpl, void* out);
template <typename T>
struct store_by_value
@@ -375,15 +441,20 @@ class FormatArgImpl {
}
template <typename T>
- static bool Dispatch(Data arg, ConversionSpec spec, void* out) {
+ static bool Dispatch(Data arg, FormatConversionSpecImpl spec, void* out) {
// A `none` conv indicates that we want the `int` conversion.
- if (ABSL_PREDICT_FALSE(spec.conv() == ConversionChar::none)) {
+ if (ABSL_PREDICT_FALSE(spec.conversion_char() ==
+ FormatConversionCharInternal::kNone)) {
return ToInt<T>(arg, static_cast<int*>(out), std::is_integral<T>(),
std::is_enum<T>());
}
-
+ if (ABSL_PREDICT_FALSE(!Contains(ArgumentToConv<T>(),
+ spec.conversion_char()))) {
+ return false;
+ }
return str_format_internal::FormatConvertImpl(
- Manager<T>::Value(arg), spec, static_cast<FormatSinkImpl*>(out))
+ Manager<T>::Value(arg), spec,
+ static_cast<FormatSinkImpl*>(out))
.value;
}
@@ -391,8 +462,9 @@ class FormatArgImpl {
Dispatcher dispatcher_;
};
-#define ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(T, E) \
- E template bool FormatArgImpl::Dispatch<T>(Data, ConversionSpec, void*)
+#define ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(T, E) \
+ E template bool FormatArgImpl::Dispatch<T>(Data, FormatConversionSpecImpl, \
+ void*)
#define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(...) \
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(str_format_internal::VoidPtr, \
diff --git a/absl/strings/internal/str_format/arg_test.cc b/absl/strings/internal/str_format/arg_test.cc
index 8d30d8b8..f53fd6bd 100644
--- a/absl/strings/internal/str_format/arg_test.cc
+++ b/absl/strings/internal/str_format/arg_test.cc
@@ -23,8 +23,17 @@ class FormatArgImplTest : public ::testing::Test {
enum Color { kRed, kGreen, kBlue };
static const char *hi() { return "hi"; }
+
+ struct X {};
+
+ X x_;
};
+inline FormatConvertResult<FormatConversionCharSet{}> AbslFormatConvert(
+ const FormatArgImplTest::X &, const FormatConversionSpec &, FormatSink *) {
+ return {false};
+}
+
TEST_F(FormatArgImplTest, ToInt) {
int out = 0;
EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(1), &out));
@@ -59,6 +68,7 @@ TEST_F(FormatArgImplTest, ToInt) {
FormatArgImpl(static_cast<int *>(nullptr)), &out));
EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl(hi()), &out));
EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl("hi"), &out));
+ EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl(x_), &out));
EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(kBlue), &out));
EXPECT_EQ(2, out);
}
@@ -95,8 +105,9 @@ TEST_F(FormatArgImplTest, OtherPtrDecayToVoidPtr) {
TEST_F(FormatArgImplTest, WorksWithCharArraysOfUnknownSize) {
std::string s;
FormatSinkImpl sink(&s);
- ConversionSpec conv;
- FormatConversionSpecImplFriend::SetConversionChar(ConversionChar::s, &conv);
+ FormatConversionSpecImpl conv;
+ FormatConversionSpecImplFriend::SetConversionChar(
+ FormatConversionCharInternal::s, &conv);
FormatConversionSpecImplFriend::SetFlags(Flags(), &conv);
FormatConversionSpecImplFriend::SetWidth(-1, &conv);
FormatConversionSpecImplFriend::SetPrecision(-1, &conv);
diff --git a/absl/strings/internal/str_format/bind.cc b/absl/strings/internal/str_format/bind.cc
index 27522fdb..6980ed1d 100644
--- a/absl/strings/internal/str_format/bind.cc
+++ b/absl/strings/internal/str_format/bind.cc
@@ -147,7 +147,7 @@ class SummarizingConverter {
<< FormatConversionSpecImplFriend::FlagsToString(bound);
if (bound.width() >= 0) ss << bound.width();
if (bound.precision() >= 0) ss << "." << bound.precision();
- ss << bound.conv() << "}";
+ ss << bound.conversion_char() << "}";
Append(ss.str());
return true;
}
diff --git a/absl/strings/internal/str_format/bind.h b/absl/strings/internal/str_format/bind.h
index cf41b197..585246e7 100644
--- a/absl/strings/internal/str_format/bind.h
+++ b/absl/strings/internal/str_format/bind.h
@@ -19,7 +19,7 @@ class UntypedFormatSpec;
namespace str_format_internal {
-class BoundConversion : public ConversionSpec {
+class BoundConversion : public FormatConversionSpecImpl {
public:
const FormatArgImpl* arg() const { return arg_; }
void set_arg(const FormatArgImpl* a) { arg_ = a; }
@@ -60,7 +60,7 @@ class UntypedFormatSpecImpl {
size_t size_;
};
-template <typename T, typename...>
+template <typename T, FormatConversionCharSet...>
struct MakeDependent {
using type = T;
};
@@ -68,7 +68,7 @@ struct MakeDependent {
// Implicitly convertible from `const char*`, `string_view`, and the
// `ExtendedParsedFormat` type. This abstraction allows all format functions to
// operate on any without providing too many overloads.
-template <typename... Args>
+template <FormatConversionCharSet... Args>
class FormatSpecTemplate
: public MakeDependent<UntypedFormatSpec, Args...>::type {
using Base = typename MakeDependent<UntypedFormatSpec, Args...>::type;
@@ -76,11 +76,11 @@ class FormatSpecTemplate
public:
#ifdef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
- // Honeypot overload for when the std::string is not constexpr.
+ // Honeypot overload for when the string is not constexpr.
// We use the 'unavailable' attribute to give a better compiler error than
// just 'method is deleted'.
FormatSpecTemplate(...) // NOLINT
- __attribute__((unavailable("Format std::string is not constexpr.")));
+ __attribute__((unavailable("Format string is not constexpr.")));
// Honeypot overload for when the format is constexpr and invalid.
// We use the 'unavailable' attribute to give a better compiler error than
@@ -105,13 +105,11 @@ class FormatSpecTemplate
// Good format overload.
FormatSpecTemplate(const char* s) // NOLINT
- __attribute__((enable_if(ValidFormatImpl<ArgumentToConv<Args>()...>(s),
- "bad format trap")))
+ __attribute__((enable_if(ValidFormatImpl<Args...>(s), "bad format trap")))
: Base(s) {}
FormatSpecTemplate(string_view s) // NOLINT
- __attribute__((enable_if(ValidFormatImpl<ArgumentToConv<Args>()...>(s),
- "bad format trap")))
+ __attribute__((enable_if(ValidFormatImpl<Args...>(s), "bad format trap")))
: Base(s) {}
#else // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
@@ -121,19 +119,14 @@ class FormatSpecTemplate
#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
- template <Conv... C, typename = typename std::enable_if<
- AllOf(sizeof...(C) == sizeof...(Args),
- Contains(ArgumentToConv<Args>(),
- C)...)>::type>
+ template <FormatConversionCharSet... C,
+ typename = typename std::enable_if<
+ AllOf(sizeof...(C) == sizeof...(Args), Contains(Args,
+ C)...)>::type>
FormatSpecTemplate(const ExtendedParsedFormat<C...>& pc) // NOLINT
: Base(&pc) {}
};
-template <typename... Args>
-struct FormatSpecDeductionBarrier {
- using type = FormatSpecTemplate<Args...>;
-};
-
class Streamable {
public:
Streamable(const UntypedFormatSpecImpl& format,
@@ -196,9 +189,9 @@ class StreamedWrapper {
private:
template <typename S>
- friend ConvertResult<Conv::s> FormatConvertImpl(const StreamedWrapper<S>& v,
- ConversionSpec conv,
- FormatSinkImpl* out);
+ friend ArgConvertResult<FormatConversionCharSetInternal::s> FormatConvertImpl(
+ const StreamedWrapper<S>& v, FormatConversionSpecImpl conv,
+ FormatSinkImpl* out);
const T& v_;
};
diff --git a/absl/strings/internal/str_format/checker.h b/absl/strings/internal/str_format/checker.h
index 8993a79b..424c51f7 100644
--- a/absl/strings/internal/str_format/checker.h
+++ b/absl/strings/internal/str_format/checker.h
@@ -24,13 +24,6 @@ constexpr bool AllOf(bool b, T... t) {
return b && AllOf(t...);
}
-template <typename Arg>
-constexpr Conv ArgumentToConv() {
- return decltype(str_format_internal::FormatConvertImpl(
- std::declval<const Arg&>(), std::declval<const ConversionSpec&>(),
- std::declval<FormatSinkImpl*>()))::kConv;
-}
-
#ifdef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
constexpr bool ContainsChar(const char* chars, char c) {
@@ -39,14 +32,14 @@ constexpr bool ContainsChar(const char* chars, char c) {
// A constexpr compatible list of Convs.
struct ConvList {
- const Conv* array;
+ const FormatConversionCharSet* array;
int count;
// We do the bound check here to avoid having to do it on the callers.
- // Returning an empty Conv has the same effect as short circuiting because it
- // will never match any conversion.
- constexpr Conv operator[](int i) const {
- return i < count ? array[i] : Conv{};
+ // Returning an empty FormatConversionCharSet has the same effect as
+ // short circuiting because it will never match any conversion.
+ constexpr FormatConversionCharSet operator[](int i) const {
+ return i < count ? array[i] : FormatConversionCharSet{};
}
constexpr ConvList without_front() const {
@@ -57,7 +50,7 @@ struct ConvList {
template <size_t count>
struct ConvListT {
// Make sure the array has size > 0.
- Conv list[count ? count : 1];
+ FormatConversionCharSet list[count ? count : 1];
};
constexpr char GetChar(string_view str, size_t index) {
@@ -310,7 +303,7 @@ class FormatParser {
ConvList args_;
};
-template <Conv... C>
+template <FormatConversionCharSet... C>
constexpr bool ValidFormatImpl(string_view format) {
return FormatParser(format,
{ConvListT<sizeof...(C)>{{C...}}.list, sizeof...(C)})
diff --git a/absl/strings/internal/str_format/checker_test.cc b/absl/strings/internal/str_format/checker_test.cc
index ea2a7681..a76d70b0 100644
--- a/absl/strings/internal/str_format/checker_test.cc
+++ b/absl/strings/internal/str_format/checker_test.cc
@@ -9,18 +9,22 @@ ABSL_NAMESPACE_BEGIN
namespace str_format_internal {
namespace {
-std::string ConvToString(Conv conv) {
+std::string ConvToString(FormatConversionCharSet conv) {
std::string out;
-#define CONV_SET_CASE(c) \
- if (Contains(conv, Conv::c)) out += #c;
+#define CONV_SET_CASE(c) \
+ if (Contains(conv, FormatConversionCharSetInternal::c)) { \
+ out += #c; \
+ }
ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(CONV_SET_CASE, )
#undef CONV_SET_CASE
- if (Contains(conv, Conv::star)) out += "*";
+ if (Contains(conv, FormatConversionCharSetInternal::kStar)) {
+ out += "*";
+ }
return out;
}
TEST(StrFormatChecker, ArgumentToConv) {
- Conv conv = ArgumentToConv<std::string>();
+ FormatConversionCharSet conv = ArgumentToConv<std::string>();
EXPECT_EQ(ConvToString(conv), "s");
conv = ArgumentToConv<const char*>();
diff --git a/absl/strings/internal/str_format/convert_test.cc b/absl/strings/internal/str_format/convert_test.cc
index cbcd7caf..634ee78b 100644
--- a/absl/strings/internal/str_format/convert_test.cc
+++ b/absl/strings/internal/str_format/convert_test.cc
@@ -1,20 +1,32 @@
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
+
#include <cctype>
#include <cmath>
+#include <limits>
#include <string>
+#include <thread> // NOLINT
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/strings/internal/str_format/bind.h"
+#include "absl/strings/match.h"
+#include "absl/types/optional.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace str_format_internal {
namespace {
+struct NativePrintfTraits {
+ bool hex_float_has_glibc_rounding;
+ bool hex_float_prefers_denormal_repr;
+ bool hex_float_uses_minimal_precision_when_not_specified;
+ bool hex_float_optimizes_leading_digit_bit_count;
+};
+
template <typename T, size_t N>
size_t ArraySize(T (&)[N]) {
return N;
@@ -57,7 +69,7 @@ std::string Esc(const T &v) {
return oss.str();
}
-void StrAppend(std::string *dst, const char *format, va_list ap) {
+void StrAppendV(std::string *dst, const char *format, va_list ap) {
// First try with a small fixed size buffer
static const int kSpaceLength = 1024;
char space[kSpaceLength];
@@ -98,15 +110,79 @@ void StrAppend(std::string *dst, const char *format, va_list ap) {
delete[] buf;
}
+void StrAppend(std::string *out, const char *format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ StrAppendV(out, format, ap);
+ va_end(ap);
+}
+
std::string StrPrint(const char *format, ...) {
va_list ap;
va_start(ap, format);
std::string result;
- StrAppend(&result, format, ap);
+ StrAppendV(&result, format, ap);
va_end(ap);
return result;
}
+NativePrintfTraits VerifyNativeImplementationImpl() {
+ NativePrintfTraits result;
+
+ // >>> hex_float_has_glibc_rounding. To have glibc's rounding behavior we need
+ // to meet three requirements:
+ //
+ // - The threshold for rounding up is 8 (for e.g. MSVC uses 9).
+ // - If the digits lower than than the 8 are non-zero then we round up.
+ // - If the digits lower than the 8 are all zero then we round toward even.
+ //
+ // The numbers below represent all the cases covering {below,at,above} the
+ // threshold (8) with both {zero,non-zero} lower bits and both {even,odd}
+ // preceding digits.
+ const double d0079 = 65657.0; // 0x1.0079p+16
+ const double d0179 = 65913.0; // 0x1.0179p+16
+ const double d0080 = 65664.0; // 0x1.0080p+16
+ const double d0180 = 65920.0; // 0x1.0180p+16
+ const double d0081 = 65665.0; // 0x1.0081p+16
+ const double d0181 = 65921.0; // 0x1.0181p+16
+ result.hex_float_has_glibc_rounding =
+ StartsWith(StrPrint("%.2a", d0079), "0x1.00") &&
+ StartsWith(StrPrint("%.2a", d0179), "0x1.01") &&
+ StartsWith(StrPrint("%.2a", d0080), "0x1.00") &&
+ StartsWith(StrPrint("%.2a", d0180), "0x1.02") &&
+ StartsWith(StrPrint("%.2a", d0081), "0x1.01") &&
+ StartsWith(StrPrint("%.2a", d0181), "0x1.02");
+
+ // >>> hex_float_prefers_denormal_repr. Formatting `denormal` on glibc yields
+ // "0x0.0000000000001p-1022", whereas on std libs that don't use denormal
+ // representation it would either be 0x1p-1074 or 0x1.0000000000000-1074.
+ const double denormal = std::numeric_limits<double>::denorm_min();
+ result.hex_float_prefers_denormal_repr =
+ StartsWith(StrPrint("%a", denormal), "0x0.0000000000001");
+
+ // >>> hex_float_uses_minimal_precision_when_not_specified. Some (non-glibc)
+ // libs will format the following as "0x1.0079000000000p+16".
+ result.hex_float_uses_minimal_precision_when_not_specified =
+ (StrPrint("%a", d0079) == "0x1.0079p+16");
+
+ // >>> hex_float_optimizes_leading_digit_bit_count. The number 1.5, when
+ // formatted by glibc should yield "0x1.8p+0" for `double` and "0xcp-3" for
+ // `long double`, i.e., number of bits in the leading digit is adapted to the
+ // number of bits in the mantissa.
+ const double d_15 = 1.5;
+ const long double ld_15 = 1.5;
+ result.hex_float_optimizes_leading_digit_bit_count =
+ StartsWith(StrPrint("%a", d_15), "0x1.8") &&
+ StartsWith(StrPrint("%La", ld_15), "0xc");
+
+ return result;
+}
+
+const NativePrintfTraits &VerifyNativeImplementation() {
+ static NativePrintfTraits native_traits = VerifyNativeImplementationImpl();
+ return native_traits;
+}
+
class FormatConvertTest : public ::testing::Test { };
template <typename T>
@@ -463,6 +539,68 @@ TEST_F(FormatConvertTest, Uint128) {
}
}
+template <typename Floating>
+void TestWithMultipleFormatsHelper(const std::vector<Floating> &floats) {
+ const NativePrintfTraits &native_traits = VerifyNativeImplementation();
+ // Reserve the space to ensure we don't allocate memory in the output itself.
+ std::string str_format_result;
+ str_format_result.reserve(1 << 20);
+ std::string string_printf_result;
+ string_printf_result.reserve(1 << 20);
+
+ const char *const kFormats[] = {
+ "%", "%.3", "%8.5", "%500", "%.5000", "%.60", "%.30", "%03",
+ "%+", "% ", "%-10", "%#15.3", "%#.0", "%.0", "%1$*2$", "%1$.*2$"};
+
+ for (const char *fmt : kFormats) {
+ for (char f : {'f', 'F', //
+ 'g', 'G', //
+ 'a', 'A', //
+ 'e', 'E'}) {
+ std::string fmt_str = std::string(fmt) + f;
+
+ if (fmt == absl::string_view("%.5000") && f != 'f' && f != 'F' &&
+ f != 'a' && f != 'A') {
+ // This particular test takes way too long with snprintf.
+ // Disable for the case we are not implementing natively.
+ continue;
+ }
+
+ if ((f == 'a' || f == 'A') &&
+ !native_traits.hex_float_has_glibc_rounding) {
+ continue;
+ }
+
+ for (Floating d : floats) {
+ if (!native_traits.hex_float_prefers_denormal_repr &&
+ (f == 'a' || f == 'A') && std::fpclassify(d) == FP_SUBNORMAL) {
+ continue;
+ }
+ int i = -10;
+ FormatArgImpl args[2] = {FormatArgImpl(d), FormatArgImpl(i)};
+ UntypedFormatSpecImpl format(fmt_str);
+
+ string_printf_result.clear();
+ StrAppend(&string_printf_result, fmt_str.c_str(), d, i);
+ str_format_result.clear();
+
+ {
+ AppendPack(&str_format_result, format, absl::MakeSpan(args));
+ }
+
+ if (string_printf_result != str_format_result) {
+ // We use ASSERT_EQ here because failures are usually correlated and a
+ // bug would print way too many failed expectations causing the test
+ // to time out.
+ ASSERT_EQ(string_printf_result, str_format_result)
+ << fmt_str << " " << StrPrint("%.18g", d) << " "
+ << StrPrint("%a", d) << " " << StrPrint("%.50f", d);
+ }
+ }
+ }
+ }
+}
+
TEST_F(FormatConvertTest, Float) {
#ifdef _MSC_VER
// MSVC has a different rounding policy than us so we can't test our
@@ -470,9 +608,62 @@ TEST_F(FormatConvertTest, Float) {
return;
#endif // _MSC_VER
- const char *const kFormats[] = {
- "%", "%.3", "%8.5", "%9", "%.60", "%.30", "%03", "%+",
- "% ", "%-10", "%#15.3", "%#.0", "%.0", "%1$*2$", "%1$.*2$"};
+ std::vector<float> floats = {0.0f,
+ -0.0f,
+ .9999999f,
+ 9999999.f,
+ std::numeric_limits<float>::max(),
+ -std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::min(),
+ -std::numeric_limits<float>::min(),
+ std::numeric_limits<float>::lowest(),
+ -std::numeric_limits<float>::lowest(),
+ std::numeric_limits<float>::epsilon(),
+ std::numeric_limits<float>::epsilon() + 1.0f,
+ std::numeric_limits<float>::infinity(),
+ -std::numeric_limits<float>::infinity()};
+
+ // Some regression tests.
+ floats.push_back(0.999999989f);
+
+ if (std::numeric_limits<float>::has_denorm != std::denorm_absent) {
+ floats.push_back(std::numeric_limits<float>::denorm_min());
+ floats.push_back(-std::numeric_limits<float>::denorm_min());
+ }
+
+ for (float base :
+ {1.f, 12.f, 123.f, 1234.f, 12345.f, 123456.f, 1234567.f, 12345678.f,
+ 123456789.f, 1234567890.f, 12345678901.f, 12345678.f, 12345678.f}) {
+ for (int exp = -123; exp <= 123; ++exp) {
+ for (int sign : {1, -1}) {
+ floats.push_back(sign * std::ldexp(base, exp));
+ }
+ }
+ }
+
+ for (int exp = -300; exp <= 300; ++exp) {
+ const float all_ones_mantissa = 0xffffff;
+ floats.push_back(std::ldexp(all_ones_mantissa, exp));
+ }
+
+ // Remove duplicates to speed up the logic below.
+ std::sort(floats.begin(), floats.end());
+ floats.erase(std::unique(floats.begin(), floats.end()), floats.end());
+
+#ifndef __APPLE__
+ // Apple formats NaN differently (+nan) vs. (nan)
+ floats.push_back(std::nan(""));
+#endif
+
+ TestWithMultipleFormatsHelper(floats);
+}
+
+TEST_F(FormatConvertTest, Double) {
+#ifdef _MSC_VER
+ // MSVC has a different rounding policy than us so we can't test our
+ // implementation against the native one there.
+ return;
+#endif // _MSC_VER
std::vector<double> doubles = {0.0,
-0.0,
@@ -489,11 +680,6 @@ TEST_F(FormatConvertTest, Float) {
std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity()};
-#ifndef __APPLE__
- // Apple formats NaN differently (+nan) vs. (nan)
- doubles.push_back(std::nan(""));
-#endif
-
// Some regression tests.
doubles.push_back(0.99999999999999989);
@@ -512,43 +698,375 @@ TEST_F(FormatConvertTest, Float) {
}
}
- for (const char *fmt : kFormats) {
- for (char f : {'f', 'F', //
- 'g', 'G', //
- 'a', 'A', //
- 'e', 'E'}) {
- std::string fmt_str = std::string(fmt) + f;
- for (double d : doubles) {
- int i = -10;
- FormatArgImpl args[2] = {FormatArgImpl(d), FormatArgImpl(i)};
- UntypedFormatSpecImpl format(fmt_str);
- // We use ASSERT_EQ here because failures are usually correlated and a
- // bug would print way too many failed expectations causing the test to
- // time out.
- ASSERT_EQ(StrPrint(fmt_str.c_str(), d, i),
- FormatPack(format, absl::MakeSpan(args)))
- << fmt_str << " " << StrPrint("%.18g", d) << " "
- << StrPrint("%.999f", d);
+ // Workaround libc bug.
+ // https://sourceware.org/bugzilla/show_bug.cgi?id=22142
+ const bool gcc_bug_22142 =
+ StrPrint("%f", std::numeric_limits<double>::max()) !=
+ "1797693134862315708145274237317043567980705675258449965989174768031"
+ "5726078002853876058955863276687817154045895351438246423432132688946"
+ "4182768467546703537516986049910576551282076245490090389328944075868"
+ "5084551339423045832369032229481658085593321233482747978262041447231"
+ "68738177180919299881250404026184124858368.000000";
+
+ if (!gcc_bug_22142) {
+ for (int exp = -300; exp <= 300; ++exp) {
+ const double all_ones_mantissa = 0x1fffffffffffff;
+ doubles.push_back(std::ldexp(all_ones_mantissa, exp));
+ }
+ }
+
+ if (gcc_bug_22142) {
+ for (auto &d : doubles) {
+ using L = std::numeric_limits<double>;
+ double d2 = std::abs(d);
+ if (d2 == L::max() || d2 == L::min() || d2 == L::denorm_min()) {
+ d = 0;
}
}
}
+
+ // Remove duplicates to speed up the logic below.
+ std::sort(doubles.begin(), doubles.end());
+ doubles.erase(std::unique(doubles.begin(), doubles.end()), doubles.end());
+
+#ifndef __APPLE__
+ // Apple formats NaN differently (+nan) vs. (nan)
+ doubles.push_back(std::nan(""));
+#endif
+
+ TestWithMultipleFormatsHelper(doubles);
+}
+
+TEST_F(FormatConvertTest, DoubleRound) {
+ std::string s;
+ const auto format = [&](const char *fmt, double d) -> std::string & {
+ s.clear();
+ FormatArgImpl args[1] = {FormatArgImpl(d)};
+ AppendPack(&s, UntypedFormatSpecImpl(fmt), absl::MakeSpan(args));
+#if !defined(_MSC_VER)
+ // MSVC has a different rounding policy than us so we can't test our
+ // implementation against the native one there.
+ EXPECT_EQ(StrPrint(fmt, d), s);
+#endif // _MSC_VER
+
+ return s;
+ };
+ // All of these values have to be exactly represented.
+ // Otherwise we might not be testing what we think we are testing.
+
+ // These values can fit in a 64bit "fast" representation.
+ const double exact_value = 0.00000000000005684341886080801486968994140625;
+ assert(exact_value == std::pow(2, -44));
+ // Round up at a 5xx.
+ EXPECT_EQ(format("%.13f", exact_value), "0.0000000000001");
+ // Round up at a >5
+ EXPECT_EQ(format("%.14f", exact_value), "0.00000000000006");
+ // Round down at a <5
+ EXPECT_EQ(format("%.16f", exact_value), "0.0000000000000568");
+ // Nine handling
+ EXPECT_EQ(format("%.35f", exact_value),
+ "0.00000000000005684341886080801486969");
+ EXPECT_EQ(format("%.36f", exact_value),
+ "0.000000000000056843418860808014869690");
+ // Round down the last nine.
+ EXPECT_EQ(format("%.37f", exact_value),
+ "0.0000000000000568434188608080148696899");
+ EXPECT_EQ(format("%.10f", 0.000003814697265625), "0.0000038147");
+ // Round up the last nine
+ EXPECT_EQ(format("%.11f", 0.000003814697265625), "0.00000381470");
+ EXPECT_EQ(format("%.12f", 0.000003814697265625), "0.000003814697");
+
+ // Round to even (down)
+ EXPECT_EQ(format("%.43f", exact_value),
+ "0.0000000000000568434188608080148696899414062");
+ // Exact
+ EXPECT_EQ(format("%.44f", exact_value),
+ "0.00000000000005684341886080801486968994140625");
+ // Round to even (up), let make the last digits 75 instead of 25
+ EXPECT_EQ(format("%.43f", exact_value + std::pow(2, -43)),
+ "0.0000000000001705302565824240446090698242188");
+ // Exact, just to check.
+ EXPECT_EQ(format("%.44f", exact_value + std::pow(2, -43)),
+ "0.00000000000017053025658242404460906982421875");
+
+ // This value has to be small enough that it won't fit in the uint128
+ // representation for printing.
+ const double small_exact_value =
+ 0.000000000000000000000000000000000000752316384526264005099991383822237233803945956334136013765601092018187046051025390625; // NOLINT
+ assert(small_exact_value == std::pow(2, -120));
+ // Round up at a 5xx.
+ EXPECT_EQ(format("%.37f", small_exact_value),
+ "0.0000000000000000000000000000000000008");
+ // Round down at a <5
+ EXPECT_EQ(format("%.38f", small_exact_value),
+ "0.00000000000000000000000000000000000075");
+ // Round up at a >5
+ EXPECT_EQ(format("%.41f", small_exact_value),
+ "0.00000000000000000000000000000000000075232");
+ // Nine handling
+ EXPECT_EQ(format("%.55f", small_exact_value),
+ "0.0000000000000000000000000000000000007523163845262640051");
+ EXPECT_EQ(format("%.56f", small_exact_value),
+ "0.00000000000000000000000000000000000075231638452626400510");
+ EXPECT_EQ(format("%.57f", small_exact_value),
+ "0.000000000000000000000000000000000000752316384526264005100");
+ EXPECT_EQ(format("%.58f", small_exact_value),
+ "0.0000000000000000000000000000000000007523163845262640051000");
+ // Round down the last nine
+ EXPECT_EQ(format("%.59f", small_exact_value),
+ "0.00000000000000000000000000000000000075231638452626400509999");
+ // Round up the last nine
+ EXPECT_EQ(format("%.79f", small_exact_value),
+ "0.000000000000000000000000000000000000"
+ "7523163845262640050999913838222372338039460");
+
+ // Round to even (down)
+ EXPECT_EQ(format("%.119f", small_exact_value),
+ "0.000000000000000000000000000000000000"
+ "75231638452626400509999138382223723380"
+ "394595633413601376560109201818704605102539062");
+ // Exact
+ EXPECT_EQ(format("%.120f", small_exact_value),
+ "0.000000000000000000000000000000000000"
+ "75231638452626400509999138382223723380"
+ "3945956334136013765601092018187046051025390625");
+ // Round to even (up), let make the last digits 75 instead of 25
+ EXPECT_EQ(format("%.119f", small_exact_value + std::pow(2, -119)),
+ "0.000000000000000000000000000000000002"
+ "25694915357879201529997415146671170141"
+ "183786900240804129680327605456113815307617188");
+ // Exact, just to check.
+ EXPECT_EQ(format("%.120f", small_exact_value + std::pow(2, -119)),
+ "0.000000000000000000000000000000000002"
+ "25694915357879201529997415146671170141"
+ "1837869002408041296803276054561138153076171875");
+}
+
+TEST_F(FormatConvertTest, DoubleRoundA) {
+ const NativePrintfTraits &native_traits = VerifyNativeImplementation();
+ std::string s;
+ const auto format = [&](const char *fmt, double d) -> std::string & {
+ s.clear();
+ FormatArgImpl args[1] = {FormatArgImpl(d)};
+ AppendPack(&s, UntypedFormatSpecImpl(fmt), absl::MakeSpan(args));
+ if (native_traits.hex_float_has_glibc_rounding) {
+ EXPECT_EQ(StrPrint(fmt, d), s);
+ }
+ return s;
+ };
+
+ // 0x1.00018000p+100
+ const double on_boundary_odd = 1267679614447900152596896153600.0;
+ EXPECT_EQ(format("%.0a", on_boundary_odd), "0x1p+100");
+ EXPECT_EQ(format("%.1a", on_boundary_odd), "0x1.0p+100");
+ EXPECT_EQ(format("%.2a", on_boundary_odd), "0x1.00p+100");
+ EXPECT_EQ(format("%.3a", on_boundary_odd), "0x1.000p+100");
+ EXPECT_EQ(format("%.4a", on_boundary_odd), "0x1.0002p+100"); // round
+ EXPECT_EQ(format("%.5a", on_boundary_odd), "0x1.00018p+100");
+ EXPECT_EQ(format("%.6a", on_boundary_odd), "0x1.000180p+100");
+
+ // 0x1.00028000p-2
+ const double on_boundary_even = 0.250009536743164062500;
+ EXPECT_EQ(format("%.0a", on_boundary_even), "0x1p-2");
+ EXPECT_EQ(format("%.1a", on_boundary_even), "0x1.0p-2");
+ EXPECT_EQ(format("%.2a", on_boundary_even), "0x1.00p-2");
+ EXPECT_EQ(format("%.3a", on_boundary_even), "0x1.000p-2");
+ EXPECT_EQ(format("%.4a", on_boundary_even), "0x1.0002p-2"); // no round
+ EXPECT_EQ(format("%.5a", on_boundary_even), "0x1.00028p-2");
+ EXPECT_EQ(format("%.6a", on_boundary_even), "0x1.000280p-2");
+
+ // 0x1.00018001p+1
+ const double slightly_over = 2.00004577683284878730773925781250;
+ EXPECT_EQ(format("%.0a", slightly_over), "0x1p+1");
+ EXPECT_EQ(format("%.1a", slightly_over), "0x1.0p+1");
+ EXPECT_EQ(format("%.2a", slightly_over), "0x1.00p+1");
+ EXPECT_EQ(format("%.3a", slightly_over), "0x1.000p+1");
+ EXPECT_EQ(format("%.4a", slightly_over), "0x1.0002p+1");
+ EXPECT_EQ(format("%.5a", slightly_over), "0x1.00018p+1");
+ EXPECT_EQ(format("%.6a", slightly_over), "0x1.000180p+1");
+
+ // 0x1.00017fffp+0
+ const double slightly_under = 1.000022887950763106346130371093750;
+ EXPECT_EQ(format("%.0a", slightly_under), "0x1p+0");
+ EXPECT_EQ(format("%.1a", slightly_under), "0x1.0p+0");
+ EXPECT_EQ(format("%.2a", slightly_under), "0x1.00p+0");
+ EXPECT_EQ(format("%.3a", slightly_under), "0x1.000p+0");
+ EXPECT_EQ(format("%.4a", slightly_under), "0x1.0001p+0");
+ EXPECT_EQ(format("%.5a", slightly_under), "0x1.00018p+0");
+ EXPECT_EQ(format("%.6a", slightly_under), "0x1.000180p+0");
+ EXPECT_EQ(format("%.7a", slightly_under), "0x1.0001800p+0");
+
+ // 0x1.1b3829ac28058p+3
+ const double hex_value = 8.85060580848964661981881363317370414733886718750;
+ EXPECT_EQ(format("%.0a", hex_value), "0x1p+3");
+ EXPECT_EQ(format("%.1a", hex_value), "0x1.2p+3");
+ EXPECT_EQ(format("%.2a", hex_value), "0x1.1bp+3");
+ EXPECT_EQ(format("%.3a", hex_value), "0x1.1b4p+3");
+ EXPECT_EQ(format("%.4a", hex_value), "0x1.1b38p+3");
+ EXPECT_EQ(format("%.5a", hex_value), "0x1.1b383p+3");
+ EXPECT_EQ(format("%.6a", hex_value), "0x1.1b382ap+3");
+ EXPECT_EQ(format("%.7a", hex_value), "0x1.1b3829bp+3");
+ EXPECT_EQ(format("%.8a", hex_value), "0x1.1b3829acp+3");
+ EXPECT_EQ(format("%.9a", hex_value), "0x1.1b3829ac3p+3");
+ EXPECT_EQ(format("%.10a", hex_value), "0x1.1b3829ac28p+3");
+ EXPECT_EQ(format("%.11a", hex_value), "0x1.1b3829ac280p+3");
+ EXPECT_EQ(format("%.12a", hex_value), "0x1.1b3829ac2806p+3");
+ EXPECT_EQ(format("%.13a", hex_value), "0x1.1b3829ac28058p+3");
+ EXPECT_EQ(format("%.14a", hex_value), "0x1.1b3829ac280580p+3");
+ EXPECT_EQ(format("%.15a", hex_value), "0x1.1b3829ac2805800p+3");
+ EXPECT_EQ(format("%.16a", hex_value), "0x1.1b3829ac28058000p+3");
+ EXPECT_EQ(format("%.17a", hex_value), "0x1.1b3829ac280580000p+3");
+ EXPECT_EQ(format("%.18a", hex_value), "0x1.1b3829ac2805800000p+3");
+ EXPECT_EQ(format("%.19a", hex_value), "0x1.1b3829ac28058000000p+3");
+ EXPECT_EQ(format("%.20a", hex_value), "0x1.1b3829ac280580000000p+3");
+ EXPECT_EQ(format("%.21a", hex_value), "0x1.1b3829ac2805800000000p+3");
+
+ // 0x1.0818283848586p+3
+ const double hex_value2 = 8.2529488658208371987257123691961169242858886718750;
+ EXPECT_EQ(format("%.0a", hex_value2), "0x1p+3");
+ EXPECT_EQ(format("%.1a", hex_value2), "0x1.1p+3");
+ EXPECT_EQ(format("%.2a", hex_value2), "0x1.08p+3");
+ EXPECT_EQ(format("%.3a", hex_value2), "0x1.082p+3");
+ EXPECT_EQ(format("%.4a", hex_value2), "0x1.0818p+3");
+ EXPECT_EQ(format("%.5a", hex_value2), "0x1.08183p+3");
+ EXPECT_EQ(format("%.6a", hex_value2), "0x1.081828p+3");
+ EXPECT_EQ(format("%.7a", hex_value2), "0x1.0818284p+3");
+ EXPECT_EQ(format("%.8a", hex_value2), "0x1.08182838p+3");
+ EXPECT_EQ(format("%.9a", hex_value2), "0x1.081828385p+3");
+ EXPECT_EQ(format("%.10a", hex_value2), "0x1.0818283848p+3");
+ EXPECT_EQ(format("%.11a", hex_value2), "0x1.08182838486p+3");
+ EXPECT_EQ(format("%.12a", hex_value2), "0x1.081828384858p+3");
+ EXPECT_EQ(format("%.13a", hex_value2), "0x1.0818283848586p+3");
+ EXPECT_EQ(format("%.14a", hex_value2), "0x1.08182838485860p+3");
+ EXPECT_EQ(format("%.15a", hex_value2), "0x1.081828384858600p+3");
+ EXPECT_EQ(format("%.16a", hex_value2), "0x1.0818283848586000p+3");
+ EXPECT_EQ(format("%.17a", hex_value2), "0x1.08182838485860000p+3");
+ EXPECT_EQ(format("%.18a", hex_value2), "0x1.081828384858600000p+3");
+ EXPECT_EQ(format("%.19a", hex_value2), "0x1.0818283848586000000p+3");
+ EXPECT_EQ(format("%.20a", hex_value2), "0x1.08182838485860000000p+3");
+ EXPECT_EQ(format("%.21a", hex_value2), "0x1.081828384858600000000p+3");
+}
+
+TEST_F(FormatConvertTest, LongDoubleRoundA) {
+ if (std::numeric_limits<long double>::digits % 4 != 0) {
+ // This test doesn't really make sense to run on platforms where a long
+ // double has a different mantissa size (mod 4) than Prod, since then the
+ // leading digit will be formatted differently.
+ return;
+ }
+ const NativePrintfTraits &native_traits = VerifyNativeImplementation();
+ std::string s;
+ const auto format = [&](const char *fmt, long double d) -> std::string & {
+ s.clear();
+ FormatArgImpl args[1] = {FormatArgImpl(d)};
+ AppendPack(&s, UntypedFormatSpecImpl(fmt), absl::MakeSpan(args));
+ if (native_traits.hex_float_has_glibc_rounding &&
+ native_traits.hex_float_optimizes_leading_digit_bit_count) {
+ EXPECT_EQ(StrPrint(fmt, d), s);
+ }
+ return s;
+ };
+
+ // 0x8.8p+4
+ const long double on_boundary_even = 136.0;
+ EXPECT_EQ(format("%.0La", on_boundary_even), "0x8p+4");
+ EXPECT_EQ(format("%.1La", on_boundary_even), "0x8.8p+4");
+ EXPECT_EQ(format("%.2La", on_boundary_even), "0x8.80p+4");
+ EXPECT_EQ(format("%.3La", on_boundary_even), "0x8.800p+4");
+ EXPECT_EQ(format("%.4La", on_boundary_even), "0x8.8000p+4");
+ EXPECT_EQ(format("%.5La", on_boundary_even), "0x8.80000p+4");
+ EXPECT_EQ(format("%.6La", on_boundary_even), "0x8.800000p+4");
+
+ // 0x9.8p+4
+ const long double on_boundary_odd = 152.0;
+ EXPECT_EQ(format("%.0La", on_boundary_odd), "0xap+4");
+ EXPECT_EQ(format("%.1La", on_boundary_odd), "0x9.8p+4");
+ EXPECT_EQ(format("%.2La", on_boundary_odd), "0x9.80p+4");
+ EXPECT_EQ(format("%.3La", on_boundary_odd), "0x9.800p+4");
+ EXPECT_EQ(format("%.4La", on_boundary_odd), "0x9.8000p+4");
+ EXPECT_EQ(format("%.5La", on_boundary_odd), "0x9.80000p+4");
+ EXPECT_EQ(format("%.6La", on_boundary_odd), "0x9.800000p+4");
+
+ // 0x8.80001p+24
+ const long double slightly_over = 142606352.0;
+ EXPECT_EQ(format("%.0La", slightly_over), "0x9p+24");
+ EXPECT_EQ(format("%.1La", slightly_over), "0x8.8p+24");
+ EXPECT_EQ(format("%.2La", slightly_over), "0x8.80p+24");
+ EXPECT_EQ(format("%.3La", slightly_over), "0x8.800p+24");
+ EXPECT_EQ(format("%.4La", slightly_over), "0x8.8000p+24");
+ EXPECT_EQ(format("%.5La", slightly_over), "0x8.80001p+24");
+ EXPECT_EQ(format("%.6La", slightly_over), "0x8.800010p+24");
+
+ // 0x8.7ffffp+24
+ const long double slightly_under = 142606320.0;
+ EXPECT_EQ(format("%.0La", slightly_under), "0x8p+24");
+ EXPECT_EQ(format("%.1La", slightly_under), "0x8.8p+24");
+ EXPECT_EQ(format("%.2La", slightly_under), "0x8.80p+24");
+ EXPECT_EQ(format("%.3La", slightly_under), "0x8.800p+24");
+ EXPECT_EQ(format("%.4La", slightly_under), "0x8.8000p+24");
+ EXPECT_EQ(format("%.5La", slightly_under), "0x8.7ffffp+24");
+ EXPECT_EQ(format("%.6La", slightly_under), "0x8.7ffff0p+24");
+ EXPECT_EQ(format("%.7La", slightly_under), "0x8.7ffff00p+24");
+
+ // 0xc.0828384858688000p+128
+ const long double eights = 4094231060438608800781871108094404067328.0;
+ EXPECT_EQ(format("%.0La", eights), "0xcp+128");
+ EXPECT_EQ(format("%.1La", eights), "0xc.1p+128");
+ EXPECT_EQ(format("%.2La", eights), "0xc.08p+128");
+ EXPECT_EQ(format("%.3La", eights), "0xc.083p+128");
+ EXPECT_EQ(format("%.4La", eights), "0xc.0828p+128");
+ EXPECT_EQ(format("%.5La", eights), "0xc.08284p+128");
+ EXPECT_EQ(format("%.6La", eights), "0xc.082838p+128");
+ EXPECT_EQ(format("%.7La", eights), "0xc.0828385p+128");
+ EXPECT_EQ(format("%.8La", eights), "0xc.08283848p+128");
+ EXPECT_EQ(format("%.9La", eights), "0xc.082838486p+128");
+ EXPECT_EQ(format("%.10La", eights), "0xc.0828384858p+128");
+ EXPECT_EQ(format("%.11La", eights), "0xc.08283848587p+128");
+ EXPECT_EQ(format("%.12La", eights), "0xc.082838485868p+128");
+ EXPECT_EQ(format("%.13La", eights), "0xc.0828384858688p+128");
+ EXPECT_EQ(format("%.14La", eights), "0xc.08283848586880p+128");
+ EXPECT_EQ(format("%.15La", eights), "0xc.082838485868800p+128");
+ EXPECT_EQ(format("%.16La", eights), "0xc.0828384858688000p+128");
+}
+
+// We don't actually store the results. This is just to exercise the rest of the
+// machinery.
+struct NullSink {
+ friend void AbslFormatFlush(NullSink *sink, string_view str) {}
+};
+
+template <typename... T>
+bool FormatWithNullSink(absl::string_view fmt, const T &... a) {
+ NullSink sink;
+ FormatArgImpl args[] = {FormatArgImpl(a)...};
+ return FormatUntyped(&sink, UntypedFormatSpecImpl(fmt), absl::MakeSpan(args));
+}
+
+TEST_F(FormatConvertTest, ExtremeWidthPrecision) {
+ for (const char *fmt : {"f"}) {
+ for (double d : {1e-100, 1.0, 1e100}) {
+ constexpr int max = std::numeric_limits<int>::max();
+ EXPECT_TRUE(FormatWithNullSink(std::string("%.*") + fmt, max, d));
+ EXPECT_TRUE(FormatWithNullSink(std::string("%1.*") + fmt, max, d));
+ EXPECT_TRUE(FormatWithNullSink(std::string("%*") + fmt, max, d));
+ EXPECT_TRUE(FormatWithNullSink(std::string("%*.*") + fmt, max, max, d));
+ }
+ }
}
TEST_F(FormatConvertTest, LongDouble) {
- const char *const kFormats[] = {"%", "%.3", "%8.5", "%9",
+#ifdef _MSC_VER
+ // MSVC has a different rounding policy than us so we can't test our
+ // implementation against the native one there.
+ return;
+#endif // _MSC_VER
+ const NativePrintfTraits &native_traits = VerifyNativeImplementation();
+ const char *const kFormats[] = {"%", "%.3", "%8.5", "%9", "%.5000",
"%.60", "%+", "% ", "%-10"};
- // This value is not representable in double, but it is in long double that
- // uses the extended format.
- // This is to verify that we are not truncating the value mistakenly through a
- // double.
- long double very_precise = 10000000000000000.25L;
-
std::vector<long double> doubles = {
0.0,
-0.0,
- very_precise,
- 1 / very_precise,
std::numeric_limits<long double>::max(),
-std::numeric_limits<long double>::max(),
std::numeric_limits<long double>::min(),
@@ -556,28 +1074,65 @@ TEST_F(FormatConvertTest, LongDouble) {
std::numeric_limits<long double>::infinity(),
-std::numeric_limits<long double>::infinity()};
+ for (long double base : {1.L, 12.L, 123.L, 1234.L, 12345.L, 123456.L,
+ 1234567.L, 12345678.L, 123456789.L, 1234567890.L,
+ 12345678901.L, 123456789012.L, 1234567890123.L,
+ // This value is not representable in double, but it
+ // is in long double that uses the extended format.
+ // This is to verify that we are not truncating the
+ // value mistakenly through a double.
+ 10000000000000000.25L}) {
+ for (int exp : {-1000, -500, 0, 500, 1000}) {
+ for (int sign : {1, -1}) {
+ doubles.push_back(sign * std::ldexp(base, exp));
+ doubles.push_back(sign / std::ldexp(base, exp));
+ }
+ }
+ }
+
+ // Regression tests
+ //
+ // Using a string literal because not all platforms support hex literals or it
+ // might be out of range.
+ doubles.push_back(std::strtold("-0xf.ffffffb5feafffbp-16324L", nullptr));
+
for (const char *fmt : kFormats) {
for (char f : {'f', 'F', //
'g', 'G', //
'a', 'A', //
'e', 'E'}) {
std::string fmt_str = std::string(fmt) + 'L' + f;
+
+ if (fmt == absl::string_view("%.5000") && f != 'f' && f != 'F' &&
+ f != 'a' && f != 'A') {
+ // This particular test takes way too long with snprintf.
+ // Disable for the case we are not implementing natively.
+ continue;
+ }
+
+ if (f == 'a' || f == 'A') {
+ if (!native_traits.hex_float_has_glibc_rounding ||
+ !native_traits.hex_float_optimizes_leading_digit_bit_count) {
+ continue;
+ }
+ }
+
for (auto d : doubles) {
FormatArgImpl arg(d);
UntypedFormatSpecImpl format(fmt_str);
// We use ASSERT_EQ here because failures are usually correlated and a
// bug would print way too many failed expectations causing the test to
// time out.
- ASSERT_EQ(StrPrint(fmt_str.c_str(), d),
- FormatPack(format, {&arg, 1}))
+ ASSERT_EQ(StrPrint(fmt_str.c_str(), d), FormatPack(format, {&arg, 1}))
<< fmt_str << " " << StrPrint("%.18Lg", d) << " "
- << StrPrint("%.999Lf", d);
+ << StrPrint("%La", d) << " " << StrPrint("%.1080Lf", d);
}
}
}
}
-TEST_F(FormatConvertTest, IntAsFloat) {
+TEST_F(FormatConvertTest, IntAsDouble) {
+ const NativePrintfTraits &native_traits = VerifyNativeImplementation();
const int kMin = std::numeric_limits<int>::min();
const int kMax = std::numeric_limits<int>::max();
const int ia[] = {
@@ -593,14 +1148,17 @@ TEST_F(FormatConvertTest, IntAsFloat) {
const char *fmt;
};
const double dx = static_cast<double>(fx);
- const Expectation kExpect[] = {
- { __LINE__, StrPrint("%f", dx), "%f" },
- { __LINE__, StrPrint("%12f", dx), "%12f" },
- { __LINE__, StrPrint("%.12f", dx), "%.12f" },
- { __LINE__, StrPrint("%12a", dx), "%12a" },
- { __LINE__, StrPrint("%.12a", dx), "%.12a" },
+ std::vector<Expectation> expect = {
+ {__LINE__, StrPrint("%f", dx), "%f"},
+ {__LINE__, StrPrint("%12f", dx), "%12f"},
+ {__LINE__, StrPrint("%.12f", dx), "%.12f"},
+ {__LINE__, StrPrint("%.12a", dx), "%.12a"},
};
- for (const Expectation &e : kExpect) {
+ if (native_traits.hex_float_uses_minimal_precision_when_not_specified) {
+ Expectation ex = {__LINE__, StrPrint("%12a", dx), "%12a"};
+ expect.push_back(ex);
+ }
+ for (const Expectation &e : expect) {
SCOPED_TRACE(e.line);
SCOPED_TRACE(e.fmt);
UntypedFormatSpecImpl format(e.fmt);
@@ -645,6 +1203,25 @@ TEST_F(FormatConvertTest, ExpectedFailures) {
EXPECT_TRUE(FormatFails("%*d", ""));
}
+// Sanity check to make sure that we are testing what we think we're testing on
+// e.g. the x86_64+glibc platform.
+TEST_F(FormatConvertTest, GlibcHasCorrectTraits) {
+#if !defined(__GLIBC__) || !defined(__x86_64__)
+ return;
+#endif
+ const NativePrintfTraits &native_traits = VerifyNativeImplementation();
+ // If one of the following tests break then it is either because the above PP
+ // macro guards failed to exclude a new platform (likely) or because something
+ // has changed in the implemention of glibc sprintf float formatting behavior.
+ // If the latter, then the code that computes these flags needs to be
+ // revisited and/or possibly the StrFormat implementation.
+ EXPECT_TRUE(native_traits.hex_float_has_glibc_rounding);
+ EXPECT_TRUE(native_traits.hex_float_prefers_denormal_repr);
+ EXPECT_TRUE(
+ native_traits.hex_float_uses_minimal_precision_when_not_specified);
+ EXPECT_TRUE(native_traits.hex_float_optimizes_leading_digit_bit_count);
+}
+
} // namespace
} // namespace str_format_internal
ABSL_NAMESPACE_END
diff --git a/absl/strings/internal/str_format/extension.cc b/absl/strings/internal/str_format/extension.cc
index 2e5bc2ce..bb0d96cf 100644
--- a/absl/strings/internal/str_format/extension.cc
+++ b/absl/strings/internal/str_format/extension.cc
@@ -33,16 +33,40 @@ std::string Flags::ToString() const {
return s;
}
-bool FormatSinkImpl::PutPaddedString(string_view v, int w, int p, bool l) {
+#define ABSL_INTERNAL_X_VAL(id) \
+ constexpr absl::FormatConversionChar FormatConversionCharInternal::id;
+ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_X_VAL, )
+#undef ABSL_INTERNAL_X_VAL
+// NOLINTNEXTLINE(readability-redundant-declaration)
+constexpr absl::FormatConversionChar FormatConversionCharInternal::kNone;
+
+#define ABSL_INTERNAL_CHAR_SET_CASE(c) \
+ constexpr FormatConversionCharSet FormatConversionCharSetInternal::c;
+ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_CHAR_SET_CASE, )
+#undef ABSL_INTERNAL_CHAR_SET_CASE
+
+// NOLINTNEXTLINE(readability-redundant-declaration)
+constexpr FormatConversionCharSet FormatConversionCharSetInternal::kStar;
+// NOLINTNEXTLINE(readability-redundant-declaration)
+constexpr FormatConversionCharSet FormatConversionCharSetInternal::kIntegral;
+// NOLINTNEXTLINE(readability-redundant-declaration)
+constexpr FormatConversionCharSet FormatConversionCharSetInternal::kFloating;
+// NOLINTNEXTLINE(readability-redundant-declaration)
+constexpr FormatConversionCharSet FormatConversionCharSetInternal::kNumeric;
+// NOLINTNEXTLINE(readability-redundant-declaration)
+constexpr FormatConversionCharSet FormatConversionCharSetInternal::kPointer;
+
+bool FormatSinkImpl::PutPaddedString(string_view value, int width,
+ int precision, bool left) {
size_t space_remaining = 0;
- if (w >= 0) space_remaining = w;
- size_t n = v.size();
- if (p >= 0) n = std::min(n, static_cast<size_t>(p));
- string_view shown(v.data(), n);
+ if (width >= 0) space_remaining = width;
+ size_t n = value.size();
+ if (precision >= 0) n = std::min(n, static_cast<size_t>(precision));
+ string_view shown(value.data(), n);
space_remaining = Excess(shown.size(), space_remaining);
- if (!l) Append(space_remaining, ' ');
+ if (!left) Append(space_remaining, ' ');
Append(shown);
- if (l) Append(space_remaining, ' ');
+ if (left) Append(space_remaining, ' ');
return true;
}
diff --git a/absl/strings/internal/str_format/extension.h b/absl/strings/internal/str_format/extension.h
index d1665753..a9b9e137 100644
--- a/absl/strings/internal/str_format/extension.h
+++ b/absl/strings/internal/str_format/extension.h
@@ -24,11 +24,16 @@
#include "absl/base/config.h"
#include "absl/base/port.h"
+#include "absl/meta/type_traits.h"
#include "absl/strings/internal/str_format/output.h"
#include "absl/strings/string_view.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
+
+enum class FormatConversionChar : uint8_t;
+enum class FormatConversionCharSet : uint64_t;
+
namespace str_format_internal {
class FormatRawSinkImpl {
@@ -102,7 +107,7 @@ class FormatSinkImpl {
size_t size() const { return size_; }
// Put 'v' to 'sink' with specified width, precision, and left flag.
- bool PutPaddedString(string_view v, int w, int p, bool l);
+ bool PutPaddedString(string_view v, int width, int precision, bool left);
template <typename T>
T Wrap() {
@@ -139,7 +144,7 @@ struct Flags {
// clang-format off
#define ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(X_VAL, X_SEP) \
/* text */ \
- X_VAL(c) X_SEP X_VAL(C) X_SEP X_VAL(s) X_SEP X_VAL(S) X_SEP \
+ X_VAL(c) X_SEP X_VAL(s) X_SEP \
/* ints */ \
X_VAL(d) X_SEP X_VAL(i) X_SEP X_VAL(o) X_SEP \
X_VAL(u) X_SEP X_VAL(x) X_SEP X_VAL(X) X_SEP \
@@ -148,14 +153,39 @@ struct Flags {
X_VAL(g) X_SEP X_VAL(G) X_SEP X_VAL(a) X_SEP X_VAL(A) X_SEP \
/* misc */ \
X_VAL(n) X_SEP X_VAL(p)
+// clang-format on
+
+// This type should not be referenced, it exists only to provide labels
+// internally that match the values declared in FormatConversionChar in
+// str_format.h. This is meant to allow internal libraries to use the same
+// declared interface type as the public interface
+// (absl::StrFormatConversionChar) while keeping the definition in a public
+// header.
+// Internal libraries should use the form
+// `FormatConversionCharInternal::c`, `FormatConversionCharInternal::kNone` for
+// comparisons. Use in switch statements is not recommended due to a bug in how
+// gcc 4.9 -Wswitch handles declared but undefined enums.
+struct FormatConversionCharInternal {
+ FormatConversionCharInternal() = delete;
-enum class FormatConversionChar : uint8_t {
- c, C, s, S, // text
+ private:
+ // clang-format off
+ enum class Enum : uint8_t {
+ c, s, // text
d, i, o, u, x, X, // int
f, F, e, E, g, G, a, A, // float
n, p, // misc
- kNone,
- none = kNone
+ kNone
+ };
+ // clang-format on
+ public:
+#define ABSL_INTERNAL_X_VAL(id) \
+ static constexpr FormatConversionChar id = \
+ static_cast<FormatConversionChar>(Enum::id);
+ ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_X_VAL, )
+#undef ABSL_INTERNAL_X_VAL
+ static constexpr FormatConversionChar kNone =
+ static_cast<FormatConversionChar>(Enum::kNone);
};
// clang-format on
@@ -163,95 +193,56 @@ inline FormatConversionChar FormatConversionCharFromChar(char c) {
switch (c) {
#define ABSL_INTERNAL_X_VAL(id) \
case #id[0]: \
- return FormatConversionChar::id;
+ return FormatConversionCharInternal::id;
ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_X_VAL, )
#undef ABSL_INTERNAL_X_VAL
}
- return FormatConversionChar::kNone;
-}
-
-inline int FormatConversionCharRadix(FormatConversionChar c) {
- switch (c) {
- case FormatConversionChar::x:
- case FormatConversionChar::X:
- case FormatConversionChar::a:
- case FormatConversionChar::A:
- case FormatConversionChar::p:
- return 16;
- case FormatConversionChar::o:
- return 8;
- default:
- return 10;
- }
+ return FormatConversionCharInternal::kNone;
}
inline bool FormatConversionCharIsUpper(FormatConversionChar c) {
- switch (c) {
- case FormatConversionChar::X:
- case FormatConversionChar::F:
- case FormatConversionChar::E:
- case FormatConversionChar::G:
- case FormatConversionChar::A:
- return true;
- default:
- return false;
- }
-}
-
-inline bool FormatConversionCharIsSigned(FormatConversionChar c) {
- switch (c) {
- case FormatConversionChar::d:
- case FormatConversionChar::i:
- return true;
- default:
- return false;
- }
-}
-
-inline bool FormatConversionCharIsIntegral(FormatConversionChar c) {
- switch (c) {
- case FormatConversionChar::d:
- case FormatConversionChar::i:
- case FormatConversionChar::u:
- case FormatConversionChar::o:
- case FormatConversionChar::x:
- case FormatConversionChar::X:
- return true;
- default:
- return false;
+ if (c == FormatConversionCharInternal::X ||
+ c == FormatConversionCharInternal::F ||
+ c == FormatConversionCharInternal::E ||
+ c == FormatConversionCharInternal::G ||
+ c == FormatConversionCharInternal::A) {
+ return true;
+ } else {
+ return false;
}
}
inline bool FormatConversionCharIsFloat(FormatConversionChar c) {
- switch (c) {
- case FormatConversionChar::a:
- case FormatConversionChar::e:
- case FormatConversionChar::f:
- case FormatConversionChar::g:
- case FormatConversionChar::A:
- case FormatConversionChar::E:
- case FormatConversionChar::F:
- case FormatConversionChar::G:
- return true;
- default:
- return false;
+ if (c == FormatConversionCharInternal::a ||
+ c == FormatConversionCharInternal::e ||
+ c == FormatConversionCharInternal::f ||
+ c == FormatConversionCharInternal::g ||
+ c == FormatConversionCharInternal::A ||
+ c == FormatConversionCharInternal::E ||
+ c == FormatConversionCharInternal::F ||
+ c == FormatConversionCharInternal::G) {
+ return true;
+ } else {
+ return false;
}
}
inline char FormatConversionCharToChar(FormatConversionChar c) {
- switch (c) {
-#define ABSL_INTERNAL_X_VAL(e) \
- case FormatConversionChar::e: \
+ if (c == FormatConversionCharInternal::kNone) {
+ return '\0';
+
+#define ABSL_INTERNAL_X_VAL(e) \
+ } else if (c == FormatConversionCharInternal::e) { \
return #e[0];
#define ABSL_INTERNAL_X_SEP
- ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_X_VAL,
- ABSL_INTERNAL_X_SEP)
- case FormatConversionChar::kNone:
- return '\0';
+ ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_X_VAL,
+ ABSL_INTERNAL_X_SEP)
+ } else {
+ return '\0';
+ }
+
#undef ABSL_INTERNAL_X_VAL
#undef ABSL_INTERNAL_X_SEP
- }
- return '\0';
}
// The associated char.
@@ -263,7 +254,7 @@ inline std::ostream& operator<<(std::ostream& os, FormatConversionChar v) {
struct FormatConversionSpecImplFriend;
-class FormatConversionSpec {
+class FormatConversionSpecImpl {
public:
// Width and precison are not specified, no flags are set.
bool is_basic() const { return flags_.basic; }
@@ -276,7 +267,7 @@ class FormatConversionSpec {
FormatConversionChar conversion_char() const {
// Keep this field first in the struct . It generates better code when
// accessing it when ConversionSpec is passed by value in registers.
- static_assert(offsetof(FormatConversionSpec, conv_) == 0, "");
+ static_assert(offsetof(FormatConversionSpecImpl, conv_) == 0, "");
return conv_;
}
@@ -287,41 +278,65 @@ class FormatConversionSpec {
// negative value.
int precision() const { return precision_; }
- // Deprecated (use has_x_flag() instead).
- Flags flags() const { return flags_; }
- // Deprecated
- FormatConversionChar conv() const { return conversion_char(); }
+ template <typename T>
+ T Wrap() {
+ return T(*this);
+ }
private:
friend struct str_format_internal::FormatConversionSpecImplFriend;
- FormatConversionChar conv_ = FormatConversionChar::kNone;
+ FormatConversionChar conv_ = FormatConversionCharInternal::kNone;
Flags flags_;
int width_;
int precision_;
};
struct FormatConversionSpecImplFriend final {
- static void SetFlags(Flags f, FormatConversionSpec* conv) {
+ static void SetFlags(Flags f, FormatConversionSpecImpl* conv) {
conv->flags_ = f;
}
static void SetConversionChar(FormatConversionChar c,
- FormatConversionSpec* conv) {
+ FormatConversionSpecImpl* conv) {
conv->conv_ = c;
}
- static void SetWidth(int w, FormatConversionSpec* conv) { conv->width_ = w; }
- static void SetPrecision(int p, FormatConversionSpec* conv) {
+ static void SetWidth(int w, FormatConversionSpecImpl* conv) {
+ conv->width_ = w;
+ }
+ static void SetPrecision(int p, FormatConversionSpecImpl* conv) {
conv->precision_ = p;
}
- static std::string FlagsToString(const FormatConversionSpec& spec) {
+ static std::string FlagsToString(const FormatConversionSpecImpl& spec) {
return spec.flags_.ToString();
}
};
-constexpr uint64_t FormatConversionCharToConvValue(char conv) {
+// Type safe OR operator.
+// We need this for two reasons:
+// 1. operator| on enums makes them decay to integers and the result is an
+// integer. We need the result to stay as an enum.
+// 2. We use "enum class" which would not work even if we accepted the decay.
+constexpr FormatConversionCharSet FormatConversionCharSetUnion(
+ FormatConversionCharSet a) {
+ return a;
+}
+
+template <typename... CharSet>
+constexpr FormatConversionCharSet FormatConversionCharSetUnion(
+ FormatConversionCharSet a, CharSet... rest) {
+ return static_cast<FormatConversionCharSet>(
+ static_cast<uint64_t>(a) |
+ static_cast<uint64_t>(FormatConversionCharSetUnion(rest...)));
+}
+
+constexpr uint64_t FormatConversionCharToConvInt(FormatConversionChar c) {
+ return uint64_t{1} << (1 + static_cast<uint8_t>(c));
+}
+
+constexpr uint64_t FormatConversionCharToConvInt(char conv) {
return
-#define ABSL_INTERNAL_CHAR_SET_CASE(c) \
- conv == #c[0] \
- ? (uint64_t{1} << (1 + static_cast<uint8_t>(FormatConversionChar::c))) \
+#define ABSL_INTERNAL_CHAR_SET_CASE(c) \
+ conv == #c[0] \
+ ? FormatConversionCharToConvInt(FormatConversionCharInternal::c) \
:
ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_CHAR_SET_CASE, )
#undef ABSL_INTERNAL_CHAR_SET_CASE
@@ -330,28 +345,29 @@ constexpr uint64_t FormatConversionCharToConvValue(char conv) {
: 0;
}
-enum class FormatConversionCharSet : uint64_t {
-#define ABSL_INTERNAL_CHAR_SET_CASE(c) \
- c = FormatConversionCharToConvValue(#c[0]),
+constexpr FormatConversionCharSet FormatConversionCharToConvValue(char conv) {
+ return static_cast<FormatConversionCharSet>(
+ FormatConversionCharToConvInt(conv));
+}
+
+struct FormatConversionCharSetInternal {
+#define ABSL_INTERNAL_CHAR_SET_CASE(c) \
+ static constexpr FormatConversionCharSet c = \
+ FormatConversionCharToConvValue(#c[0]);
ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_CHAR_SET_CASE, )
#undef ABSL_INTERNAL_CHAR_SET_CASE
// Used for width/precision '*' specification.
- kStar = FormatConversionCharToConvValue('*'),
- // Some predefined values:
- kIntegral = d | i | u | o | x | X,
- kFloating = a | e | f | g | A | E | F | G,
- kNumeric = kIntegral | kFloating,
- kString = s,
- kPointer = p,
-
- // The following are deprecated
- star = kStar,
- integral = kIntegral,
- floating = kFloating,
- numeric = kNumeric,
- string = kString,
- pointer = kPointer
+ static constexpr FormatConversionCharSet kStar =
+ FormatConversionCharToConvValue('*');
+
+ static constexpr FormatConversionCharSet kIntegral =
+ FormatConversionCharSetUnion(d, i, u, o, x, X);
+ static constexpr FormatConversionCharSet kFloating =
+ FormatConversionCharSetUnion(a, e, f, g, A, E, F, G);
+ static constexpr FormatConversionCharSet kNumeric =
+ FormatConversionCharSetUnion(kIntegral, kFloating);
+ static constexpr FormatConversionCharSet kPointer = p;
};
// Type safe OR operator.
@@ -361,18 +377,29 @@ enum class FormatConversionCharSet : uint64_t {
// 2. We use "enum class" which would not work even if we accepted the decay.
constexpr FormatConversionCharSet operator|(FormatConversionCharSet a,
FormatConversionCharSet b) {
- return FormatConversionCharSet(static_cast<uint64_t>(a) |
- static_cast<uint64_t>(b));
+ return FormatConversionCharSetUnion(a, b);
+}
+
+// Overloaded conversion functions to support absl::ParsedFormat.
+// Get a conversion with a single character in it.
+constexpr FormatConversionCharSet ToFormatConversionCharSet(char c) {
+ return static_cast<FormatConversionCharSet>(
+ FormatConversionCharToConvValue(c));
}
// Get a conversion with a single character in it.
-constexpr FormatConversionCharSet ConversionCharToConv(char c) {
- return FormatConversionCharSet(FormatConversionCharToConvValue(c));
+constexpr FormatConversionCharSet ToFormatConversionCharSet(
+ FormatConversionCharSet c) {
+ return c;
}
+template <typename T>
+void ToFormatConversionCharSet(T) = delete;
+
// Checks whether `c` exists in `set`.
constexpr bool Contains(FormatConversionCharSet set, char c) {
- return (static_cast<uint64_t>(set) & FormatConversionCharToConvValue(c)) != 0;
+ return (static_cast<uint64_t>(set) &
+ static_cast<uint64_t>(FormatConversionCharToConvValue(c))) != 0;
}
// Checks whether all the characters in `c` are contained in `set`
@@ -382,31 +409,16 @@ constexpr bool Contains(FormatConversionCharSet set,
static_cast<uint64_t>(c);
}
-// Return type of the AbslFormatConvert() functions.
-// The FormatConversionCharSet template parameter is used to inform the
-// framework of what conversion characters are supported by that
-// AbslFormatConvert routine.
-template <FormatConversionCharSet C>
-struct FormatConvertResult {
- static constexpr FormatConversionCharSet kConv = C;
- bool value;
-};
-
-template <FormatConversionCharSet C>
-constexpr FormatConversionCharSet FormatConvertResult<C>::kConv;
+// Checks whether all the characters in `c` are contained in `set`
+constexpr bool Contains(FormatConversionCharSet set, FormatConversionChar c) {
+ return (static_cast<uint64_t>(set) & FormatConversionCharToConvInt(c)) != 0;
+}
// Return capacity - used, clipped to a minimum of 0.
inline size_t Excess(size_t used, size_t capacity) {
return used < capacity ? capacity - used : 0;
}
-// Type alias for use during migration.
-using ConversionChar = FormatConversionChar;
-using ConversionSpec = FormatConversionSpec;
-using Conv = FormatConversionCharSet;
-template <FormatConversionCharSet C>
-using ConvertResult = FormatConvertResult<C>;
-
} // namespace str_format_internal
ABSL_NAMESPACE_END
diff --git a/absl/strings/internal/str_format/extension_test.cc b/absl/strings/internal/str_format/extension_test.cc
index 4e23fefb..1c93fdb1 100644
--- a/absl/strings/internal/str_format/extension_test.cc
+++ b/absl/strings/internal/str_format/extension_test.cc
@@ -19,9 +19,26 @@
#include <random>
#include <string>
+#include "gtest/gtest.h"
#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
-#include "gtest/gtest.h"
+namespace my_namespace {
+class UserDefinedType {
+ public:
+ UserDefinedType() = default;
+
+ void Append(absl::string_view str) { value_.append(str.data(), str.size()); }
+ const std::string& Value() const { return value_; }
+
+ friend void AbslFormatFlush(UserDefinedType* x, absl::string_view str) {
+ x->Append(str);
+ }
+
+ private:
+ std::string value_;
+};
+} // namespace my_namespace
namespace {
@@ -63,4 +80,19 @@ TEST(FormatExtensionTest, SinkAppendChars) {
EXPECT_EQ(actual, expected);
}
}
+
+TEST(FormatExtensionTest, VerifyEnumEquality) {
+#define X_VAL(id) \
+ EXPECT_EQ(absl::FormatConversionChar::id, \
+ absl::str_format_internal::FormatConversionCharInternal::id);
+ ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(X_VAL, );
+#undef X_VAL
+
+#define X_VAL(id) \
+ EXPECT_EQ(absl::FormatConversionCharSet::id, \
+ absl::str_format_internal::FormatConversionCharSetInternal::id);
+ ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(X_VAL, );
+#undef X_VAL
+}
+
} // namespace
diff --git a/absl/strings/internal/str_format/float_conversion.cc b/absl/strings/internal/str_format/float_conversion.cc
index d4c647c3..20aeada5 100644
--- a/absl/strings/internal/str_format/float_conversion.cc
+++ b/absl/strings/internal/str_format/float_conversion.cc
@@ -1,12 +1,23 @@
#include "absl/strings/internal/str_format/float_conversion.h"
#include <string.h>
+
#include <algorithm>
#include <cassert>
#include <cmath>
+#include <limits>
#include <string>
+#include "absl/base/attributes.h"
#include "absl/base/config.h"
+#include "absl/base/internal/bits.h"
+#include "absl/base/optimization.h"
+#include "absl/functional/function_ref.h"
+#include "absl/meta/type_traits.h"
+#include "absl/numeric/int128.h"
+#include "absl/strings/numbers.h"
+#include "absl/types/optional.h"
+#include "absl/types/span.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -14,13 +25,901 @@ namespace str_format_internal {
namespace {
-char *CopyStringTo(string_view v, char *out) {
+// The code below wants to avoid heap allocations.
+// To do so it needs to allocate memory on the stack.
+// `StackArray` will allocate memory on the stack in the form of a uint32_t
+// array and call the provided callback with said memory.
+// It will allocate memory in increments of 512 bytes. We could allocate the
+// largest needed unconditionally, but that is more than we need in most of
+// cases. This way we use less stack in the common cases.
+class StackArray {
+ using Func = absl::FunctionRef<void(absl::Span<uint32_t>)>;
+ static constexpr size_t kStep = 512 / sizeof(uint32_t);
+ // 5 steps is 2560 bytes, which is enough to hold a long double with the
+ // largest/smallest exponents.
+ // The operations below will static_assert their particular maximum.
+ static constexpr size_t kNumSteps = 5;
+
+ // We do not want this function to be inlined.
+ // Otherwise the caller will allocate the stack space unnecessarily for all
+ // the variants even though it only calls one.
+ template <size_t steps>
+ ABSL_ATTRIBUTE_NOINLINE static void RunWithCapacityImpl(Func f) {
+ uint32_t values[steps * kStep]{};
+ f(absl::MakeSpan(values));
+ }
+
+ public:
+ static constexpr size_t kMaxCapacity = kStep * kNumSteps;
+
+ static void RunWithCapacity(size_t capacity, Func f) {
+ assert(capacity <= kMaxCapacity);
+ const size_t step = (capacity + kStep - 1) / kStep;
+ assert(step <= kNumSteps);
+ switch (step) {
+ case 1:
+ return RunWithCapacityImpl<1>(f);
+ case 2:
+ return RunWithCapacityImpl<2>(f);
+ case 3:
+ return RunWithCapacityImpl<3>(f);
+ case 4:
+ return RunWithCapacityImpl<4>(f);
+ case 5:
+ return RunWithCapacityImpl<5>(f);
+ }
+
+ assert(false && "Invalid capacity");
+ }
+};
+
+// Calculates `10 * (*v) + carry` and stores the result in `*v` and returns
+// the carry.
+template <typename Int>
+inline Int MultiplyBy10WithCarry(Int *v, Int carry) {
+ using BiggerInt = absl::conditional_t<sizeof(Int) == 4, uint64_t, uint128>;
+ BiggerInt tmp = 10 * static_cast<BiggerInt>(*v) + carry;
+ *v = static_cast<Int>(tmp);
+ return static_cast<Int>(tmp >> (sizeof(Int) * 8));
+}
+
+// Calculates `(2^64 * carry + *v) / 10`.
+// Stores the quotient in `*v` and returns the remainder.
+// Requires: `0 <= carry <= 9`
+inline uint64_t DivideBy10WithCarry(uint64_t *v, uint64_t carry) {
+ constexpr uint64_t divisor = 10;
+ // 2^64 / divisor = chunk_quotient + chunk_remainder / divisor
+ constexpr uint64_t chunk_quotient = (uint64_t{1} << 63) / (divisor / 2);
+ constexpr uint64_t chunk_remainder = uint64_t{} - chunk_quotient * divisor;
+
+ const uint64_t mod = *v % divisor;
+ const uint64_t next_carry = chunk_remainder * carry + mod;
+ *v = *v / divisor + carry * chunk_quotient + next_carry / divisor;
+ return next_carry % divisor;
+}
+
+// Generates the decimal representation for an integer of the form `v * 2^exp`,
+// where `v` and `exp` are both positive integers.
+// It generates the digits from the left (ie the most significant digit first)
+// to allow for direct printing into the sink.
+//
+// Requires `0 <= exp` and `exp <= numeric_limits<long double>::max_exponent`.
+class BinaryToDecimal {
+ static constexpr int ChunksNeeded(int exp) {
+ // We will left shift a uint128 by `exp` bits, so we need `128+exp` total
+ // bits. Round up to 32.
+ // See constructor for details about adding `10%` to the value.
+ return (128 + exp + 31) / 32 * 11 / 10;
+ }
+
+ public:
+ // Run the conversion for `v * 2^exp` and call `f(binary_to_decimal)`.
+ // This function will allocate enough stack space to perform the conversion.
+ static void RunConversion(uint128 v, int exp,
+ absl::FunctionRef<void(BinaryToDecimal)> f) {
+ assert(exp > 0);
+ assert(exp <= std::numeric_limits<long double>::max_exponent);
+ static_assert(
+ StackArray::kMaxCapacity >=
+ ChunksNeeded(std::numeric_limits<long double>::max_exponent),
+ "");
+
+ StackArray::RunWithCapacity(
+ ChunksNeeded(exp),
+ [=](absl::Span<uint32_t> input) { f(BinaryToDecimal(input, v, exp)); });
+ }
+
+ int TotalDigits() const {
+ return static_cast<int>((decimal_end_ - decimal_start_) * kDigitsPerChunk +
+ CurrentDigits().size());
+ }
+
+ // See the current block of digits.
+ absl::string_view CurrentDigits() const {
+ return absl::string_view(digits_ + kDigitsPerChunk - size_, size_);
+ }
+
+ // Advance the current view of digits.
+ // Returns `false` when no more digits are available.
+ bool AdvanceDigits() {
+ if (decimal_start_ >= decimal_end_) return false;
+
+ uint32_t w = data_[decimal_start_++];
+ for (size_ = 0; size_ < kDigitsPerChunk; w /= 10) {
+ digits_[kDigitsPerChunk - ++size_] = w % 10 + '0';
+ }
+ return true;
+ }
+
+ private:
+ BinaryToDecimal(absl::Span<uint32_t> data, uint128 v, int exp) : data_(data) {
+ // We need to print the digits directly into the sink object without
+ // buffering them all first. To do this we need two things:
+ // - to know the total number of digits to do padding when necessary
+ // - to generate the decimal digits from the left.
+ //
+ // In order to do this, we do a two pass conversion.
+ // On the first pass we convert the binary representation of the value into
+ // a decimal representation in which each uint32_t chunk holds up to 9
+ // decimal digits. In the second pass we take each decimal-holding-uint32_t
+ // value and generate the ascii decimal digits into `digits_`.
+ //
+ // The binary and decimal representations actually share the same memory
+ // region. As we go converting the chunks from binary to decimal we free
+ // them up and reuse them for the decimal representation. One caveat is that
+ // the decimal representation is around 7% less efficient in space than the
+ // binary one. We allocate an extra 10% memory to account for this. See
+ // ChunksNeeded for this calculation.
+ int chunk_index = exp / 32;
+ decimal_start_ = decimal_end_ = ChunksNeeded(exp);
+ const int offset = exp % 32;
+ // Left shift v by exp bits.
+ data_[chunk_index] = static_cast<uint32_t>(v << offset);
+ for (v >>= (32 - offset); v; v >>= 32)
+ data_[++chunk_index] = static_cast<uint32_t>(v);
+
+ while (chunk_index >= 0) {
+ // While we have more than one chunk available, go in steps of 1e9.
+ // `data_[chunk_index]` holds the highest non-zero binary chunk, so keep
+ // the variable updated.
+ uint32_t carry = 0;
+ for (int i = chunk_index; i >= 0; --i) {
+ uint64_t tmp = uint64_t{data_[i]} + (uint64_t{carry} << 32);
+ data_[i] = static_cast<uint32_t>(tmp / uint64_t{1000000000});
+ carry = static_cast<uint32_t>(tmp % uint64_t{1000000000});
+ }
+
+ // If the highest chunk is now empty, remove it from view.
+ if (data_[chunk_index] == 0) --chunk_index;
+
+ --decimal_start_;
+ assert(decimal_start_ != chunk_index);
+ data_[decimal_start_] = carry;
+ }
+
+ // Fill the first set of digits. The first chunk might not be complete, so
+ // handle differently.
+ for (uint32_t first = data_[decimal_start_++]; first != 0; first /= 10) {
+ digits_[kDigitsPerChunk - ++size_] = first % 10 + '0';
+ }
+ }
+
+ private:
+ static constexpr int kDigitsPerChunk = 9;
+
+ int decimal_start_;
+ int decimal_end_;
+
+ char digits_[kDigitsPerChunk];
+ int size_ = 0;
+
+ absl::Span<uint32_t> data_;
+};
+
+// Converts a value of the form `x * 2^-exp` into a sequence of decimal digits.
+// Requires `-exp < 0` and
+// `-exp >= limits<long double>::min_exponent - limits<long double>::digits`.
+class FractionalDigitGenerator {
+ public:
+ // Run the conversion for `v * 2^exp` and call `f(generator)`.
+ // This function will allocate enough stack space to perform the conversion.
+ static void RunConversion(
+ uint128 v, int exp, absl::FunctionRef<void(FractionalDigitGenerator)> f) {
+ using Limits = std::numeric_limits<long double>;
+ assert(-exp < 0);
+ assert(-exp >= Limits::min_exponent - 128);
+ static_assert(StackArray::kMaxCapacity >=
+ (Limits::digits + 128 - Limits::min_exponent + 31) / 32,
+ "");
+ StackArray::RunWithCapacity((Limits::digits + exp + 31) / 32,
+ [=](absl::Span<uint32_t> input) {
+ f(FractionalDigitGenerator(input, v, exp));
+ });
+ }
+
+ // Returns true if there are any more non-zero digits left.
+ bool HasMoreDigits() const { return next_digit_ != 0 || chunk_index_ >= 0; }
+
+ // Returns true if the remainder digits are greater than 5000...
+ bool IsGreaterThanHalf() const {
+ return next_digit_ > 5 || (next_digit_ == 5 && chunk_index_ >= 0);
+ }
+ // Returns true if the remainder digits are exactly 5000...
+ bool IsExactlyHalf() const { return next_digit_ == 5 && chunk_index_ < 0; }
+
+ struct Digits {
+ int digit_before_nine;
+ int num_nines;
+ };
+
+ // Get the next set of digits.
+ // They are composed by a non-9 digit followed by a runs of zero or more 9s.
+ Digits GetDigits() {
+ Digits digits{next_digit_, 0};
+
+ next_digit_ = GetOneDigit();
+ while (next_digit_ == 9) {
+ ++digits.num_nines;
+ next_digit_ = GetOneDigit();
+ }
+
+ return digits;
+ }
+
+ private:
+ // Return the next digit.
+ int GetOneDigit() {
+ if (chunk_index_ < 0) return 0;
+
+ uint32_t carry = 0;
+ for (int i = chunk_index_; i >= 0; --i) {
+ carry = MultiplyBy10WithCarry(&data_[i], carry);
+ }
+ // If the lowest chunk is now empty, remove it from view.
+ if (data_[chunk_index_] == 0) --chunk_index_;
+ return carry;
+ }
+
+ FractionalDigitGenerator(absl::Span<uint32_t> data, uint128 v, int exp)
+ : chunk_index_(exp / 32), data_(data) {
+ const int offset = exp % 32;
+ // Right shift `v` by `exp` bits.
+ data_[chunk_index_] = static_cast<uint32_t>(v << (32 - offset));
+ v >>= offset;
+ // Make sure we don't overflow the data. We already calculated that
+ // non-zero bits fit, so we might not have space for leading zero bits.
+ for (int pos = chunk_index_; v; v >>= 32)
+ data_[--pos] = static_cast<uint32_t>(v);
+
+ // Fill next_digit_, as GetDigits expects it to be populated always.
+ next_digit_ = GetOneDigit();
+ }
+
+ int next_digit_;
+ int chunk_index_;
+ absl::Span<uint32_t> data_;
+};
+
+// Count the number of leading zero bits.
+int LeadingZeros(uint64_t v) { return base_internal::CountLeadingZeros64(v); }
+int LeadingZeros(uint128 v) {
+ auto high = static_cast<uint64_t>(v >> 64);
+ auto low = static_cast<uint64_t>(v);
+ return high != 0 ? base_internal::CountLeadingZeros64(high)
+ : 64 + base_internal::CountLeadingZeros64(low);
+}
+
+// Round up the text digits starting at `p`.
+// The buffer must have an extra digit that is known to not need rounding.
+// This is done below by having an extra '0' digit on the left.
+void RoundUp(char *p) {
+ while (*p == '9' || *p == '.') {
+ if (*p == '9') *p = '0';
+ --p;
+ }
+ ++*p;
+}
+
+// Check the previous digit and round up or down to follow the round-to-even
+// policy.
+void RoundToEven(char *p) {
+ if (*p == '.') --p;
+ if (*p % 2 == 1) RoundUp(p);
+}
+
+// Simple integral decimal digit printing for values that fit in 64-bits.
+// Returns the pointer to the last written digit.
+char *PrintIntegralDigitsFromRightFast(uint64_t v, char *p) {
+ do {
+ *--p = DivideBy10WithCarry(&v, 0) + '0';
+ } while (v != 0);
+ return p;
+}
+
+// Simple integral decimal digit printing for values that fit in 128-bits.
+// Returns the pointer to the last written digit.
+char *PrintIntegralDigitsFromRightFast(uint128 v, char *p) {
+ auto high = static_cast<uint64_t>(v >> 64);
+ auto low = static_cast<uint64_t>(v);
+
+ while (high != 0) {
+ uint64_t carry = DivideBy10WithCarry(&high, 0);
+ carry = DivideBy10WithCarry(&low, carry);
+ *--p = carry + '0';
+ }
+ return PrintIntegralDigitsFromRightFast(low, p);
+}
+
+// Simple fractional decimal digit printing for values that fir in 64-bits after
+// shifting.
+// Performs rounding if necessary to fit within `precision`.
+// Returns the pointer to one after the last character written.
+char *PrintFractionalDigitsFast(uint64_t v, char *start, int exp,
+ int precision) {
+ char *p = start;
+ v <<= (64 - exp);
+ while (precision > 0) {
+ if (!v) return p;
+ *p++ = MultiplyBy10WithCarry(&v, uint64_t{0}) + '0';
+ --precision;
+ }
+
+ // We need to round.
+ if (v < 0x8000000000000000) {
+ // We round down, so nothing to do.
+ } else if (v > 0x8000000000000000) {
+ // We round up.
+ RoundUp(p - 1);
+ } else {
+ RoundToEven(p - 1);
+ }
+
+ assert(precision == 0);
+ // Precision can only be zero here.
+ return p;
+}
+
+// Simple fractional decimal digit printing for values that fir in 128-bits
+// after shifting.
+// Performs rounding if necessary to fit within `precision`.
+// Returns the pointer to one after the last character written.
+char *PrintFractionalDigitsFast(uint128 v, char *start, int exp,
+ int precision) {
+ char *p = start;
+ v <<= (128 - exp);
+ auto high = static_cast<uint64_t>(v >> 64);
+ auto low = static_cast<uint64_t>(v);
+
+ // While we have digits to print and `low` is not empty, do the long
+ // multiplication.
+ while (precision > 0 && low != 0) {
+ uint64_t carry = MultiplyBy10WithCarry(&low, uint64_t{0});
+ carry = MultiplyBy10WithCarry(&high, carry);
+
+ *p++ = carry + '0';
+ --precision;
+ }
+
+ // Now `low` is empty, so use a faster approach for the rest of the digits.
+ // This block is pretty much the same as the main loop for the 64-bit case
+ // above.
+ while (precision > 0) {
+ if (!high) return p;
+ *p++ = MultiplyBy10WithCarry(&high, uint64_t{0}) + '0';
+ --precision;
+ }
+
+ // We need to round.
+ if (high < 0x8000000000000000) {
+ // We round down, so nothing to do.
+ } else if (high > 0x8000000000000000 || low != 0) {
+ // We round up.
+ RoundUp(p - 1);
+ } else {
+ RoundToEven(p - 1);
+ }
+
+ assert(precision == 0);
+ // Precision can only be zero here.
+ return p;
+}
+
+struct FormatState {
+ char sign_char;
+ int precision;
+ const FormatConversionSpecImpl &conv;
+ FormatSinkImpl *sink;
+
+ // In `alt` mode (flag #) we keep the `.` even if there are no fractional
+ // digits. In non-alt mode, we strip it.
+ bool ShouldPrintDot() const { return precision != 0 || conv.has_alt_flag(); }
+};
+
+struct Padding {
+ int left_spaces;
+ int zeros;
+ int right_spaces;
+};
+
+Padding ExtraWidthToPadding(size_t total_size, const FormatState &state) {
+ if (state.conv.width() < 0 ||
+ static_cast<size_t>(state.conv.width()) <= total_size) {
+ return {0, 0, 0};
+ }
+ int missing_chars = state.conv.width() - total_size;
+ if (state.conv.has_left_flag()) {
+ return {0, 0, missing_chars};
+ } else if (state.conv.has_zero_flag()) {
+ return {0, missing_chars, 0};
+ } else {
+ return {missing_chars, 0, 0};
+ }
+}
+
+void FinalPrint(const FormatState &state, absl::string_view data,
+ int padding_offset, int trailing_zeros,
+ absl::string_view data_postfix) {
+ if (state.conv.width() < 0) {
+ // No width specified. Fast-path.
+ if (state.sign_char != '\0') state.sink->Append(1, state.sign_char);
+ state.sink->Append(data);
+ state.sink->Append(trailing_zeros, '0');
+ state.sink->Append(data_postfix);
+ return;
+ }
+
+ auto padding = ExtraWidthToPadding((state.sign_char != '\0' ? 1 : 0) +
+ data.size() + data_postfix.size() +
+ static_cast<size_t>(trailing_zeros),
+ state);
+
+ state.sink->Append(padding.left_spaces, ' ');
+ if (state.sign_char != '\0') state.sink->Append(1, state.sign_char);
+ // Padding in general needs to be inserted somewhere in the middle of `data`.
+ state.sink->Append(data.substr(0, padding_offset));
+ state.sink->Append(padding.zeros, '0');
+ state.sink->Append(data.substr(padding_offset));
+ state.sink->Append(trailing_zeros, '0');
+ state.sink->Append(data_postfix);
+ state.sink->Append(padding.right_spaces, ' ');
+}
+
+// Fastpath %f formatter for when the shifted value fits in a simple integral
+// type.
+// Prints `v*2^exp` with the options from `state`.
+template <typename Int>
+void FormatFFast(Int v, int exp, const FormatState &state) {
+ constexpr int input_bits = sizeof(Int) * 8;
+
+ static constexpr size_t integral_size =
+ /* in case we need to round up an extra digit */ 1 +
+ /* decimal digits for uint128 */ 40 + 1;
+ char buffer[integral_size + /* . */ 1 + /* max digits uint128 */ 128];
+ buffer[integral_size] = '.';
+ char *const integral_digits_end = buffer + integral_size;
+ char *integral_digits_start;
+ char *const fractional_digits_start = buffer + integral_size + 1;
+ char *fractional_digits_end = fractional_digits_start;
+
+ if (exp >= 0) {
+ const int total_bits = input_bits - LeadingZeros(v) + exp;
+ integral_digits_start =
+ total_bits <= 64
+ ? PrintIntegralDigitsFromRightFast(static_cast<uint64_t>(v) << exp,
+ integral_digits_end)
+ : PrintIntegralDigitsFromRightFast(static_cast<uint128>(v) << exp,
+ integral_digits_end);
+ } else {
+ exp = -exp;
+
+ integral_digits_start = PrintIntegralDigitsFromRightFast(
+ exp < input_bits ? v >> exp : 0, integral_digits_end);
+ // PrintFractionalDigits may pull a carried 1 all the way up through the
+ // integral portion.
+ integral_digits_start[-1] = '0';
+
+ fractional_digits_end =
+ exp <= 64 ? PrintFractionalDigitsFast(v, fractional_digits_start, exp,
+ state.precision)
+ : PrintFractionalDigitsFast(static_cast<uint128>(v),
+ fractional_digits_start, exp,
+ state.precision);
+ // There was a carry, so include the first digit too.
+ if (integral_digits_start[-1] != '0') --integral_digits_start;
+ }
+
+ size_t size = fractional_digits_end - integral_digits_start;
+
+ // In `alt` mode (flag #) we keep the `.` even if there are no fractional
+ // digits. In non-alt mode, we strip it.
+ if (!state.ShouldPrintDot()) --size;
+ FinalPrint(state, absl::string_view(integral_digits_start, size),
+ /*padding_offset=*/0,
+ static_cast<int>(state.precision - (fractional_digits_end -
+ fractional_digits_start)),
+ /*data_postfix=*/"");
+}
+
+// Slow %f formatter for when the shifted value does not fit in a uint128, and
+// `exp > 0`.
+// Prints `v*2^exp` with the options from `state`.
+// This one is guaranteed to not have fractional digits, so we don't have to
+// worry about anything after the `.`.
+void FormatFPositiveExpSlow(uint128 v, int exp, const FormatState &state) {
+ BinaryToDecimal::RunConversion(v, exp, [&](BinaryToDecimal btd) {
+ const size_t total_digits =
+ btd.TotalDigits() +
+ (state.ShouldPrintDot() ? static_cast<size_t>(state.precision) + 1 : 0);
+
+ const auto padding = ExtraWidthToPadding(
+ total_digits + (state.sign_char != '\0' ? 1 : 0), state);
+
+ state.sink->Append(padding.left_spaces, ' ');
+ if (state.sign_char != '\0') state.sink->Append(1, state.sign_char);
+ state.sink->Append(padding.zeros, '0');
+
+ do {
+ state.sink->Append(btd.CurrentDigits());
+ } while (btd.AdvanceDigits());
+
+ if (state.ShouldPrintDot()) state.sink->Append(1, '.');
+ state.sink->Append(state.precision, '0');
+ state.sink->Append(padding.right_spaces, ' ');
+ });
+}
+
+// Slow %f formatter for when the shifted value does not fit in a uint128, and
+// `exp < 0`.
+// Prints `v*2^exp` with the options from `state`.
+// This one is guaranteed to be < 1.0, so we don't have to worry about integral
+// digits.
+void FormatFNegativeExpSlow(uint128 v, int exp, const FormatState &state) {
+ const size_t total_digits =
+ /* 0 */ 1 +
+ (state.ShouldPrintDot() ? static_cast<size_t>(state.precision) + 1 : 0);
+ auto padding =
+ ExtraWidthToPadding(total_digits + (state.sign_char ? 1 : 0), state);
+ padding.zeros += 1;
+ state.sink->Append(padding.left_spaces, ' ');
+ if (state.sign_char != '\0') state.sink->Append(1, state.sign_char);
+ state.sink->Append(padding.zeros, '0');
+
+ if (state.ShouldPrintDot()) state.sink->Append(1, '.');
+
+ // Print digits
+ int digits_to_go = state.precision;
+
+ FractionalDigitGenerator::RunConversion(
+ v, exp, [&](FractionalDigitGenerator digit_gen) {
+ // There are no digits to print here.
+ if (state.precision == 0) return;
+
+ // We go one digit at a time, while keeping track of runs of nines.
+ // The runs of nines are used to perform rounding when necessary.
+
+ while (digits_to_go > 0 && digit_gen.HasMoreDigits()) {
+ auto digits = digit_gen.GetDigits();
+
+ // Now we have a digit and a run of nines.
+ // See if we can print them all.
+ if (digits.num_nines + 1 < digits_to_go) {
+ // We don't have to round yet, so print them.
+ state.sink->Append(1, digits.digit_before_nine + '0');
+ state.sink->Append(digits.num_nines, '9');
+ digits_to_go -= digits.num_nines + 1;
+
+ } else {
+ // We can't print all the nines, see where we have to truncate.
+
+ bool round_up = false;
+ if (digits.num_nines + 1 > digits_to_go) {
+ // We round up at a nine. No need to print them.
+ round_up = true;
+ } else {
+ // We can fit all the nines, but truncate just after it.
+ if (digit_gen.IsGreaterThanHalf()) {
+ round_up = true;
+ } else if (digit_gen.IsExactlyHalf()) {
+ // Round to even
+ round_up =
+ digits.num_nines != 0 || digits.digit_before_nine % 2 == 1;
+ }
+ }
+
+ if (round_up) {
+ state.sink->Append(1, digits.digit_before_nine + '1');
+ --digits_to_go;
+ // The rest will be zeros.
+ } else {
+ state.sink->Append(1, digits.digit_before_nine + '0');
+ state.sink->Append(digits_to_go - 1, '9');
+ digits_to_go = 0;
+ }
+ return;
+ }
+ }
+ });
+
+ state.sink->Append(digits_to_go, '0');
+ state.sink->Append(padding.right_spaces, ' ');
+}
+
+template <typename Int>
+void FormatF(Int mantissa, int exp, const FormatState &state) {
+ if (exp >= 0) {
+ const int total_bits = sizeof(Int) * 8 - LeadingZeros(mantissa) + exp;
+
+ // Fallback to the slow stack-based approach if we can't do it in a 64 or
+ // 128 bit state.
+ if (ABSL_PREDICT_FALSE(total_bits > 128)) {
+ return FormatFPositiveExpSlow(mantissa, exp, state);
+ }
+ } else {
+ // Fallback to the slow stack-based approach if we can't do it in a 64 or
+ // 128 bit state.
+ if (ABSL_PREDICT_FALSE(exp < -128)) {
+ return FormatFNegativeExpSlow(mantissa, -exp, state);
+ }
+ }
+ return FormatFFast(mantissa, exp, state);
+}
+
+// Grab the group of four bits (nibble) from `n`. E.g., nibble 1 corresponds to
+// bits 4-7.
+template <typename Int>
+uint8_t GetNibble(Int n, int nibble_index) {
+ constexpr Int mask_low_nibble = Int{0xf};
+ int shift = nibble_index * 4;
+ n &= mask_low_nibble << shift;
+ return static_cast<uint8_t>((n >> shift) & 0xf);
+}
+
+// Add one to the given nibble, applying carry to higher nibbles. Returns true
+// if overflow, false otherwise.
+template <typename Int>
+bool IncrementNibble(int nibble_index, Int *n) {
+ constexpr int kShift = sizeof(Int) * 8 - 1;
+ constexpr int kNumNibbles = sizeof(Int) * 8 / 4;
+ Int before = *n >> kShift;
+ // Here we essentially want to take the number 1 and move it into the requsted
+ // nibble, then add it to *n to effectively increment the nibble. However,
+ // ASan will complain if we try to shift the 1 beyond the limits of the Int,
+ // i.e., if the nibble_index is out of range. So therefore we check for this
+ // and if we are out of range we just add 0 which leaves *n unchanged, which
+ // seems like the reasonable thing to do in that case.
+ *n += ((nibble_index >= kNumNibbles) ? 0 : (Int{1} << (nibble_index * 4)));
+ Int after = *n >> kShift;
+ return (before && !after) || (nibble_index >= kNumNibbles);
+}
+
+// Return a mask with 1's in the given nibble and all lower nibbles.
+template <typename Int>
+Int MaskUpToNibbleInclusive(int nibble_index) {
+ constexpr int kNumNibbles = sizeof(Int) * 8 / 4;
+ static const Int ones = ~Int{0};
+ return ones >> std::max(0, 4 * (kNumNibbles - nibble_index - 1));
+}
+
+// Return a mask with 1's below the given nibble.
+template <typename Int>
+Int MaskUpToNibbleExclusive(int nibble_index) {
+ return nibble_index <= 0 ? 0 : MaskUpToNibbleInclusive<Int>(nibble_index - 1);
+}
+
+template <typename Int>
+Int MoveToNibble(uint8_t nibble, int nibble_index) {
+ return Int{nibble} << (4 * nibble_index);
+}
+
+// Given mantissa size, find optimal # of mantissa bits to put in initial digit.
+//
+// In the hex representation we keep a single hex digit to the left of the dot.
+// However, the question as to how many bits of the mantissa should be put into
+// that hex digit in theory is arbitrary, but in practice it is optimal to
+// choose based on the size of the mantissa. E.g., for a `double`, there are 53
+// mantissa bits, so that means that we should put 1 bit to the left of the dot,
+// thereby leaving 52 bits to the right, which is evenly divisible by four and
+// thus all fractional digits represent actual precision. For a `long double`,
+// on the other hand, there are 64 bits of mantissa, thus we can use all four
+// bits for the initial hex digit and still have a number left over (60) that is
+// a multiple of four. Once again, the goal is to have all fractional digits
+// represent real precision.
+template <typename Float>
+constexpr int HexFloatLeadingDigitSizeInBits() {
+ return std::numeric_limits<Float>::digits % 4 > 0
+ ? std::numeric_limits<Float>::digits % 4
+ : 4;
+}
+
+// This function captures the rounding behavior of glibc for hex float
+// representations. E.g. when rounding 0x1.ab800000 to a precision of .2
+// ("%.2a") glibc will round up because it rounds toward the even number (since
+// 0xb is an odd number, it will round up to 0xc). However, when rounding at a
+// point that is not followed by 800000..., it disregards the parity and rounds
+// up if > 8 and rounds down if < 8.
+template <typename Int>
+bool HexFloatNeedsRoundUp(Int mantissa, int final_nibble_displayed,
+ uint8_t leading) {
+ // If the last nibble (hex digit) to be displayed is the lowest on in the
+ // mantissa then that means that we don't have any further nibbles to inform
+ // rounding, so don't round.
+ if (final_nibble_displayed <= 0) {
+ return false;
+ }
+ int rounding_nibble_idx = final_nibble_displayed - 1;
+ constexpr int kTotalNibbles = sizeof(Int) * 8 / 4;
+ assert(final_nibble_displayed <= kTotalNibbles);
+ Int mantissa_up_to_rounding_nibble_inclusive =
+ mantissa & MaskUpToNibbleInclusive<Int>(rounding_nibble_idx);
+ Int eight = MoveToNibble<Int>(8, rounding_nibble_idx);
+ if (mantissa_up_to_rounding_nibble_inclusive != eight) {
+ return mantissa_up_to_rounding_nibble_inclusive > eight;
+ }
+ // Nibble in question == 8.
+ uint8_t round_if_odd = (final_nibble_displayed == kTotalNibbles)
+ ? leading
+ : GetNibble(mantissa, final_nibble_displayed);
+ return round_if_odd % 2 == 1;
+}
+
+// Stores values associated with a Float type needed by the FormatA
+// implementation in order to avoid templatizing that function by the Float
+// type.
+struct HexFloatTypeParams {
+ template <typename Float>
+ explicit HexFloatTypeParams(Float)
+ : min_exponent(std::numeric_limits<Float>::min_exponent - 1),
+ leading_digit_size_bits(HexFloatLeadingDigitSizeInBits<Float>()) {
+ assert(leading_digit_size_bits >= 1 && leading_digit_size_bits <= 4);
+ }
+
+ int min_exponent;
+ int leading_digit_size_bits;
+};
+
+// Hex Float Rounding. First check if we need to round; if so, then we do that
+// by manipulating (incrementing) the mantissa, that way we can later print the
+// mantissa digits by iterating through them in the same way regardless of
+// whether a rounding happened.
+template <typename Int>
+void FormatARound(bool precision_specified, const FormatState &state,
+ uint8_t *leading, Int *mantissa, int *exp) {
+ constexpr int kTotalNibbles = sizeof(Int) * 8 / 4;
+ // Index of the last nibble that we could display given precision.
+ int final_nibble_displayed =
+ precision_specified ? std::max(0, (kTotalNibbles - state.precision)) : 0;
+ if (HexFloatNeedsRoundUp(*mantissa, final_nibble_displayed, *leading)) {
+ // Need to round up.
+ bool overflow = IncrementNibble(final_nibble_displayed, mantissa);
+ *leading += (overflow ? 1 : 0);
+ if (ABSL_PREDICT_FALSE(*leading > 15)) {
+ // We have overflowed the leading digit. This would mean that we would
+ // need two hex digits to the left of the dot, which is not allowed. So
+ // adjust the mantissa and exponent so that the result is always 1.0eXXX.
+ *leading = 1;
+ *mantissa = 0;
+ *exp += 4;
+ }
+ }
+ // Now that we have handled a possible round-up we can go ahead and zero out
+ // all the nibbles of the mantissa that we won't need.
+ if (precision_specified) {
+ *mantissa &= ~MaskUpToNibbleExclusive<Int>(final_nibble_displayed);
+ }
+}
+
+template <typename Int>
+void FormatANormalize(const HexFloatTypeParams float_traits, uint8_t *leading,
+ Int *mantissa, int *exp) {
+ constexpr int kIntBits = sizeof(Int) * 8;
+ static const Int kHighIntBit = Int{1} << (kIntBits - 1);
+ const int kLeadDigitBitsCount = float_traits.leading_digit_size_bits;
+ // Normalize mantissa so that highest bit set is in MSB position, unless we
+ // get interrupted by the exponent threshold.
+ while (*mantissa && !(*mantissa & kHighIntBit)) {
+ if (ABSL_PREDICT_FALSE(*exp - 1 < float_traits.min_exponent)) {
+ *mantissa >>= (float_traits.min_exponent - *exp);
+ *exp = float_traits.min_exponent;
+ return;
+ }
+ *mantissa <<= 1;
+ --*exp;
+ }
+ // Extract bits for leading digit then shift them away leaving the
+ // fractional part.
+ *leading =
+ static_cast<uint8_t>(*mantissa >> (kIntBits - kLeadDigitBitsCount));
+ *exp -= (*mantissa != 0) ? kLeadDigitBitsCount : *exp;
+ *mantissa <<= kLeadDigitBitsCount;
+}
+
+template <typename Int>
+void FormatA(const HexFloatTypeParams float_traits, Int mantissa, int exp,
+ bool uppercase, const FormatState &state) {
+ // Int properties.
+ constexpr int kIntBits = sizeof(Int) * 8;
+ constexpr int kTotalNibbles = sizeof(Int) * 8 / 4;
+ // Did the user specify a precision explicitly?
+ const bool precision_specified = state.conv.precision() >= 0;
+
+ // ========== Normalize/Denormalize ==========
+ exp += kIntBits; // make all digits fractional digits.
+ // This holds the (up to four) bits of leading digit, i.e., the '1' in the
+ // number 0x1.e6fp+2. It's always > 0 unless number is zero or denormal.
+ uint8_t leading = 0;
+ FormatANormalize(float_traits, &leading, &mantissa, &exp);
+
+ // =============== Rounding ==================
+ // Check if we need to round; if so, then we do that by manipulating
+ // (incrementing) the mantissa before beginning to print characters.
+ FormatARound(precision_specified, state, &leading, &mantissa, &exp);
+
+ // ============= Format Result ===============
+ // This buffer holds the "0x1.ab1de3" portion of "0x1.ab1de3pe+2". Compute the
+ // size with long double which is the largest of the floats.
+ constexpr size_t kBufSizeForHexFloatRepr =
+ 2 // 0x
+ + std::numeric_limits<long double>::digits / 4 // number of hex digits
+ + 1 // round up
+ + 1; // "." (dot)
+ char digits_buffer[kBufSizeForHexFloatRepr];
+ char *digits_iter = digits_buffer;
+ const char *const digits =
+ static_cast<const char *>("0123456789ABCDEF0123456789abcdef") +
+ (uppercase ? 0 : 16);
+
+ // =============== Hex Prefix ================
+ *digits_iter++ = '0';
+ *digits_iter++ = uppercase ? 'X' : 'x';
+
+ // ========== Non-Fractional Digit ===========
+ *digits_iter++ = digits[leading];
+
+ // ================== Dot ====================
+ // There are three reasons we might need a dot. Keep in mind that, at this
+ // point, the mantissa holds only the fractional part.
+ if ((precision_specified && state.precision > 0) ||
+ (!precision_specified && mantissa > 0) || state.conv.has_alt_flag()) {
+ *digits_iter++ = '.';
+ }
+
+ // ============ Fractional Digits ============
+ int digits_emitted = 0;
+ while (mantissa > 0) {
+ *digits_iter++ = digits[GetNibble(mantissa, kTotalNibbles - 1)];
+ mantissa <<= 4;
+ ++digits_emitted;
+ }
+ int trailing_zeros =
+ precision_specified ? state.precision - digits_emitted : 0;
+ assert(trailing_zeros >= 0);
+ auto digits_result = string_view(digits_buffer, digits_iter - digits_buffer);
+
+ // =============== Exponent ==================
+ constexpr size_t kBufSizeForExpDecRepr =
+ numbers_internal::kFastToBufferSize // requred for FastIntToBuffer
+ + 1 // 'p' or 'P'
+ + 1; // '+' or '-'
+ char exp_buffer[kBufSizeForExpDecRepr];
+ exp_buffer[0] = uppercase ? 'P' : 'p';
+ exp_buffer[1] = exp >= 0 ? '+' : '-';
+ numbers_internal::FastIntToBuffer(exp < 0 ? -exp : exp, exp_buffer + 2);
+
+ // ============ Assemble Result ==============
+ FinalPrint(state, //
+ digits_result, // 0xN.NNN...
+ 2, // offset in `data` to start padding if needed.
+ trailing_zeros, // num remaining mantissa padding zeros
+ exp_buffer); // exponent
+}
+
+char *CopyStringTo(absl::string_view v, char *out) {
std::memcpy(out, v.data(), v.size());
return out + v.size();
}
template <typename Float>
-bool FallbackToSnprintf(const Float v, const ConversionSpec &conv,
+bool FallbackToSnprintf(const Float v, const FormatConversionSpecImpl &conv,
FormatSinkImpl *sink) {
int w = conv.width() >= 0 ? conv.width() : 0;
int p = conv.precision() >= 0 ? conv.precision() : -1;
@@ -33,17 +932,17 @@ bool FallbackToSnprintf(const Float v, const ConversionSpec &conv,
if (std::is_same<long double, Float>()) {
*fp++ = 'L';
}
- *fp++ = FormatConversionCharToChar(conv.conv());
+ *fp++ = FormatConversionCharToChar(conv.conversion_char());
*fp = 0;
assert(fp < fmt + sizeof(fmt));
}
std::string space(512, '\0');
- string_view result;
+ absl::string_view result;
while (true) {
int n = snprintf(&space[0], space.size(), fmt, w, p, v);
if (n < 0) return false;
if (static_cast<size_t>(n) < space.size()) {
- result = string_view(space.data(), n);
+ result = absl::string_view(space.data(), n);
break;
}
space.resize(n + 1);
@@ -96,21 +995,24 @@ enum class FormatStyle { Fixed, Precision };
// Otherwise, return false.
template <typename Float>
bool ConvertNonNumericFloats(char sign_char, Float v,
- const ConversionSpec &conv, FormatSinkImpl *sink) {
+ const FormatConversionSpecImpl &conv,
+ FormatSinkImpl *sink) {
char text[4], *ptr = text;
- if (sign_char) *ptr++ = sign_char;
+ if (sign_char != '\0') *ptr++ = sign_char;
if (std::isnan(v)) {
- ptr = std::copy_n(FormatConversionCharIsUpper(conv.conv()) ? "NAN" : "nan",
- 3, ptr);
+ ptr = std::copy_n(
+ FormatConversionCharIsUpper(conv.conversion_char()) ? "NAN" : "nan", 3,
+ ptr);
} else if (std::isinf(v)) {
- ptr = std::copy_n(FormatConversionCharIsUpper(conv.conv()) ? "INF" : "inf",
- 3, ptr);
+ ptr = std::copy_n(
+ FormatConversionCharIsUpper(conv.conversion_char()) ? "INF" : "inf", 3,
+ ptr);
} else {
return false;
}
return sink->PutPaddedString(string_view(text, ptr - text), conv.width(), -1,
- conv.flags().left);
+ conv.has_left_flag());
}
// Round up the last digit of the value.
@@ -170,7 +1072,12 @@ constexpr bool CanFitMantissa() {
template <typename Float>
struct Decomposed {
- Float mantissa;
+ using MantissaType =
+ absl::conditional_t<std::is_same<long double, Float>::value, uint128,
+ uint64_t>;
+ static_assert(std::numeric_limits<Float>::digits <= sizeof(MantissaType) * 8,
+ "");
+ MantissaType mantissa;
int exponent;
};
@@ -181,7 +1088,8 @@ Decomposed<Float> Decompose(Float v) {
Float m = std::frexp(v, &exp);
m = std::ldexp(m, std::numeric_limits<Float>::digits);
exp -= std::numeric_limits<Float>::digits;
- return {m, exp};
+
+ return {static_cast<typename Decomposed<Float>::MantissaType>(m), exp};
}
// Print 'digits' as decimal.
@@ -350,31 +1258,32 @@ bool FloatToBuffer(Decomposed<Float> decomposed, int precision, Buffer *out,
return false;
}
-void WriteBufferToSink(char sign_char, string_view str,
- const ConversionSpec &conv, FormatSinkImpl *sink) {
+void WriteBufferToSink(char sign_char, absl::string_view str,
+ const FormatConversionSpecImpl &conv,
+ FormatSinkImpl *sink) {
int left_spaces = 0, zeros = 0, right_spaces = 0;
int missing_chars =
conv.width() >= 0 ? std::max(conv.width() - static_cast<int>(str.size()) -
static_cast<int>(sign_char != 0),
0)
: 0;
- if (conv.flags().left) {
+ if (conv.has_left_flag()) {
right_spaces = missing_chars;
- } else if (conv.flags().zero) {
+ } else if (conv.has_zero_flag()) {
zeros = missing_chars;
} else {
left_spaces = missing_chars;
}
sink->Append(left_spaces, ' ');
- if (sign_char) sink->Append(1, sign_char);
+ if (sign_char != '\0') sink->Append(1, sign_char);
sink->Append(zeros, '0');
sink->Append(str);
sink->Append(right_spaces, ' ');
}
template <typename Float>
-bool FloatToSink(const Float v, const ConversionSpec &conv,
+bool FloatToSink(const Float v, const FormatConversionSpecImpl &conv,
FormatSinkImpl *sink) {
// Print the sign or the sign column.
Float abs_v = v;
@@ -382,9 +1291,9 @@ bool FloatToSink(const Float v, const ConversionSpec &conv,
if (std::signbit(abs_v)) {
sign_char = '-';
abs_v = -abs_v;
- } else if (conv.flags().show_pos) {
+ } else if (conv.has_show_pos_flag()) {
sign_char = '+';
- } else if (conv.flags().sign_col) {
+ } else if (conv.has_sign_col_flag()) {
sign_char = ' ';
}
@@ -401,89 +1310,92 @@ bool FloatToSink(const Float v, const ConversionSpec &conv,
Buffer buffer;
- switch (conv.conv()) {
- case ConversionChar::f:
- case ConversionChar::F:
- if (!FloatToBuffer<FormatStyle::Fixed>(decomposed, precision, &buffer,
- nullptr)) {
- return FallbackToSnprintf(v, conv, sink);
- }
- if (!conv.flags().alt && buffer.back() == '.') buffer.pop_back();
- break;
-
- case ConversionChar::e:
- case ConversionChar::E:
- if (!FloatToBuffer<FormatStyle::Precision>(decomposed, precision, &buffer,
- &exp)) {
- return FallbackToSnprintf(v, conv, sink);
- }
- if (!conv.flags().alt && buffer.back() == '.') buffer.pop_back();
- PrintExponent(exp, FormatConversionCharIsUpper(conv.conv()) ? 'E' : 'e',
- &buffer);
- break;
-
- case ConversionChar::g:
- case ConversionChar::G:
- precision = std::max(0, precision - 1);
- if (!FloatToBuffer<FormatStyle::Precision>(decomposed, precision, &buffer,
- &exp)) {
- return FallbackToSnprintf(v, conv, sink);
- }
- if (precision + 1 > exp && exp >= -4) {
- if (exp < 0) {
- // Have 1.23456, needs 0.00123456
- // Move the first digit
- buffer.begin[1] = *buffer.begin;
- // Add some zeros
- for (; exp < -1; ++exp) *buffer.begin-- = '0';
- *buffer.begin-- = '.';
- *buffer.begin = '0';
- } else if (exp > 0) {
- // Have 1.23456, needs 1234.56
- // Move the '.' exp positions to the right.
- std::rotate(buffer.begin + 1, buffer.begin + 2,
- buffer.begin + exp + 2);
- }
- exp = 0;
- }
- if (!conv.flags().alt) {
- while (buffer.back() == '0') buffer.pop_back();
- if (buffer.back() == '.') buffer.pop_back();
- }
- if (exp) {
- PrintExponent(exp, FormatConversionCharIsUpper(conv.conv()) ? 'E' : 'e',
- &buffer);
- }
- break;
+ FormatConversionChar c = conv.conversion_char();
- case ConversionChar::a:
- case ConversionChar::A:
+ if (c == FormatConversionCharInternal::f ||
+ c == FormatConversionCharInternal::F) {
+ FormatF(decomposed.mantissa, decomposed.exponent,
+ {sign_char, precision, conv, sink});
+ return true;
+ } else if (c == FormatConversionCharInternal::e ||
+ c == FormatConversionCharInternal::E) {
+ if (!FloatToBuffer<FormatStyle::Precision>(decomposed, precision, &buffer,
+ &exp)) {
return FallbackToSnprintf(v, conv, sink);
-
- default:
- return false;
+ }
+ if (!conv.has_alt_flag() && buffer.back() == '.') buffer.pop_back();
+ PrintExponent(
+ exp, FormatConversionCharIsUpper(conv.conversion_char()) ? 'E' : 'e',
+ &buffer);
+ } else if (c == FormatConversionCharInternal::g ||
+ c == FormatConversionCharInternal::G) {
+ precision = std::max(0, precision - 1);
+ if (!FloatToBuffer<FormatStyle::Precision>(decomposed, precision, &buffer,
+ &exp)) {
+ return FallbackToSnprintf(v, conv, sink);
+ }
+ if (precision + 1 > exp && exp >= -4) {
+ if (exp < 0) {
+ // Have 1.23456, needs 0.00123456
+ // Move the first digit
+ buffer.begin[1] = *buffer.begin;
+ // Add some zeros
+ for (; exp < -1; ++exp) *buffer.begin-- = '0';
+ *buffer.begin-- = '.';
+ *buffer.begin = '0';
+ } else if (exp > 0) {
+ // Have 1.23456, needs 1234.56
+ // Move the '.' exp positions to the right.
+ std::rotate(buffer.begin + 1, buffer.begin + 2, buffer.begin + exp + 2);
+ }
+ exp = 0;
+ }
+ if (!conv.has_alt_flag()) {
+ while (buffer.back() == '0') buffer.pop_back();
+ if (buffer.back() == '.') buffer.pop_back();
+ }
+ if (exp) {
+ PrintExponent(
+ exp, FormatConversionCharIsUpper(conv.conversion_char()) ? 'E' : 'e',
+ &buffer);
+ }
+ } else if (c == FormatConversionCharInternal::a ||
+ c == FormatConversionCharInternal::A) {
+ bool uppercase = (c == FormatConversionCharInternal::A);
+ FormatA(HexFloatTypeParams(Float{}), decomposed.mantissa,
+ decomposed.exponent, uppercase, {sign_char, precision, conv, sink});
+ return true;
+ } else {
+ return false;
}
WriteBufferToSink(sign_char,
- string_view(buffer.begin, buffer.end - buffer.begin), conv,
- sink);
+ absl::string_view(buffer.begin, buffer.end - buffer.begin),
+ conv, sink);
return true;
}
} // namespace
-bool ConvertFloatImpl(long double v, const ConversionSpec &conv,
+bool ConvertFloatImpl(long double v, const FormatConversionSpecImpl &conv,
FormatSinkImpl *sink) {
+ if (std::numeric_limits<long double>::digits ==
+ 2 * std::numeric_limits<double>::digits) {
+ // This is the `double-double` representation of `long double`.
+ // We do not handle it natively. Fallback to snprintf.
+ return FallbackToSnprintf(v, conv, sink);
+ }
+
return FloatToSink(v, conv, sink);
}
-bool ConvertFloatImpl(float v, const ConversionSpec &conv,
+bool ConvertFloatImpl(float v, const FormatConversionSpecImpl &conv,
FormatSinkImpl *sink) {
- return FloatToSink(v, conv, sink);
+ return FloatToSink(static_cast<double>(v), conv, sink);
}
-bool ConvertFloatImpl(double v, const ConversionSpec &conv,
+bool ConvertFloatImpl(double v, const FormatConversionSpecImpl &conv,
FormatSinkImpl *sink) {
return FloatToSink(v, conv, sink);
}
diff --git a/absl/strings/internal/str_format/float_conversion.h b/absl/strings/internal/str_format/float_conversion.h
index 49a6a636..e78bc191 100644
--- a/absl/strings/internal/str_format/float_conversion.h
+++ b/absl/strings/internal/str_format/float_conversion.h
@@ -7,13 +7,13 @@ namespace absl {
ABSL_NAMESPACE_BEGIN
namespace str_format_internal {
-bool ConvertFloatImpl(float v, const ConversionSpec &conv,
+bool ConvertFloatImpl(float v, const FormatConversionSpecImpl &conv,
FormatSinkImpl *sink);
-bool ConvertFloatImpl(double v, const ConversionSpec &conv,
+bool ConvertFloatImpl(double v, const FormatConversionSpecImpl &conv,
FormatSinkImpl *sink);
-bool ConvertFloatImpl(long double v, const ConversionSpec &conv,
+bool ConvertFloatImpl(long double v, const FormatConversionSpecImpl &conv,
FormatSinkImpl *sink);
} // namespace str_format_internal
diff --git a/absl/strings/internal/str_format/output.h b/absl/strings/internal/str_format/output.h
index 28b288b7..8030dae0 100644
--- a/absl/strings/internal/str_format/output.h
+++ b/absl/strings/internal/str_format/output.h
@@ -30,9 +30,6 @@
namespace absl {
ABSL_NAMESPACE_BEGIN
-
-class Cord;
-
namespace str_format_internal {
// RawSink implementation that writes into a char* buffer.
@@ -77,12 +74,6 @@ inline void AbslFormatFlush(std::ostream* out, string_view s) {
out->write(s.data(), s.size());
}
-template <class AbslCord, typename = typename std::enable_if<
- std::is_same<AbslCord, absl::Cord>::value>::type>
-inline void AbslFormatFlush(AbslCord* out, string_view s) {
- out->Append(s);
-}
-
inline void AbslFormatFlush(FILERawSink* sink, string_view v) {
sink->Write(v);
}
@@ -91,10 +82,11 @@ inline void AbslFormatFlush(BufferRawSink* sink, string_view v) {
sink->Write(v);
}
+// This is a SFINAE to get a better compiler error message when the type
+// is not supported.
template <typename T>
-auto InvokeFlush(T* out, string_view s)
- -> decltype(str_format_internal::AbslFormatFlush(out, s)) {
- str_format_internal::AbslFormatFlush(out, s);
+auto InvokeFlush(T* out, string_view s) -> decltype(AbslFormatFlush(out, s)) {
+ AbslFormatFlush(out, s);
}
} // namespace str_format_internal
diff --git a/absl/strings/internal/str_format/output_test.cc b/absl/strings/internal/str_format/output_test.cc
index e54e6f70..ce2e91a0 100644
--- a/absl/strings/internal/str_format/output_test.cc
+++ b/absl/strings/internal/str_format/output_test.cc
@@ -19,6 +19,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "absl/strings/cord.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -37,6 +38,12 @@ TEST(InvokeFlush, Stream) {
EXPECT_EQ(str.str(), "ABCDEF");
}
+TEST(InvokeFlush, Cord) {
+ absl::Cord str("ABC");
+ str_format_internal::InvokeFlush(&str, "DEF");
+ EXPECT_EQ(str, "ABCDEF");
+}
+
TEST(BufferRawSink, Limits) {
char buf[16];
{
@@ -70,4 +77,3 @@ TEST(BufferRawSink, Limits) {
} // namespace
ABSL_NAMESPACE_END
} // namespace absl
-
diff --git a/absl/strings/internal/str_format/parser.cc b/absl/strings/internal/str_format/parser.cc
index aab68db9..cc55dfa9 100644
--- a/absl/strings/internal/str_format/parser.cc
+++ b/absl/strings/internal/str_format/parser.cc
@@ -17,7 +17,7 @@ namespace absl {
ABSL_NAMESPACE_BEGIN
namespace str_format_internal {
-using CC = ConversionChar;
+using CC = FormatConversionCharInternal;
using LM = LengthMod;
ABSL_CONST_INIT const ConvTag kTags[256] = {
@@ -29,9 +29,9 @@ ABSL_CONST_INIT const ConvTag kTags[256] = {
{}, {}, {}, {}, {}, {}, {}, {}, // 28-2f
{}, {}, {}, {}, {}, {}, {}, {}, // 30-37
{}, {}, {}, {}, {}, {}, {}, {}, // 38-3f
- {}, CC::A, {}, CC::C, {}, CC::E, CC::F, CC::G, // @ABCDEFG
+ {}, CC::A, {}, {}, {}, CC::E, CC::F, CC::G, // @ABCDEFG
{}, {}, {}, {}, LM::L, {}, {}, {}, // HIJKLMNO
- {}, {}, {}, CC::S, {}, {}, {}, {}, // PQRSTUVW
+ {}, {}, {}, {}, {}, {}, {}, {}, // PQRSTUVW
CC::X, {}, {}, {}, {}, {}, {}, {}, // XYZ[\]^_
{}, CC::a, {}, CC::c, CC::d, CC::e, CC::f, CC::g, // `abcdefg
LM::h, CC::i, LM::j, {}, LM::l, {}, CC::n, CC::o, // hijklmno
@@ -296,15 +296,17 @@ struct ParsedFormatBase::ParsedFormatConsumer {
char* data_pos;
};
-ParsedFormatBase::ParsedFormatBase(string_view format, bool allow_ignored,
- std::initializer_list<Conv> convs)
+ParsedFormatBase::ParsedFormatBase(
+ string_view format, bool allow_ignored,
+ std::initializer_list<FormatConversionCharSet> convs)
: data_(format.empty() ? nullptr : new char[format.size()]) {
has_error_ = !ParseFormatString(format, ParsedFormatConsumer(this)) ||
!MatchesConversions(allow_ignored, convs);
}
bool ParsedFormatBase::MatchesConversions(
- bool allow_ignored, std::initializer_list<Conv> convs) const {
+ bool allow_ignored,
+ std::initializer_list<FormatConversionCharSet> convs) const {
std::unordered_set<int> used;
auto add_if_valid_conv = [&](int pos, char c) {
if (static_cast<size_t>(pos) > convs.size() ||
diff --git a/absl/strings/internal/str_format/parser.h b/absl/strings/internal/str_format/parser.h
index 45c90d1d..fffed04f 100644
--- a/absl/strings/internal/str_format/parser.h
+++ b/absl/strings/internal/str_format/parser.h
@@ -67,7 +67,7 @@ struct UnboundConversion {
Flags flags;
LengthMod length_mod = LengthMod::none;
- ConversionChar conv = FormatConversionChar::kNone;
+ FormatConversionChar conv = FormatConversionCharInternal::kNone;
};
// Consume conversion spec prefix (not including '%') of [p, end) if valid.
@@ -83,7 +83,7 @@ const char* ConsumeUnboundConversion(const char* p, const char* end,
// conversions.
class ConvTag {
public:
- constexpr ConvTag(ConversionChar conversion_char) // NOLINT
+ constexpr ConvTag(FormatConversionChar conversion_char) // NOLINT
: tag_(static_cast<int8_t>(conversion_char)) {}
// We invert the length modifiers to make them negative so that we can easily
// test for them.
@@ -94,9 +94,9 @@ class ConvTag {
bool is_conv() const { return tag_ >= 0; }
bool is_length() const { return tag_ < 0 && tag_ != -128; }
- ConversionChar as_conv() const {
+ FormatConversionChar as_conv() const {
assert(is_conv());
- return static_cast<ConversionChar>(tag_);
+ return static_cast<FormatConversionChar>(tag_);
}
LengthMod as_length() const {
assert(is_length());
@@ -143,7 +143,7 @@ bool ParseFormatString(string_view src, Consumer consumer) {
auto tag = GetTagForChar(percent[1]);
if (tag.is_conv()) {
if (ABSL_PREDICT_FALSE(next_arg < 0)) {
- // This indicates an error in the format std::string.
+ // This indicates an error in the format string.
// The only way to get `next_arg < 0` here is to have a positional
// argument first which sets next_arg to -1 and then a non-positional
// argument.
@@ -186,8 +186,9 @@ constexpr bool EnsureConstexpr(string_view s) {
class ParsedFormatBase {
public:
- explicit ParsedFormatBase(string_view format, bool allow_ignored,
- std::initializer_list<Conv> convs);
+ explicit ParsedFormatBase(
+ string_view format, bool allow_ignored,
+ std::initializer_list<FormatConversionCharSet> convs);
ParsedFormatBase(const ParsedFormatBase& other) { *this = other; }
@@ -234,8 +235,9 @@ class ParsedFormatBase {
private:
// Returns whether the conversions match and if !allow_ignored it verifies
// that all conversions are used by the format.
- bool MatchesConversions(bool allow_ignored,
- std::initializer_list<Conv> convs) const;
+ bool MatchesConversions(
+ bool allow_ignored,
+ std::initializer_list<FormatConversionCharSet> convs) const;
struct ParsedFormatConsumer;
@@ -280,14 +282,14 @@ class ParsedFormatBase {
// This is the only API that allows the user to pass a runtime specified format
// string. These factory functions will return NULL if the format does not match
// the conversions requested by the user.
-template <str_format_internal::Conv... C>
+template <FormatConversionCharSet... C>
class ExtendedParsedFormat : public str_format_internal::ParsedFormatBase {
public:
explicit ExtendedParsedFormat(string_view format)
#ifdef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
__attribute__((
enable_if(str_format_internal::EnsureConstexpr(format),
- "Format std::string is not constexpr."),
+ "Format string is not constexpr."),
enable_if(str_format_internal::ValidFormatImpl<C...>(format),
"Format specified does not match the template arguments.")))
#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
diff --git a/absl/strings/internal/str_format/parser_test.cc b/absl/strings/internal/str_format/parser_test.cc
index 1b1ee030..5aced987 100644
--- a/absl/strings/internal/str_format/parser_test.cc
+++ b/absl/strings/internal/str_format/parser_test.cc
@@ -41,23 +41,23 @@ TEST(LengthModTest, Names) {
TEST(ConversionCharTest, Names) {
struct Expectation {
- ConversionChar id;
+ FormatConversionChar id;
char name;
};
// clang-format off
const Expectation kExpect[] = {
-#define X(c) {ConversionChar::c, #c[0]}
- X(c), X(C), X(s), X(S), // text
+#define X(c) {FormatConversionCharInternal::c, #c[0]}
+ X(c), X(s), // text
X(d), X(i), X(o), X(u), X(x), X(X), // int
X(f), X(F), X(e), X(E), X(g), X(G), X(a), X(A), // float
X(n), X(p), // misc
#undef X
- {ConversionChar::none, '\0'},
+ {FormatConversionCharInternal::kNone, '\0'},
};
// clang-format on
for (auto e : kExpect) {
SCOPED_TRACE(e.name);
- ConversionChar v = e.id;
+ FormatConversionChar v = e.id;
EXPECT_EQ(e.name, FormatConversionCharToChar(v));
}
}
@@ -349,7 +349,8 @@ TEST_F(ParsedFormatTest, ValueSemantics) {
ParsedFormatBase p2 = p1; // copy construct (empty)
EXPECT_EQ(SummarizeParsedFormat(p1), SummarizeParsedFormat(p2));
- p1 = ParsedFormatBase("hello%s", true, {Conv::s}); // move assign
+ p1 = ParsedFormatBase("hello%s", true,
+ {FormatConversionCharSetInternal::s}); // move assign
EXPECT_EQ("[hello]{s:1$s}", SummarizeParsedFormat(p1));
ParsedFormatBase p3 = p1; // copy construct (nonempty)
@@ -367,7 +368,7 @@ TEST_F(ParsedFormatTest, ValueSemantics) {
struct ExpectParse {
const char* in;
- std::initializer_list<Conv> conv_set;
+ std::initializer_list<FormatConversionCharSet> conv_set;
const char* out;
};
@@ -377,9 +378,9 @@ TEST_F(ParsedFormatTest, Parsing) {
const ExpectParse kExpect[] = {
{"", {}, ""},
{"ab", {}, "[ab]"},
- {"a%d", {Conv::d}, "[a]{d:1$d}"},
- {"a%+d", {Conv::d}, "[a]{+d:1$d}"},
- {"a% d", {Conv::d}, "[a]{ d:1$d}"},
+ {"a%d", {FormatConversionCharSetInternal::d}, "[a]{d:1$d}"},
+ {"a%+d", {FormatConversionCharSetInternal::d}, "[a]{+d:1$d}"},
+ {"a% d", {FormatConversionCharSetInternal::d}, "[a]{ d:1$d}"},
{"a%b %d", {}, "[a]!"}, // stop after error
};
for (const auto& e : kExpect) {
@@ -391,13 +392,13 @@ TEST_F(ParsedFormatTest, Parsing) {
TEST_F(ParsedFormatTest, ParsingFlagOrder) {
const ExpectParse kExpect[] = {
- {"a%+ 0d", {Conv::d}, "[a]{+ 0d:1$d}"},
- {"a%+0 d", {Conv::d}, "[a]{+0 d:1$d}"},
- {"a%0+ d", {Conv::d}, "[a]{0+ d:1$d}"},
- {"a% +0d", {Conv::d}, "[a]{ +0d:1$d}"},
- {"a%0 +d", {Conv::d}, "[a]{0 +d:1$d}"},
- {"a% 0+d", {Conv::d}, "[a]{ 0+d:1$d}"},
- {"a%+ 0+d", {Conv::d}, "[a]{+ 0+d:1$d}"},
+ {"a%+ 0d", {FormatConversionCharSetInternal::d}, "[a]{+ 0d:1$d}"},
+ {"a%+0 d", {FormatConversionCharSetInternal::d}, "[a]{+0 d:1$d}"},
+ {"a%0+ d", {FormatConversionCharSetInternal::d}, "[a]{0+ d:1$d}"},
+ {"a% +0d", {FormatConversionCharSetInternal::d}, "[a]{ +0d:1$d}"},
+ {"a%0 +d", {FormatConversionCharSetInternal::d}, "[a]{0 +d:1$d}"},
+ {"a% 0+d", {FormatConversionCharSetInternal::d}, "[a]{ 0+d:1$d}"},
+ {"a%+ 0+d", {FormatConversionCharSetInternal::d}, "[a]{+ 0+d:1$d}"},
};
for (const auto& e : kExpect) {
SCOPED_TRACE(e.in);