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:
		
							parent
							
								
									a1d6689907
								
							
						
					
					
						commit
						d85783fd0b
					
				
					 24 changed files with 1112 additions and 197 deletions
				
			
		|  | @ -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( | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|  |  | ||||||
|  | @ -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.
 | ||||||
|  |  | ||||||
|  | @ -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:
 | ||||||
| //
 | //
 | ||||||
|  |  | ||||||
|  | @ -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()), | ||||||
|  |  | ||||||
|  | @ -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)); | ||||||
|  |  | ||||||
|  | @ -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", | ||||||
|     ], |     ], | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 { | ||||||
|  |  | ||||||
|  | @ -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) { | ||||||
|  |  | ||||||
|  | @ -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)}; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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,     \ | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|  |  | ||||||
|  | @ -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_; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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*>(); | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -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.
 | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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
 | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|  |  | ||||||
|  | @ -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>",
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue