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…
Reference in a new issue