Export of internal Abseil changes

--
f34cd235a12ad0ee1fea3a1ee5a427272dc2b285 by Abseil Team <absl-team@google.com>:

Migrates uses of deprecated map types to recommended types.

PiperOrigin-RevId: 309945156

--
e3410a47ad32c0775b6911610bc47b22938decad by Matthew Brown <matthewbr@google.com>:

Internal Change

PiperOrigin-RevId: 309856021

--
a58cfa25e0bb59e7fa9647ac1aae65eaccff0086 by Greg Falcon <gfalcon@google.com>:

Internal change.

PiperOrigin-RevId: 309804612

--
cdc5ec310035fbe25f496bda283fe655d94d7769 by Mark Barolak <mbar@google.com>:

Standardize the header comments for friend functions in cord.h

PiperOrigin-RevId: 309779073

--
fe61602701be795e54477b0fdbf5ffc1df12a6b7 by Samuel Benzaquen <sbenza@google.com>:

Implement %f natively for any input.
It evaluates the input at runtime and allocates stack space accordingly.

This removes a potential fallback into snprintf, improves performance, and removes all memory allocations in this formatting path.

PiperOrigin-RevId: 309752501

--
79e2a24f3f959e8b06ddf1d440bbabbd5f89b5b7 by Greg Falcon <gfalcon@google.com>:

Add a Cord::swap() method.  Many other Abseil types already provide this, but it was missing here.

We already provided a two-argument free function form of `swap()`, but that API is better suited for generic code.  The swap member function is a better API when the types are known.

PiperOrigin-RevId: 309751740

--
85cdf60024f153fb4fcb7fe68ed2b14b9faf119d by Derek Mauro <dmauro@google.com>:

Cleanup uses of "linker initialized" SpinLocks

PiperOrigin-RevId: 309581867

--
9e5443bfcec4b94056b13c75326576e987ab88fb by Matt Kulukundis <kfm@google.com>:

Clarify intended mixing properties of `absl::Hash`

PiperOrigin-RevId: 309520174

--
a0630f0827b67f217aaeae68a448fe4c1101e17d by Greg Falcon <gfalcon@google.com>:

Comment out a test in Emscripten to sidestep `long double` issues.

PiperOrigin-RevId: 309482953
GitOrigin-RevId: f34cd235a12ad0ee1fea3a1ee5a427272dc2b285
Change-Id: Icce0c9d547117374d596b9d684e4054ddd118669
This commit is contained in:
Abseil Team 2020-05-05 07:54:14 -07:00 committed by vslashg
parent a1d6689907
commit d85783fd0b
24 changed files with 1112 additions and 197 deletions

View file

@ -541,7 +541,10 @@ cc_test(
copts = ABSL_TEST_COPTS, copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS, linkopts = ABSL_DEFAULT_LINKOPTS,
tags = ["no_test_ios_x86_64"], tags = ["no_test_ios_x86_64"],
deps = [":malloc_internal"], deps = [
":malloc_internal",
"//absl/container:node_hash_map",
],
) )
cc_test( cc_test(

View file

@ -497,6 +497,7 @@ absl_cc_test(
${ABSL_TEST_COPTS} ${ABSL_TEST_COPTS}
DEPS DEPS
absl::malloc_internal absl::malloc_internal
absl::node_hash_map
Threads::Threads Threads::Threads
) )

View file

@ -21,6 +21,8 @@
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#include "absl/container/node_hash_map.h"
namespace absl { namespace absl {
ABSL_NAMESPACE_BEGIN ABSL_NAMESPACE_BEGIN
namespace base_internal { namespace base_internal {
@ -75,7 +77,7 @@ static bool using_low_level_alloc = false;
// allocations and deallocations are reported via the MallocHook // allocations and deallocations are reported via the MallocHook
// interface. // interface.
static void Test(bool use_new_arena, bool call_malloc_hook, int n) { static void Test(bool use_new_arena, bool call_malloc_hook, int n) {
typedef std::unordered_map<int, BlockDesc> AllocMap; typedef absl::node_hash_map<int, BlockDesc> AllocMap;
AllocMap allocated; AllocMap allocated;
AllocMap::iterator it; AllocMap::iterator it;
BlockDesc block_desc; BlockDesc block_desc;

View file

@ -149,13 +149,15 @@ struct FileMappingHint {
// Moreover, we are using only TryLock(), if the decorator list // Moreover, we are using only TryLock(), if the decorator list
// is being modified (is busy), we skip all decorators, and possibly // is being modified (is busy), we skip all decorators, and possibly
// loose some info. Sorry, that's the best we could do. // loose some info. Sorry, that's the best we could do.
base_internal::SpinLock g_decorators_mu(base_internal::kLinkerInitialized); ABSL_CONST_INIT absl::base_internal::SpinLock g_decorators_mu(
absl::kConstInit, absl::base_internal::SCHEDULE_KERNEL_ONLY);
const int kMaxFileMappingHints = 8; const int kMaxFileMappingHints = 8;
int g_num_file_mapping_hints; int g_num_file_mapping_hints;
FileMappingHint g_file_mapping_hints[kMaxFileMappingHints]; FileMappingHint g_file_mapping_hints[kMaxFileMappingHints];
// Protects g_file_mapping_hints. // Protects g_file_mapping_hints.
base_internal::SpinLock g_file_mapping_mu(base_internal::kLinkerInitialized); ABSL_CONST_INIT absl::base_internal::SpinLock g_file_mapping_mu(
absl::kConstInit, absl::base_internal::SCHEDULE_KERNEL_ONLY);
// Async-signal-safe function to zero a buffer. // Async-signal-safe function to zero a buffer.
// memset() is not guaranteed to be async-signal-safe. // memset() is not guaranteed to be async-signal-safe.

View file

@ -37,8 +37,11 @@
// types. Hashing of that combined state is separately done by `absl::Hash`. // types. Hashing of that combined state is separately done by `absl::Hash`.
// //
// One should assume that a hash algorithm is chosen randomly at the start of // One should assume that a hash algorithm is chosen randomly at the start of
// each process. E.g., absl::Hash<int>()(9) in one process and // each process. E.g., `absl::Hash<int>{}(9)` in one process and
// absl::Hash<int>()(9) in another process are likely to differ. // `absl::Hash<int>{}(9)` in another process are likely to differ.
//
// `absl::Hash` is intended to strongly mix input bits with a target of passing
// an [Avalanche Test](https://en.wikipedia.org/wiki/Avalanche_effect).
// //
// Example: // Example:
// //

View file

@ -130,12 +130,15 @@ TYPED_TEST(GaussianDistributionInterfaceTest, SerializeTest) {
ss >> after; ss >> after;
#if defined(__powerpc64__) || defined(__PPC64__) || defined(__powerpc__) || \ #if defined(__powerpc64__) || defined(__PPC64__) || defined(__powerpc__) || \
defined(__ppc__) || defined(__PPC__) defined(__ppc__) || defined(__PPC__) || defined(__EMSCRIPTEN__)
if (std::is_same<TypeParam, long double>::value) { if (std::is_same<TypeParam, long double>::value) {
// Roundtripping floating point values requires sufficient precision // Roundtripping floating point values requires sufficient precision
// to reconstruct the exact value. It turns out that long double // to reconstruct the exact value. It turns out that long double
// has some errors doing this on ppc, particularly for values // has some errors doing this on ppc, particularly for values
// near {1.0 +/- epsilon}. // near {1.0 +/- epsilon}.
//
// Emscripten is even worse, implementing long double as a 128-bit
// type, but shipping with a strtold() that doesn't support that.
if (mean <= std::numeric_limits<double>::max() && if (mean <= std::numeric_limits<double>::max() &&
mean >= std::numeric_limits<double>::lowest()) { mean >= std::numeric_limits<double>::lowest()) {
EXPECT_EQ(static_cast<double>(before.mean()), EXPECT_EQ(static_cast<double>(before.mean()),

View file

@ -28,7 +28,7 @@ TEST(WideMultiplyTest, MultiplyU64ToU128Test) {
EXPECT_EQ(absl::uint128(0), MultiplyU64ToU128(0, 0)); EXPECT_EQ(absl::uint128(0), MultiplyU64ToU128(0, 0));
// Max uint64 // Max uint64_t
EXPECT_EQ(MultiplyU64ToU128(kMax, kMax), EXPECT_EQ(MultiplyU64ToU128(kMax, kMax),
absl::MakeUint128(0xfffffffffffffffe, 0x0000000000000001)); absl::MakeUint128(0xfffffffffffffffe, 0x0000000000000001));
EXPECT_EQ(absl::MakeUint128(0, kMax), MultiplyU64ToU128(kMax, 1)); EXPECT_EQ(absl::MakeUint128(0, kMax), MultiplyU64ToU128(kMax, 1));

View file

@ -638,10 +638,13 @@ cc_library(
visibility = ["//visibility:private"], visibility = ["//visibility:private"],
deps = [ deps = [
":strings", ":strings",
"//absl/base:bits",
"//absl/base:config", "//absl/base:config",
"//absl/base:core_headers", "//absl/base:core_headers",
"//absl/functional:function_ref",
"//absl/meta:type_traits", "//absl/meta:type_traits",
"//absl/numeric:int128", "//absl/numeric:int128",
"//absl/types:optional",
"//absl/types:span", "//absl/types:span",
], ],
) )
@ -718,7 +721,7 @@ cc_test(
deps = [ deps = [
":str_format_internal", ":str_format_internal",
"//absl/base:raw_logging_internal", "//absl/base:raw_logging_internal",
"//absl/numeric:int128", "//absl/types:optional",
"@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest_main",
], ],
) )

View file

@ -392,6 +392,7 @@ absl_cc_library(
COPTS COPTS
${ABSL_DEFAULT_COPTS} ${ABSL_DEFAULT_COPTS}
DEPS DEPS
absl::bits
absl::strings absl::strings
absl::config absl::config
absl::core_headers absl::core_headers

View file

@ -162,7 +162,7 @@ class Cord {
if (contents_.is_tree()) DestroyCordSlow(); if (contents_.is_tree()) DestroyCordSlow();
} }
// Cord::MakeCordFromExternal(data, callable) // MakeCordFromExternal()
// //
// Creates a Cord that takes ownership of external string memory. The // Creates a Cord that takes ownership of external string memory. The
// contents of `data` are not copied to the Cord; instead, the external // contents of `data` are not copied to the Cord; instead, the external
@ -246,10 +246,17 @@ class Cord {
// (pos + new_size) >= size(), the result is the subrange [pos, size()). // (pos + new_size) >= size(), the result is the subrange [pos, size()).
Cord Subcord(size_t pos, size_t new_size) const; Cord Subcord(size_t pos, size_t new_size) const;
// Cord::swap()
//
// Swaps the contents of the Cord with `other`.
void swap(Cord& other) noexcept;
// swap() // swap()
// //
// Swaps the data of Cord `x` with Cord `y`. // Swaps the contents of two Cords.
friend void swap(Cord& x, Cord& y) noexcept; friend void swap(Cord& x, Cord& y) noexcept {
x.swap(y);
}
// Cord::size() // Cord::size()
// //
@ -1032,6 +1039,10 @@ inline Cord& Cord::operator=(const Cord& x) {
inline Cord::Cord(Cord&& src) noexcept : contents_(std::move(src.contents_)) {} inline Cord::Cord(Cord&& src) noexcept : contents_(std::move(src.contents_)) {}
inline void Cord::swap(Cord& other) noexcept {
contents_.Swap(&other.contents_);
}
inline Cord& Cord::operator=(Cord&& x) noexcept { inline Cord& Cord::operator=(Cord&& x) noexcept {
contents_ = std::move(x.contents_); contents_ = std::move(x.contents_);
return *this; return *this;
@ -1308,10 +1319,6 @@ inline bool operator<=(absl::string_view x, const Cord& y) { return !(y < x); }
inline bool operator>=(const Cord& x, absl::string_view y) { return !(x < y); } inline bool operator>=(const Cord& x, absl::string_view y) { return !(x < y); }
inline bool operator>=(absl::string_view x, const Cord& y) { return !(x < y); } inline bool operator>=(absl::string_view x, const Cord& y) { return !(x < y); }
// Overload of swap for Cord. The use of non-const references is
// required. :(
inline void swap(Cord& x, Cord& y) noexcept { y.contents_.Swap(&x.contents_); }
// Some internals exposed to test code. // Some internals exposed to test code.
namespace strings_internal { namespace strings_internal {
class CordTestAccess { class CordTestAccess {

View file

@ -396,6 +396,9 @@ TEST(Cord, Swap) {
swap(x, y); swap(x, y);
ASSERT_EQ(x, absl::Cord(b)); ASSERT_EQ(x, absl::Cord(b));
ASSERT_EQ(y, absl::Cord(a)); ASSERT_EQ(y, absl::Cord(a));
x.swap(y);
ASSERT_EQ(x, absl::Cord(a));
ASSERT_EQ(y, absl::Cord(b));
} }
static void VerifyCopyToString(const absl::Cord& cord) { static void VerifyCopyToString(const absl::Cord& cord) {

View file

@ -167,24 +167,26 @@ class IntDigits {
// Note: 'o' conversions do not have a base indicator, it's just that // Note: 'o' conversions do not have a base indicator, it's just that
// the '#' flag is specified to modify the precision for 'o' conversions. // the '#' flag is specified to modify the precision for 'o' conversions.
string_view BaseIndicator(const IntDigits &as_digits, string_view BaseIndicator(const IntDigits &as_digits,
const ConversionSpec conv) { const FormatConversionSpecImpl conv) {
// always show 0x for %p. // always show 0x for %p.
bool alt = conv.has_alt_flag() || conv.conversion_char() == ConversionChar::p; bool alt = conv.has_alt_flag() ||
bool hex = (conv.conversion_char() == FormatConversionChar::x || conv.conversion_char() == FormatConversionCharInternal::p;
conv.conversion_char() == FormatConversionChar::X || bool hex = (conv.conversion_char() == FormatConversionCharInternal::x ||
conv.conversion_char() == FormatConversionChar::p); conv.conversion_char() == FormatConversionCharInternal::X ||
conv.conversion_char() == FormatConversionCharInternal::p);
// From the POSIX description of '#' flag: // From the POSIX description of '#' flag:
// "For x or X conversion specifiers, a non-zero result shall have // "For x or X conversion specifiers, a non-zero result shall have
// 0x (or 0X) prefixed to it." // 0x (or 0X) prefixed to it."
if (alt && hex && !as_digits.without_neg_or_zero().empty()) { if (alt && hex && !as_digits.without_neg_or_zero().empty()) {
return conv.conversion_char() == FormatConversionChar::X ? "0X" : "0x"; return conv.conversion_char() == FormatConversionCharInternal::X ? "0X"
: "0x";
} }
return {}; return {};
} }
string_view SignColumn(bool neg, const ConversionSpec conv) { string_view SignColumn(bool neg, const FormatConversionSpecImpl conv) {
if (conv.conversion_char() == FormatConversionChar::d || if (conv.conversion_char() == FormatConversionCharInternal::d ||
conv.conversion_char() == FormatConversionChar::i) { conv.conversion_char() == FormatConversionCharInternal::i) {
if (neg) return "-"; if (neg) return "-";
if (conv.has_show_pos_flag()) return "+"; if (conv.has_show_pos_flag()) return "+";
if (conv.has_sign_col_flag()) return " "; if (conv.has_sign_col_flag()) return " ";
@ -192,7 +194,7 @@ string_view SignColumn(bool neg, const ConversionSpec conv) {
return {}; return {};
} }
bool ConvertCharImpl(unsigned char v, const ConversionSpec conv, bool ConvertCharImpl(unsigned char v, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
size_t fill = 0; size_t fill = 0;
if (conv.width() >= 0) fill = conv.width(); if (conv.width() >= 0) fill = conv.width();
@ -204,7 +206,8 @@ bool ConvertCharImpl(unsigned char v, const ConversionSpec conv,
} }
bool ConvertIntImplInnerSlow(const IntDigits &as_digits, bool ConvertIntImplInnerSlow(const IntDigits &as_digits,
const ConversionSpec conv, FormatSinkImpl *sink) { const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
// Print as a sequence of Substrings: // Print as a sequence of Substrings:
// [left_spaces][sign][base_indicator][zeroes][formatted][right_spaces] // [left_spaces][sign][base_indicator][zeroes][formatted][right_spaces]
size_t fill = 0; size_t fill = 0;
@ -224,7 +227,8 @@ bool ConvertIntImplInnerSlow(const IntDigits &as_digits,
if (!precision_specified) if (!precision_specified)
precision = 1; precision = 1;
if (conv.has_alt_flag() && conv.conversion_char() == ConversionChar::o) { if (conv.has_alt_flag() &&
conv.conversion_char() == FormatConversionCharInternal::o) {
// From POSIX description of the '#' (alt) flag: // From POSIX description of the '#' (alt) flag:
// "For o conversion, it increases the precision (if necessary) to // "For o conversion, it increases the precision (if necessary) to
// force the first digit of the result to be zero." // force the first digit of the result to be zero."
@ -258,42 +262,43 @@ bool ConvertIntImplInnerSlow(const IntDigits &as_digits,
} }
template <typename T> template <typename T>
bool ConvertIntArg(T v, const ConversionSpec conv, FormatSinkImpl *sink) { bool ConvertIntArg(T v, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
using U = typename MakeUnsigned<T>::type; using U = typename MakeUnsigned<T>::type;
IntDigits as_digits; IntDigits as_digits;
switch (conv.conversion_char()) { switch (conv.conversion_char()) {
case FormatConversionChar::c: case FormatConversionCharInternal::c:
return ConvertCharImpl(static_cast<unsigned char>(v), conv, sink); return ConvertCharImpl(static_cast<unsigned char>(v), conv, sink);
case FormatConversionChar::o: case FormatConversionCharInternal::o:
as_digits.PrintAsOct(static_cast<U>(v)); as_digits.PrintAsOct(static_cast<U>(v));
break; break;
case FormatConversionChar::x: case FormatConversionCharInternal::x:
as_digits.PrintAsHexLower(static_cast<U>(v)); as_digits.PrintAsHexLower(static_cast<U>(v));
break; break;
case FormatConversionChar::X: case FormatConversionCharInternal::X:
as_digits.PrintAsHexUpper(static_cast<U>(v)); as_digits.PrintAsHexUpper(static_cast<U>(v));
break; break;
case FormatConversionChar::u: case FormatConversionCharInternal::u:
as_digits.PrintAsDec(static_cast<U>(v)); as_digits.PrintAsDec(static_cast<U>(v));
break; break;
case FormatConversionChar::d: case FormatConversionCharInternal::d:
case FormatConversionChar::i: case FormatConversionCharInternal::i:
as_digits.PrintAsDec(v); as_digits.PrintAsDec(v);
break; break;
case FormatConversionChar::a: case FormatConversionCharInternal::a:
case FormatConversionChar::e: case FormatConversionCharInternal::e:
case FormatConversionChar::f: case FormatConversionCharInternal::f:
case FormatConversionChar::g: case FormatConversionCharInternal::g:
case FormatConversionChar::A: case FormatConversionCharInternal::A:
case FormatConversionChar::E: case FormatConversionCharInternal::E:
case FormatConversionChar::F: case FormatConversionCharInternal::F:
case FormatConversionChar::G: case FormatConversionCharInternal::G:
return ConvertFloatImpl(static_cast<double>(v), conv, sink); return ConvertFloatImpl(static_cast<double>(v), conv, sink);
default: default:
@ -308,12 +313,13 @@ bool ConvertIntArg(T v, const ConversionSpec conv, FormatSinkImpl *sink) {
} }
template <typename T> template <typename T>
bool ConvertFloatArg(T v, const ConversionSpec conv, FormatSinkImpl *sink) { bool ConvertFloatArg(T v, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return FormatConversionCharIsFloat(conv.conversion_char()) && return FormatConversionCharIsFloat(conv.conversion_char()) &&
ConvertFloatImpl(v, conv, sink); ConvertFloatImpl(v, conv, sink);
} }
inline bool ConvertStringArg(string_view v, const ConversionSpec conv, inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
if (conv.conversion_char() != FormatConversionCharInternal::s) return false; if (conv.conversion_char() != FormatConversionCharInternal::s) return false;
if (conv.is_basic()) { if (conv.is_basic()) {
@ -328,19 +334,20 @@ inline bool ConvertStringArg(string_view v, const ConversionSpec conv,
// ==================== Strings ==================== // ==================== Strings ====================
StringConvertResult FormatConvertImpl(const std::string &v, StringConvertResult FormatConvertImpl(const std::string &v,
const ConversionSpec conv, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertStringArg(v, conv, sink)}; return {ConvertStringArg(v, conv, sink)};
} }
StringConvertResult FormatConvertImpl(string_view v, const ConversionSpec conv, StringConvertResult FormatConvertImpl(string_view v,
const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertStringArg(v, conv, sink)}; return {ConvertStringArg(v, conv, sink)};
} }
ArgConvertResult<FormatConversionCharSetUnion( ArgConvertResult<FormatConversionCharSetUnion(
FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::p)> FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::p)>
FormatConvertImpl(const char *v, const ConversionSpec conv, FormatConvertImpl(const char *v, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
if (conv.conversion_char() == FormatConversionCharInternal::p) if (conv.conversion_char() == FormatConversionCharInternal::p)
return {FormatConvertImpl(VoidPtr(v), conv, sink).value}; return {FormatConvertImpl(VoidPtr(v), conv, sink).value};
@ -358,7 +365,7 @@ FormatConvertImpl(const char *v, const ConversionSpec conv,
// ==================== Raw pointers ==================== // ==================== Raw pointers ====================
ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl( ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl(
VoidPtr v, const ConversionSpec conv, FormatSinkImpl *sink) { VoidPtr v, const FormatConversionSpecImpl conv, FormatSinkImpl *sink) {
if (conv.conversion_char() != FormatConversionCharInternal::p) return {false}; if (conv.conversion_char() != FormatConversionCharInternal::p) return {false};
if (!v.value) { if (!v.value) {
sink->Append("(nil)"); sink->Append("(nil)");
@ -370,82 +377,87 @@ ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl(
} }
// ==================== Floats ==================== // ==================== Floats ====================
FloatingConvertResult FormatConvertImpl(float v, const ConversionSpec conv, FloatingConvertResult FormatConvertImpl(float v,
const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertFloatArg(v, conv, sink)}; return {ConvertFloatArg(v, conv, sink)};
} }
FloatingConvertResult FormatConvertImpl(double v, const ConversionSpec conv, FloatingConvertResult FormatConvertImpl(double v,
const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertFloatArg(v, conv, sink)}; return {ConvertFloatArg(v, conv, sink)};
} }
FloatingConvertResult FormatConvertImpl(long double v, FloatingConvertResult FormatConvertImpl(long double v,
const ConversionSpec conv, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertFloatArg(v, conv, sink)}; return {ConvertFloatArg(v, conv, sink)};
} }
// ==================== Chars ==================== // ==================== Chars ====================
IntegralConvertResult FormatConvertImpl(char v, const ConversionSpec conv, IntegralConvertResult FormatConvertImpl(char v,
const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)}; return {ConvertIntArg(v, conv, sink)};
} }
IntegralConvertResult FormatConvertImpl(signed char v, IntegralConvertResult FormatConvertImpl(signed char v,
const ConversionSpec conv, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)}; return {ConvertIntArg(v, conv, sink)};
} }
IntegralConvertResult FormatConvertImpl(unsigned char v, IntegralConvertResult FormatConvertImpl(unsigned char v,
const ConversionSpec conv, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)}; return {ConvertIntArg(v, conv, sink)};
} }
// ==================== Ints ==================== // ==================== Ints ====================
IntegralConvertResult FormatConvertImpl(short v, // NOLINT IntegralConvertResult FormatConvertImpl(short v, // NOLINT
const ConversionSpec conv, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)}; return {ConvertIntArg(v, conv, sink)};
} }
IntegralConvertResult FormatConvertImpl(unsigned short v, // NOLINT IntegralConvertResult FormatConvertImpl(unsigned short v, // NOLINT
const ConversionSpec conv, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)}; return {ConvertIntArg(v, conv, sink)};
} }
IntegralConvertResult FormatConvertImpl(int v, const ConversionSpec conv, IntegralConvertResult FormatConvertImpl(int v,
const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)}; return {ConvertIntArg(v, conv, sink)};
} }
IntegralConvertResult FormatConvertImpl(unsigned v, const ConversionSpec conv, IntegralConvertResult FormatConvertImpl(unsigned v,
const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)}; return {ConvertIntArg(v, conv, sink)};
} }
IntegralConvertResult FormatConvertImpl(long v, // NOLINT IntegralConvertResult FormatConvertImpl(long v, // NOLINT
const ConversionSpec conv, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)}; return {ConvertIntArg(v, conv, sink)};
} }
IntegralConvertResult FormatConvertImpl(unsigned long v, // NOLINT IntegralConvertResult FormatConvertImpl(unsigned long v, // NOLINT
const ConversionSpec conv, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)}; return {ConvertIntArg(v, conv, sink)};
} }
IntegralConvertResult FormatConvertImpl(long long v, // NOLINT IntegralConvertResult FormatConvertImpl(long long v, // NOLINT
const ConversionSpec conv, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)}; return {ConvertIntArg(v, conv, sink)};
} }
IntegralConvertResult FormatConvertImpl(unsigned long long v, // NOLINT IntegralConvertResult FormatConvertImpl(unsigned long long v, // NOLINT
const ConversionSpec conv, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)}; return {ConvertIntArg(v, conv, sink)};
} }
IntegralConvertResult FormatConvertImpl(absl::int128 v, IntegralConvertResult FormatConvertImpl(absl::int128 v,
const ConversionSpec conv, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)}; return {ConvertIntArg(v, conv, sink)};
} }
IntegralConvertResult FormatConvertImpl(absl::uint128 v, IntegralConvertResult FormatConvertImpl(absl::uint128 v,
const ConversionSpec conv, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)}; return {ConvertIntArg(v, conv, sink)};
} }

View file

@ -67,20 +67,24 @@ constexpr FormatConversionCharSet ExtractCharSet(ArgConvertResult<C>) {
using StringConvertResult = using StringConvertResult =
ArgConvertResult<FormatConversionCharSetInternal::s>; ArgConvertResult<FormatConversionCharSetInternal::s>;
ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl( ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl(
VoidPtr v, ConversionSpec conv, FormatSinkImpl* sink); VoidPtr v, FormatConversionSpecImpl conv, FormatSinkImpl* sink);
// Strings. // Strings.
StringConvertResult FormatConvertImpl(const std::string& v, ConversionSpec conv, StringConvertResult FormatConvertImpl(const std::string& v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
StringConvertResult FormatConvertImpl(string_view v, ConversionSpec conv, StringConvertResult FormatConvertImpl(string_view v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
ArgConvertResult<FormatConversionCharSetUnion( ArgConvertResult<FormatConversionCharSetUnion(
FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::p)> FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::p)>
FormatConvertImpl(const char* v, ConversionSpec conv, FormatSinkImpl* sink); FormatConvertImpl(const char* v, const FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
template <class AbslCord, typename std::enable_if<std::is_same< template <class AbslCord, typename std::enable_if<std::is_same<
AbslCord, absl::Cord>::value>::type* = nullptr> AbslCord, absl::Cord>::value>::type* = nullptr>
StringConvertResult FormatConvertImpl(const AbslCord& value, StringConvertResult FormatConvertImpl(const AbslCord& value,
ConversionSpec conv, FormatConversionSpecImpl conv,
FormatSinkImpl* sink) { FormatSinkImpl* sink) {
if (conv.conversion_char() != FormatConversionCharInternal::s) { if (conv.conversion_char() != FormatConversionCharInternal::s) {
return {false}; return {false};
@ -127,50 +131,55 @@ using FloatingConvertResult =
ArgConvertResult<FormatConversionCharSetInternal::kFloating>; ArgConvertResult<FormatConversionCharSetInternal::kFloating>;
// Floats. // Floats.
FloatingConvertResult FormatConvertImpl(float v, ConversionSpec conv, FloatingConvertResult FormatConvertImpl(float v, FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
FloatingConvertResult FormatConvertImpl(double v, ConversionSpec conv, FloatingConvertResult FormatConvertImpl(double v, FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
FloatingConvertResult FormatConvertImpl(long double v, ConversionSpec conv, FloatingConvertResult FormatConvertImpl(long double v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
// Chars. // Chars.
IntegralConvertResult FormatConvertImpl(char v, ConversionSpec conv, IntegralConvertResult FormatConvertImpl(char v, FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
IntegralConvertResult FormatConvertImpl(signed char v, ConversionSpec conv, IntegralConvertResult FormatConvertImpl(signed char v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
IntegralConvertResult FormatConvertImpl(unsigned char v, ConversionSpec conv, IntegralConvertResult FormatConvertImpl(unsigned char v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
// Ints. // Ints.
IntegralConvertResult FormatConvertImpl(short v, // NOLINT IntegralConvertResult FormatConvertImpl(short v, // NOLINT
ConversionSpec conv, FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
IntegralConvertResult FormatConvertImpl(unsigned short v, // NOLINT IntegralConvertResult FormatConvertImpl(unsigned short v, // NOLINT
ConversionSpec conv, FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
IntegralConvertResult FormatConvertImpl(int v, ConversionSpec conv, IntegralConvertResult FormatConvertImpl(int v, FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
IntegralConvertResult FormatConvertImpl(unsigned v, ConversionSpec conv, IntegralConvertResult FormatConvertImpl(unsigned v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
IntegralConvertResult FormatConvertImpl(long v, // NOLINT IntegralConvertResult FormatConvertImpl(long v, // NOLINT
ConversionSpec conv, FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
IntegralConvertResult FormatConvertImpl(unsigned long v, // NOLINT IntegralConvertResult FormatConvertImpl(unsigned long v, // NOLINT
ConversionSpec conv, FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
IntegralConvertResult FormatConvertImpl(long long v, // NOLINT IntegralConvertResult FormatConvertImpl(long long v, // NOLINT
ConversionSpec conv, FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
IntegralConvertResult FormatConvertImpl(unsigned long long v, // NOLINT IntegralConvertResult FormatConvertImpl(unsigned long long v, // NOLINT
ConversionSpec conv, FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
IntegralConvertResult FormatConvertImpl(int128 v, ConversionSpec conv, IntegralConvertResult FormatConvertImpl(int128 v, FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
IntegralConvertResult FormatConvertImpl(uint128 v, ConversionSpec conv, IntegralConvertResult FormatConvertImpl(uint128 v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
template <typename T, enable_if_t<std::is_same<T, bool>::value, int> = 0> 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) { FormatSinkImpl* sink) {
return FormatConvertImpl(static_cast<int>(v), conv, sink); return FormatConvertImpl(static_cast<int>(v), conv, sink);
} }
@ -181,11 +190,11 @@ template <typename T>
typename std::enable_if<std::is_enum<T>::value && typename std::enable_if<std::is_enum<T>::value &&
!HasUserDefinedConvert<T>::value, !HasUserDefinedConvert<T>::value,
IntegralConvertResult>::type IntegralConvertResult>::type
FormatConvertImpl(T v, ConversionSpec conv, FormatSinkImpl* sink); FormatConvertImpl(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink);
template <typename T> template <typename T>
StringConvertResult FormatConvertImpl(const StreamedWrapper<T>& v, StringConvertResult FormatConvertImpl(const StreamedWrapper<T>& v,
ConversionSpec conv, FormatConversionSpecImpl conv,
FormatSinkImpl* out) { FormatSinkImpl* out) {
std::ostringstream oss; std::ostringstream oss;
oss << v.v_; oss << v.v_;
@ -198,7 +207,8 @@ StringConvertResult FormatConvertImpl(const StreamedWrapper<T>& v,
struct FormatCountCaptureHelper { struct FormatCountCaptureHelper {
template <class T = int> template <class T = int>
static ArgConvertResult<FormatConversionCharSetInternal::n> ConvertHelper( static ArgConvertResult<FormatConversionCharSetInternal::n> ConvertHelper(
const FormatCountCapture& v, ConversionSpec conv, FormatSinkImpl* sink) { const FormatCountCapture& v, FormatConversionSpecImpl conv,
FormatSinkImpl* sink) {
const absl::enable_if_t<sizeof(T) != 0, FormatCountCapture>& v2 = v; const absl::enable_if_t<sizeof(T) != 0, FormatCountCapture>& v2 = v;
if (conv.conversion_char() != if (conv.conversion_char() !=
@ -212,7 +222,8 @@ struct FormatCountCaptureHelper {
template <class T = int> template <class T = int>
ArgConvertResult<FormatConversionCharSetInternal::n> FormatConvertImpl( ArgConvertResult<FormatConversionCharSetInternal::n> FormatConvertImpl(
const FormatCountCapture& v, ConversionSpec conv, FormatSinkImpl* sink) { const FormatCountCapture& v, FormatConversionSpecImpl conv,
FormatSinkImpl* sink) {
return FormatCountCaptureHelper::ConvertHelper(v, conv, sink); return FormatCountCaptureHelper::ConvertHelper(v, conv, sink);
} }
@ -221,13 +232,13 @@ ArgConvertResult<FormatConversionCharSetInternal::n> FormatConvertImpl(
struct FormatArgImplFriend { struct FormatArgImplFriend {
template <typename Arg> template <typename Arg>
static bool ToInt(Arg arg, int* out) { static bool ToInt(Arg arg, int* out) {
// A value initialized ConversionSpec has a `none` conv, which tells the // A value initialized FormatConversionSpecImpl has a `none` conv, which
// dispatcher to run the `int` conversion. // tells the dispatcher to run the `int` conversion.
return arg.dispatcher_(arg.data_, {}, out); return arg.dispatcher_(arg.data_, {}, out);
} }
template <typename Arg> template <typename Arg>
static bool Convert(Arg arg, str_format_internal::ConversionSpec conv, static bool Convert(Arg arg, FormatConversionSpecImpl conv,
FormatSinkImpl* out) { FormatSinkImpl* out) {
return arg.dispatcher_(arg.data_, conv, out); return arg.dispatcher_(arg.data_, conv, out);
} }
@ -251,7 +262,7 @@ class FormatArgImpl {
char buf[kInlinedSpace]; char buf[kInlinedSpace];
}; };
using Dispatcher = bool (*)(Data, ConversionSpec, void* out); using Dispatcher = bool (*)(Data, FormatConversionSpecImpl, void* out);
template <typename T> template <typename T>
struct store_by_value struct store_by_value
@ -393,7 +404,7 @@ class FormatArgImpl {
} }
template <typename T> 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. // A `none` conv indicates that we want the `int` conversion.
if (ABSL_PREDICT_FALSE(spec.conversion_char() == if (ABSL_PREDICT_FALSE(spec.conversion_char() ==
FormatConversionCharInternal::kNone)) { FormatConversionCharInternal::kNone)) {
@ -410,8 +421,9 @@ class FormatArgImpl {
Dispatcher dispatcher_; Dispatcher dispatcher_;
}; };
#define ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(T, E) \ #define ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(T, E) \
E template bool FormatArgImpl::Dispatch<T>(Data, ConversionSpec, void*) E template bool FormatArgImpl::Dispatch<T>(Data, FormatConversionSpecImpl, \
void*)
#define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(...) \ #define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(...) \
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(str_format_internal::VoidPtr, \ ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(str_format_internal::VoidPtr, \

View file

@ -95,8 +95,9 @@ TEST_F(FormatArgImplTest, OtherPtrDecayToVoidPtr) {
TEST_F(FormatArgImplTest, WorksWithCharArraysOfUnknownSize) { TEST_F(FormatArgImplTest, WorksWithCharArraysOfUnknownSize) {
std::string s; std::string s;
FormatSinkImpl sink(&s); FormatSinkImpl sink(&s);
ConversionSpec conv; FormatConversionSpecImpl conv;
FormatConversionSpecImplFriend::SetConversionChar(ConversionChar::s, &conv); FormatConversionSpecImplFriend::SetConversionChar(FormatConversionChar::s,
&conv);
FormatConversionSpecImplFriend::SetFlags(Flags(), &conv); FormatConversionSpecImplFriend::SetFlags(Flags(), &conv);
FormatConversionSpecImplFriend::SetWidth(-1, &conv); FormatConversionSpecImplFriend::SetWidth(-1, &conv);
FormatConversionSpecImplFriend::SetPrecision(-1, &conv); FormatConversionSpecImplFriend::SetPrecision(-1, &conv);

View file

@ -19,7 +19,7 @@ class UntypedFormatSpec;
namespace str_format_internal { namespace str_format_internal {
class BoundConversion : public ConversionSpec { class BoundConversion : public FormatConversionSpecImpl {
public: public:
const FormatArgImpl* arg() const { return arg_; } const FormatArgImpl* arg() const { return arg_; }
void set_arg(const FormatArgImpl* a) { arg_ = a; } void set_arg(const FormatArgImpl* a) { arg_ = a; }
@ -119,7 +119,7 @@ class FormatSpecTemplate
#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER #endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
template <Conv... C, template <FormatConversionCharSet... C,
typename = typename std::enable_if< typename = typename std::enable_if<
AllOf(sizeof...(C) == sizeof...(Args), Contains(Args, AllOf(sizeof...(C) == sizeof...(Args), Contains(Args,
C)...)>::type> C)...)>::type>
@ -190,7 +190,8 @@ class StreamedWrapper {
private: private:
template <typename S> template <typename S>
friend ArgConvertResult<FormatConversionCharSetInternal::s> FormatConvertImpl( friend ArgConvertResult<FormatConversionCharSetInternal::s> FormatConvertImpl(
const StreamedWrapper<S>& v, ConversionSpec conv, FormatSinkImpl* out); const StreamedWrapper<S>& v, FormatConversionSpecImpl conv,
FormatSinkImpl* out);
const T& v_; const T& v_;
}; };

View file

@ -24,7 +24,7 @@ std::string ConvToString(FormatConversionCharSet conv) {
} }
TEST(StrFormatChecker, ArgumentToConv) { TEST(StrFormatChecker, ArgumentToConv) {
Conv conv = ArgumentToConv<std::string>(); FormatConversionCharSet conv = ArgumentToConv<std::string>();
EXPECT_EQ(ConvToString(conv), "s"); EXPECT_EQ(ConvToString(conv), "s");
conv = ArgumentToConv<const char*>(); conv = ArgumentToConv<const char*>();

View file

@ -1,14 +1,18 @@
#include <errno.h> #include <errno.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <cctype> #include <cctype>
#include <cmath> #include <cmath>
#include <limits>
#include <string> #include <string>
#include <thread> // NOLINT
#include "gmock/gmock.h" #include "gmock/gmock.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "absl/base/internal/raw_logging.h" #include "absl/base/internal/raw_logging.h"
#include "absl/strings/internal/str_format/bind.h" #include "absl/strings/internal/str_format/bind.h"
#include "absl/types/optional.h"
namespace absl { namespace absl {
ABSL_NAMESPACE_BEGIN ABSL_NAMESPACE_BEGIN
@ -57,7 +61,7 @@ std::string Esc(const T &v) {
return oss.str(); 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 // First try with a small fixed size buffer
static const int kSpaceLength = 1024; static const int kSpaceLength = 1024;
char space[kSpaceLength]; char space[kSpaceLength];
@ -98,11 +102,18 @@ void StrAppend(std::string *dst, const char *format, va_list ap) {
delete[] buf; 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, ...) { std::string StrPrint(const char *format, ...) {
va_list ap; va_list ap;
va_start(ap, format); va_start(ap, format);
std::string result; std::string result;
StrAppend(&result, format, ap); StrAppendV(&result, format, ap);
va_end(ap); va_end(ap);
return result; return result;
} }
@ -471,8 +482,8 @@ TEST_F(FormatConvertTest, Float) {
#endif // _MSC_VER #endif // _MSC_VER
const char *const kFormats[] = { const char *const kFormats[] = {
"%", "%.3", "%8.5", "%9", "%.60", "%.30", "%03", "%+", "%", "%.3", "%8.5", "%500", "%.5000", "%.60", "%.30", "%03",
"% ", "%-10", "%#15.3", "%#.0", "%.0", "%1$*2$", "%1$.*2$"}; "%+", "% ", "%-10", "%#15.3", "%#.0", "%.0", "%1$*2$", "%1$.*2$"};
std::vector<double> doubles = {0.0, std::vector<double> doubles = {0.0,
-0.0, -0.0,
@ -489,11 +500,6 @@ TEST_F(FormatConvertTest, Float) {
std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity(),
-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. // Some regression tests.
doubles.push_back(0.99999999999999989); doubles.push_back(0.99999999999999989);
@ -512,43 +518,204 @@ TEST_F(FormatConvertTest, Float) {
} }
} }
// 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
// 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);
for (const char *fmt : kFormats) { for (const char *fmt : kFormats) {
for (char f : {'f', 'F', // for (char f : {'f', 'F', //
'g', 'G', // 'g', 'G', //
'a', 'A', // 'a', 'A', //
'e', 'E'}) { 'e', 'E'}) {
std::string fmt_str = std::string(fmt) + f; std::string fmt_str = std::string(fmt) + f;
if (fmt == absl::string_view("%.5000") && f != 'f' && f != 'F') {
// This particular test takes way too long with snprintf.
// Disable for the case we are not implementing natively.
continue;
}
for (double d : doubles) { for (double d : doubles) {
int i = -10; int i = -10;
FormatArgImpl args[2] = {FormatArgImpl(d), FormatArgImpl(i)}; FormatArgImpl args[2] = {FormatArgImpl(d), FormatArgImpl(i)};
UntypedFormatSpecImpl format(fmt_str); 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 string_printf_result.clear();
// time out. StrAppend(&string_printf_result, fmt_str.c_str(), d, i);
ASSERT_EQ(StrPrint(fmt_str.c_str(), d, i), str_format_result.clear();
FormatPack(format, absl::MakeSpan(args)))
<< fmt_str << " " << StrPrint("%.18g", d) << " " {
<< StrPrint("%.999f", d); 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("%.1080f", d);
}
} }
} }
} }
} }
TEST_F(FormatConvertTest, LongDouble) { TEST_F(FormatConvertTest, FloatRound) {
const char *const kFormats[] = {"%", "%.3", "%8.5", "%9", std::string s;
"%.60", "%+", "% ", "%-10"}; 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
// This value is not representable in double, but it is in long double that return s;
// uses the extended format. };
// This is to verify that we are not truncating the value mistakenly through a // All of these values have to be exactly represented.
// double. // Otherwise we might not be testing what we think we are testing.
long double very_precise = 10000000000000000.25L;
// 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, LongDouble) {
#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 char *const kFormats[] = {"%", "%.3", "%8.5", "%9", "%.5000",
"%.60", "%+", "% ", "%-10"};
std::vector<long double> doubles = { std::vector<long double> doubles = {
0.0, 0.0,
-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>::max(), -std::numeric_limits<long double>::max(),
std::numeric_limits<long double>::min(), std::numeric_limits<long double>::min(),
@ -556,22 +723,44 @@ TEST_F(FormatConvertTest, LongDouble) {
std::numeric_limits<long double>::infinity(), std::numeric_limits<long double>::infinity(),
-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));
}
}
}
for (const char *fmt : kFormats) { for (const char *fmt : kFormats) {
for (char f : {'f', 'F', // for (char f : {'f', 'F', //
'g', 'G', // 'g', 'G', //
'a', 'A', // 'a', 'A', //
'e', 'E'}) { 'e', 'E'}) {
std::string fmt_str = std::string(fmt) + 'L' + f; std::string fmt_str = std::string(fmt) + 'L' + f;
if (fmt == absl::string_view("%.5000") && f != 'f' && f != 'F') {
// This particular test takes way too long with snprintf.
// Disable for the case we are not implementing natively.
continue;
}
for (auto d : doubles) { for (auto d : doubles) {
FormatArgImpl arg(d); FormatArgImpl arg(d);
UntypedFormatSpecImpl format(fmt_str); UntypedFormatSpecImpl format(fmt_str);
// We use ASSERT_EQ here because failures are usually correlated and a // We use ASSERT_EQ here because failures are usually correlated and a
// bug would print way too many failed expectations causing the test to // bug would print way too many failed expectations causing the test to
// time out. // time out.
ASSERT_EQ(StrPrint(fmt_str.c_str(), d), ASSERT_EQ(StrPrint(fmt_str.c_str(), d), FormatPack(format, {&arg, 1}))
FormatPack(format, {&arg, 1}))
<< fmt_str << " " << StrPrint("%.18Lg", d) << " " << fmt_str << " " << StrPrint("%.18Lg", d) << " "
<< StrPrint("%.999Lf", d); << StrPrint("%La", d) << " " << StrPrint("%.1080Lf", d);
} }
} }
} }

View file

@ -411,11 +411,6 @@ inline size_t Excess(size_t used, size_t capacity) {
return used < capacity ? capacity - used : 0; return used < capacity ? capacity - used : 0;
} }
// Type alias for use during migration.
using ConversionChar = FormatConversionChar;
using ConversionSpec = FormatConversionSpecImpl;
using Conv = FormatConversionCharSet;
class FormatConversionSpec { class FormatConversionSpec {
public: public:
// Width and precison are not specified, no flags are set. // Width and precison are not specified, no flags are set.

View file

@ -1,12 +1,22 @@
#include "absl/strings/internal/str_format/float_conversion.h" #include "absl/strings/internal/str_format/float_conversion.h"
#include <string.h> #include <string.h>
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
#include <cmath> #include <cmath>
#include <limits>
#include <string> #include <string>
#include "absl/base/attributes.h"
#include "absl/base/config.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/types/optional.h"
#include "absl/types/span.h"
namespace absl { namespace absl {
ABSL_NAMESPACE_BEGIN ABSL_NAMESPACE_BEGIN
@ -14,13 +24,640 @@ namespace str_format_internal {
namespace { 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 size_t 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) {
assert(-exp < 0);
assert(-exp >= std::numeric_limits<long double>::min_exponent - 128);
static_assert(
StackArray::kMaxCapacity >=
(128 - std::numeric_limits<long double>::min_exponent + 31) / 32,
"");
StackArray::RunWithCapacity((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(int total_size, const FormatState &state) {
int missing_chars = std::max(state.conv.width() - total_size, 0);
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(absl::string_view data, int trailing_zeros,
const FormatState &state) {
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');
return;
}
auto padding =
ExtraWidthToPadding((state.sign_char != '\0' ? 1 : 0) +
static_cast<int>(data.size()) + trailing_zeros,
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');
state.sink->Append(data);
state.sink->Append(trailing_zeros, '0');
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(absl::string_view(integral_digits_start, size),
static_cast<int>(state.precision - (fractional_digits_end -
fractional_digits_start)),
state);
}
// 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 int total_digits =
btd.TotalDigits() + (state.ShouldPrintDot() ? 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 int total_digits =
/* 0 */ 1 + (state.ShouldPrintDot() ? 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);
}
char *CopyStringTo(absl::string_view v, char *out) {
std::memcpy(out, v.data(), v.size()); std::memcpy(out, v.data(), v.size());
return out + v.size(); return out + v.size();
} }
template <typename Float> template <typename Float>
bool FallbackToSnprintf(const Float v, const ConversionSpec &conv, bool FallbackToSnprintf(const Float v, const FormatConversionSpecImpl &conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
int w = conv.width() >= 0 ? conv.width() : 0; int w = conv.width() >= 0 ? conv.width() : 0;
int p = conv.precision() >= 0 ? conv.precision() : -1; int p = conv.precision() >= 0 ? conv.precision() : -1;
@ -38,12 +675,12 @@ bool FallbackToSnprintf(const Float v, const ConversionSpec &conv,
assert(fp < fmt + sizeof(fmt)); assert(fp < fmt + sizeof(fmt));
} }
std::string space(512, '\0'); std::string space(512, '\0');
string_view result; absl::string_view result;
while (true) { while (true) {
int n = snprintf(&space[0], space.size(), fmt, w, p, v); int n = snprintf(&space[0], space.size(), fmt, w, p, v);
if (n < 0) return false; if (n < 0) return false;
if (static_cast<size_t>(n) < space.size()) { if (static_cast<size_t>(n) < space.size()) {
result = string_view(space.data(), n); result = absl::string_view(space.data(), n);
break; break;
} }
space.resize(n + 1); space.resize(n + 1);
@ -96,9 +733,10 @@ enum class FormatStyle { Fixed, Precision };
// Otherwise, return false. // Otherwise, return false.
template <typename Float> template <typename Float>
bool ConvertNonNumericFloats(char sign_char, Float v, bool ConvertNonNumericFloats(char sign_char, Float v,
const ConversionSpec &conv, FormatSinkImpl *sink) { const FormatConversionSpecImpl &conv,
FormatSinkImpl *sink) {
char text[4], *ptr = text; char text[4], *ptr = text;
if (sign_char) *ptr++ = sign_char; if (sign_char != '\0') *ptr++ = sign_char;
if (std::isnan(v)) { if (std::isnan(v)) {
ptr = std::copy_n( ptr = std::copy_n(
FormatConversionCharIsUpper(conv.conversion_char()) ? "NAN" : "nan", 3, FormatConversionCharIsUpper(conv.conversion_char()) ? "NAN" : "nan", 3,
@ -172,7 +810,12 @@ constexpr bool CanFitMantissa() {
template <typename Float> template <typename Float>
struct Decomposed { 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; int exponent;
}; };
@ -183,7 +826,8 @@ Decomposed<Float> Decompose(Float v) {
Float m = std::frexp(v, &exp); Float m = std::frexp(v, &exp);
m = std::ldexp(m, std::numeric_limits<Float>::digits); m = std::ldexp(m, std::numeric_limits<Float>::digits);
exp -= 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. // Print 'digits' as decimal.
@ -352,8 +996,9 @@ bool FloatToBuffer(Decomposed<Float> decomposed, int precision, Buffer *out,
return false; return false;
} }
void WriteBufferToSink(char sign_char, string_view str, void WriteBufferToSink(char sign_char, absl::string_view str,
const ConversionSpec &conv, FormatSinkImpl *sink) { const FormatConversionSpecImpl &conv,
FormatSinkImpl *sink) {
int left_spaces = 0, zeros = 0, right_spaces = 0; int left_spaces = 0, zeros = 0, right_spaces = 0;
int missing_chars = int missing_chars =
conv.width() >= 0 ? std::max(conv.width() - static_cast<int>(str.size()) - conv.width() >= 0 ? std::max(conv.width() - static_cast<int>(str.size()) -
@ -369,14 +1014,14 @@ void WriteBufferToSink(char sign_char, string_view str,
} }
sink->Append(left_spaces, ' '); 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(zeros, '0');
sink->Append(str); sink->Append(str);
sink->Append(right_spaces, ' '); sink->Append(right_spaces, ' ');
} }
template <typename Float> template <typename Float>
bool FloatToSink(const Float v, const ConversionSpec &conv, bool FloatToSink(const Float v, const FormatConversionSpecImpl &conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
// Print the sign or the sign column. // Print the sign or the sign column.
Float abs_v = v; Float abs_v = v;
@ -407,11 +1052,9 @@ bool FloatToSink(const Float v, const ConversionSpec &conv,
if (c == FormatConversionCharInternal::f || if (c == FormatConversionCharInternal::f ||
c == FormatConversionCharInternal::F) { c == FormatConversionCharInternal::F) {
if (!FloatToBuffer<FormatStyle::Fixed>(decomposed, precision, &buffer, FormatF(decomposed.mantissa, decomposed.exponent,
nullptr)) { {sign_char, precision, conv, sink});
return FallbackToSnprintf(v, conv, sink); return true;
}
if (!conv.has_alt_flag() && buffer.back() == '.') buffer.pop_back();
} else if (c == FormatConversionCharInternal::e || } else if (c == FormatConversionCharInternal::e ||
c == FormatConversionCharInternal::E) { c == FormatConversionCharInternal::E) {
if (!FloatToBuffer<FormatStyle::Precision>(decomposed, precision, &buffer, if (!FloatToBuffer<FormatStyle::Precision>(decomposed, precision, &buffer,
@ -462,25 +1105,32 @@ bool FloatToSink(const Float v, const ConversionSpec &conv,
} }
WriteBufferToSink(sign_char, WriteBufferToSink(sign_char,
string_view(buffer.begin, buffer.end - buffer.begin), conv, absl::string_view(buffer.begin, buffer.end - buffer.begin),
sink); conv, sink);
return true; return true;
} }
} // namespace } // 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 FormatConversionSpecImpl &conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return FloatToSink(v, conv, sink); return FloatToSink(v, conv, sink);
} }
bool ConvertFloatImpl(float v, const ConversionSpec &conv, bool ConvertFloatImpl(double v, const FormatConversionSpecImpl &conv,
FormatSinkImpl *sink) {
return FloatToSink(v, conv, sink);
}
bool ConvertFloatImpl(double v, const ConversionSpec &conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return FloatToSink(v, conv, sink); return FloatToSink(v, conv, sink);
} }

View file

@ -7,13 +7,13 @@ namespace absl {
ABSL_NAMESPACE_BEGIN ABSL_NAMESPACE_BEGIN
namespace str_format_internal { namespace str_format_internal {
bool ConvertFloatImpl(float v, const ConversionSpec &conv, bool ConvertFloatImpl(float v, const FormatConversionSpecImpl &conv,
FormatSinkImpl *sink); FormatSinkImpl *sink);
bool ConvertFloatImpl(double v, const ConversionSpec &conv, bool ConvertFloatImpl(double v, const FormatConversionSpecImpl &conv,
FormatSinkImpl *sink); FormatSinkImpl *sink);
bool ConvertFloatImpl(long double v, const ConversionSpec &conv, bool ConvertFloatImpl(long double v, const FormatConversionSpecImpl &conv,
FormatSinkImpl *sink); FormatSinkImpl *sink);
} // namespace str_format_internal } // namespace str_format_internal

View file

@ -83,7 +83,7 @@ const char* ConsumeUnboundConversion(const char* p, const char* end,
// conversions. // conversions.
class ConvTag { class ConvTag {
public: public:
constexpr ConvTag(ConversionChar conversion_char) // NOLINT constexpr ConvTag(FormatConversionChar conversion_char) // NOLINT
: tag_(static_cast<int8_t>(conversion_char)) {} : tag_(static_cast<int8_t>(conversion_char)) {}
// We invert the length modifiers to make them negative so that we can easily // We invert the length modifiers to make them negative so that we can easily
// test for them. // test for them.
@ -94,9 +94,9 @@ class ConvTag {
bool is_conv() const { return tag_ >= 0; } bool is_conv() const { return tag_ >= 0; }
bool is_length() const { return tag_ < 0 && tag_ != -128; } bool is_length() const { return tag_ < 0 && tag_ != -128; }
ConversionChar as_conv() const { FormatConversionChar as_conv() const {
assert(is_conv()); assert(is_conv());
return static_cast<ConversionChar>(tag_); return static_cast<FormatConversionChar>(tag_);
} }
LengthMod as_length() const { LengthMod as_length() const {
assert(is_length()); assert(is_length());
@ -282,7 +282,7 @@ class ParsedFormatBase {
// This is the only API that allows the user to pass a runtime specified format // 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 // string. These factory functions will return NULL if the format does not match
// the conversions requested by the user. // the conversions requested by the user.
template <str_format_internal::Conv... C> template <FormatConversionCharSet... C>
class ExtendedParsedFormat : public str_format_internal::ParsedFormatBase { class ExtendedParsedFormat : public str_format_internal::ParsedFormatBase {
public: public:
explicit ExtendedParsedFormat(string_view format) explicit ExtendedParsedFormat(string_view format)

View file

@ -41,7 +41,7 @@ TEST(LengthModTest, Names) {
TEST(ConversionCharTest, Names) { TEST(ConversionCharTest, Names) {
struct Expectation { struct Expectation {
ConversionChar id; FormatConversionChar id;
char name; char name;
}; };
// clang-format off // clang-format off
@ -57,7 +57,7 @@ TEST(ConversionCharTest, Names) {
// clang-format on // clang-format on
for (auto e : kExpect) { for (auto e : kExpect) {
SCOPED_TRACE(e.name); SCOPED_TRACE(e.name);
ConversionChar v = e.id; FormatConversionChar v = e.id;
EXPECT_EQ(e.name, FormatConversionCharToChar(v)); EXPECT_EQ(e.name, FormatConversionCharToChar(v));
} }
} }
@ -368,7 +368,7 @@ TEST_F(ParsedFormatTest, ValueSemantics) {
struct ExpectParse { struct ExpectParse {
const char* in; const char* in;
std::initializer_list<Conv> conv_set; std::initializer_list<FormatConversionCharSet> conv_set;
const char* out; const char* out;
}; };

View file

@ -532,76 +532,103 @@ TEST_F(ParsedFormatTest, SimpleUncheckedIncorrect) {
EXPECT_FALSE((ParsedFormat<'s', 'd', 'g'>::New(format))); EXPECT_FALSE((ParsedFormat<'s', 'd', 'g'>::New(format)));
} }
using str_format_internal::Conv; using absl::str_format_internal::FormatConversionCharSet;
TEST_F(ParsedFormatTest, UncheckedCorrect) { TEST_F(ParsedFormatTest, UncheckedCorrect) {
auto f = ExtendedParsedFormat<Conv::d>::New("ABC%dDEF"); auto f = ExtendedParsedFormat<FormatConversionCharSet::d>::New("ABC%dDEF");
ASSERT_TRUE(f); ASSERT_TRUE(f);
EXPECT_EQ("[ABC]{d:1$d}[DEF]", SummarizeParsedFormat(*f)); EXPECT_EQ("[ABC]{d:1$d}[DEF]", SummarizeParsedFormat(*f));
std::string format = "%sFFF%dZZZ%f"; std::string format = "%sFFF%dZZZ%f";
auto f2 = ExtendedParsedFormat<Conv::kString, Conv::d, Conv::kFloating>::New( auto f2 =
format); ExtendedParsedFormat<FormatConversionCharSet::kString,
FormatConversionCharSet::d,
FormatConversionCharSet::kFloating>::New(format);
ASSERT_TRUE(f2); ASSERT_TRUE(f2);
EXPECT_EQ("{s:1$s}[FFF]{d:2$d}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2)); EXPECT_EQ("{s:1$s}[FFF]{d:2$d}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2));
f2 = ExtendedParsedFormat<Conv::kString, Conv::d, Conv::kFloating>::New( f2 =
"%s %d %f"); ExtendedParsedFormat<FormatConversionCharSet::kString,
FormatConversionCharSet::d,
FormatConversionCharSet::kFloating>::New("%s %d %f");
ASSERT_TRUE(f2); ASSERT_TRUE(f2);
EXPECT_EQ("{s:1$s}[ ]{d:2$d}[ ]{f:3$f}", SummarizeParsedFormat(*f2)); EXPECT_EQ("{s:1$s}[ ]{d:2$d}[ ]{f:3$f}", SummarizeParsedFormat(*f2));
auto star = ExtendedParsedFormat<Conv::kStar, Conv::d>::New("%*d"); auto star = ExtendedParsedFormat<FormatConversionCharSet::kStar,
FormatConversionCharSet::d>::New("%*d");
ASSERT_TRUE(star); ASSERT_TRUE(star);
EXPECT_EQ("{*d:2$1$*d}", SummarizeParsedFormat(*star)); EXPECT_EQ("{*d:2$1$*d}", SummarizeParsedFormat(*star));
auto dollar = ExtendedParsedFormat<Conv::d, Conv::s>::New("%2$s %1$d"); auto dollar =
ExtendedParsedFormat<FormatConversionCharSet::d,
FormatConversionCharSet::s>::New("%2$s %1$d");
ASSERT_TRUE(dollar); ASSERT_TRUE(dollar);
EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}", SummarizeParsedFormat(*dollar)); EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}", SummarizeParsedFormat(*dollar));
// with reuse // with reuse
dollar = ExtendedParsedFormat<Conv::d, Conv::s>::New("%2$s %1$d %1$d"); dollar =
ExtendedParsedFormat<FormatConversionCharSet::d,
FormatConversionCharSet::s>::New("%2$s %1$d %1$d");
ASSERT_TRUE(dollar); ASSERT_TRUE(dollar);
EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}[ ]{1$d:1$d}", EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}[ ]{1$d:1$d}",
SummarizeParsedFormat(*dollar)); SummarizeParsedFormat(*dollar));
} }
TEST_F(ParsedFormatTest, UncheckedIgnoredArgs) { TEST_F(ParsedFormatTest, UncheckedIgnoredArgs) {
EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::s>::New("ABC"))); EXPECT_FALSE((ExtendedParsedFormat<FormatConversionCharSet::d,
EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::s>::New("%dABC"))); FormatConversionCharSet::s>::New("ABC")));
EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::s>::New("ABC%2$s"))); EXPECT_FALSE(
auto f = ExtendedParsedFormat<Conv::d, Conv::s>::NewAllowIgnored("ABC"); (ExtendedParsedFormat<FormatConversionCharSet::d,
FormatConversionCharSet::s>::New("%dABC")));
EXPECT_FALSE(
(ExtendedParsedFormat<FormatConversionCharSet::d,
FormatConversionCharSet::s>::New("ABC%2$s")));
auto f =
ExtendedParsedFormat<FormatConversionCharSet::d,
FormatConversionCharSet::s>::NewAllowIgnored("ABC");
ASSERT_TRUE(f); ASSERT_TRUE(f);
EXPECT_EQ("[ABC]", SummarizeParsedFormat(*f)); EXPECT_EQ("[ABC]", SummarizeParsedFormat(*f));
f = ExtendedParsedFormat<Conv::d, Conv::s>::NewAllowIgnored("%dABC"); f = ExtendedParsedFormat<
FormatConversionCharSet::d,
FormatConversionCharSet::s>::NewAllowIgnored("%dABC");
ASSERT_TRUE(f); ASSERT_TRUE(f);
EXPECT_EQ("{d:1$d}[ABC]", SummarizeParsedFormat(*f)); EXPECT_EQ("{d:1$d}[ABC]", SummarizeParsedFormat(*f));
f = ExtendedParsedFormat<Conv::d, Conv::s>::NewAllowIgnored("ABC%2$s"); f = ExtendedParsedFormat<
FormatConversionCharSet::d,
FormatConversionCharSet::s>::NewAllowIgnored("ABC%2$s");
ASSERT_TRUE(f); ASSERT_TRUE(f);
EXPECT_EQ("[ABC]{2$s:2$s}", SummarizeParsedFormat(*f)); EXPECT_EQ("[ABC]{2$s:2$s}", SummarizeParsedFormat(*f));
} }
TEST_F(ParsedFormatTest, UncheckedMultipleTypes) { TEST_F(ParsedFormatTest, UncheckedMultipleTypes) {
auto dx = ExtendedParsedFormat<Conv::d | Conv::x>::New("%1$d %1$x"); auto dx = ExtendedParsedFormat<FormatConversionCharSet::d |
FormatConversionCharSet::x>::New("%1$d %1$x");
EXPECT_TRUE(dx); EXPECT_TRUE(dx);
EXPECT_EQ("{1$d:1$d}[ ]{1$x:1$x}", SummarizeParsedFormat(*dx)); EXPECT_EQ("{1$d:1$d}[ ]{1$x:1$x}", SummarizeParsedFormat(*dx));
dx = ExtendedParsedFormat<Conv::d | Conv::x>::New("%1$d"); dx = ExtendedParsedFormat<FormatConversionCharSet::d |
FormatConversionCharSet::x>::New("%1$d");
EXPECT_TRUE(dx); EXPECT_TRUE(dx);
EXPECT_EQ("{1$d:1$d}", SummarizeParsedFormat(*dx)); EXPECT_EQ("{1$d:1$d}", SummarizeParsedFormat(*dx));
} }
TEST_F(ParsedFormatTest, UncheckedIncorrect) { TEST_F(ParsedFormatTest, UncheckedIncorrect) {
EXPECT_FALSE(ExtendedParsedFormat<Conv::d>::New("")); EXPECT_FALSE(ExtendedParsedFormat<FormatConversionCharSet::d>::New(""));
EXPECT_FALSE(ExtendedParsedFormat<Conv::d>::New("ABC%dDEF%d")); EXPECT_FALSE(
ExtendedParsedFormat<FormatConversionCharSet::d>::New("ABC%dDEF%d"));
std::string format = "%sFFF%dZZZ%f"; std::string format = "%sFFF%dZZZ%f";
EXPECT_FALSE((ExtendedParsedFormat<Conv::s, Conv::d, Conv::g>::New(format))); EXPECT_FALSE((ExtendedParsedFormat<FormatConversionCharSet::s,
FormatConversionCharSet::d,
FormatConversionCharSet::g>::New(format)));
} }
TEST_F(ParsedFormatTest, RegressionMixPositional) { TEST_F(ParsedFormatTest, RegressionMixPositional) {
EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::o>::New("%1$d %o"))); EXPECT_FALSE(
(ExtendedParsedFormat<FormatConversionCharSet::d,
FormatConversionCharSet::o>::New("%1$d %o")));
} }
using FormatWrapperTest = ::testing::Test; using FormatWrapperTest = ::testing::Test;

View file

@ -50,7 +50,7 @@
// //
// Supported types: // Supported types:
// * absl::string_view, std::string, const char* (null is equivalent to "") // * absl::string_view, std::string, const char* (null is equivalent to "")
// * int32_t, int64_t, uint32_t, uint64 // * int32_t, int64_t, uint32_t, uint64_t
// * float, double // * float, double
// * bool (Printed as "true" or "false") // * bool (Printed as "true" or "false")
// * pointer types other than char* (Printed as "0x<lower case hex string>", // * pointer types other than char* (Printed as "0x<lower case hex string>",