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,
|
||||
linkopts = ABSL_DEFAULT_LINKOPTS,
|
||||
tags = ["no_test_ios_x86_64"],
|
||||
deps = [":malloc_internal"],
|
||||
deps = [
|
||||
":malloc_internal",
|
||||
"//absl/container:node_hash_map",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
|
|
|
@ -497,6 +497,7 @@ absl_cc_test(
|
|||
${ABSL_TEST_COPTS}
|
||||
DEPS
|
||||
absl::malloc_internal
|
||||
absl::node_hash_map
|
||||
Threads::Threads
|
||||
)
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/container/node_hash_map.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace base_internal {
|
||||
|
@ -75,7 +77,7 @@ static bool using_low_level_alloc = false;
|
|||
// allocations and deallocations are reported via the MallocHook
|
||||
// interface.
|
||||
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::iterator it;
|
||||
BlockDesc block_desc;
|
||||
|
|
|
@ -149,13 +149,15 @@ struct FileMappingHint {
|
|||
// Moreover, we are using only TryLock(), if the decorator list
|
||||
// is being modified (is busy), we skip all decorators, and possibly
|
||||
// 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;
|
||||
int g_num_file_mapping_hints;
|
||||
FileMappingHint g_file_mapping_hints[kMaxFileMappingHints];
|
||||
// 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.
|
||||
// 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`.
|
||||
//
|
||||
// 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
|
||||
// absl::Hash<int>()(9) in another process are likely to differ.
|
||||
// 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` is intended to strongly mix input bits with a target of passing
|
||||
// an [Avalanche Test](https://en.wikipedia.org/wiki/Avalanche_effect).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
|
|
|
@ -130,12 +130,15 @@ TYPED_TEST(GaussianDistributionInterfaceTest, SerializeTest) {
|
|||
ss >> after;
|
||||
|
||||
#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) {
|
||||
// Roundtripping floating point values requires sufficient precision
|
||||
// to reconstruct the exact value. It turns out that long double
|
||||
// has some errors doing this on ppc, particularly for values
|
||||
// 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() &&
|
||||
mean >= std::numeric_limits<double>::lowest()) {
|
||||
EXPECT_EQ(static_cast<double>(before.mean()),
|
||||
|
|
|
@ -28,7 +28,7 @@ TEST(WideMultiplyTest, MultiplyU64ToU128Test) {
|
|||
|
||||
EXPECT_EQ(absl::uint128(0), MultiplyU64ToU128(0, 0));
|
||||
|
||||
// Max uint64
|
||||
// Max uint64_t
|
||||
EXPECT_EQ(MultiplyU64ToU128(kMax, kMax),
|
||||
absl::MakeUint128(0xfffffffffffffffe, 0x0000000000000001));
|
||||
EXPECT_EQ(absl::MakeUint128(0, kMax), MultiplyU64ToU128(kMax, 1));
|
||||
|
|
|
@ -638,10 +638,13 @@ cc_library(
|
|||
visibility = ["//visibility:private"],
|
||||
deps = [
|
||||
":strings",
|
||||
"//absl/base:bits",
|
||||
"//absl/base:config",
|
||||
"//absl/base:core_headers",
|
||||
"//absl/functional:function_ref",
|
||||
"//absl/meta:type_traits",
|
||||
"//absl/numeric:int128",
|
||||
"//absl/types:optional",
|
||||
"//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
@ -718,7 +721,7 @@ cc_test(
|
|||
deps = [
|
||||
":str_format_internal",
|
||||
"//absl/base:raw_logging_internal",
|
||||
"//absl/numeric:int128",
|
||||
"//absl/types:optional",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -392,6 +392,7 @@ absl_cc_library(
|
|||
COPTS
|
||||
${ABSL_DEFAULT_COPTS}
|
||||
DEPS
|
||||
absl::bits
|
||||
absl::strings
|
||||
absl::config
|
||||
absl::core_headers
|
||||
|
|
|
@ -162,7 +162,7 @@ class Cord {
|
|||
if (contents_.is_tree()) DestroyCordSlow();
|
||||
}
|
||||
|
||||
// Cord::MakeCordFromExternal(data, callable)
|
||||
// MakeCordFromExternal()
|
||||
//
|
||||
// Creates a Cord that takes ownership of external string memory. The
|
||||
// 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()).
|
||||
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()
|
||||
//
|
||||
// Swaps the data of Cord `x` with Cord `y`.
|
||||
friend void swap(Cord& x, Cord& y) noexcept;
|
||||
// Swaps the contents of two Cords.
|
||||
friend void swap(Cord& x, Cord& y) noexcept {
|
||||
x.swap(y);
|
||||
}
|
||||
|
||||
// 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 void Cord::swap(Cord& other) noexcept {
|
||||
contents_.Swap(&other.contents_);
|
||||
}
|
||||
|
||||
inline Cord& Cord::operator=(Cord&& x) noexcept {
|
||||
contents_ = std::move(x.contents_);
|
||||
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>=(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.
|
||||
namespace strings_internal {
|
||||
class CordTestAccess {
|
||||
|
|
|
@ -396,6 +396,9 @@ TEST(Cord, Swap) {
|
|||
swap(x, y);
|
||||
ASSERT_EQ(x, absl::Cord(b));
|
||||
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) {
|
||||
|
|
|
@ -167,24 +167,26 @@ class IntDigits {
|
|||
// Note: 'o' conversions do not have a base indicator, it's just that
|
||||
// the '#' flag is specified to modify the precision for 'o' conversions.
|
||||
string_view BaseIndicator(const IntDigits &as_digits,
|
||||
const ConversionSpec conv) {
|
||||
const FormatConversionSpecImpl conv) {
|
||||
// always show 0x for %p.
|
||||
bool alt = conv.has_alt_flag() || conv.conversion_char() == ConversionChar::p;
|
||||
bool hex = (conv.conversion_char() == FormatConversionChar::x ||
|
||||
conv.conversion_char() == FormatConversionChar::X ||
|
||||
conv.conversion_char() == FormatConversionChar::p);
|
||||
bool alt = conv.has_alt_flag() ||
|
||||
conv.conversion_char() == FormatConversionCharInternal::p;
|
||||
bool hex = (conv.conversion_char() == FormatConversionCharInternal::x ||
|
||||
conv.conversion_char() == FormatConversionCharInternal::X ||
|
||||
conv.conversion_char() == FormatConversionCharInternal::p);
|
||||
// From the POSIX description of '#' flag:
|
||||
// "For x or X conversion specifiers, a non-zero result shall have
|
||||
// 0x (or 0X) prefixed to it."
|
||||
if (alt && 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 {};
|
||||
}
|
||||
|
||||
string_view SignColumn(bool neg, const ConversionSpec conv) {
|
||||
if (conv.conversion_char() == FormatConversionChar::d ||
|
||||
conv.conversion_char() == FormatConversionChar::i) {
|
||||
string_view SignColumn(bool neg, const FormatConversionSpecImpl conv) {
|
||||
if (conv.conversion_char() == FormatConversionCharInternal::d ||
|
||||
conv.conversion_char() == FormatConversionCharInternal::i) {
|
||||
if (neg) return "-";
|
||||
if (conv.has_show_pos_flag()) return "+";
|
||||
if (conv.has_sign_col_flag()) return " ";
|
||||
|
@ -192,7 +194,7 @@ string_view SignColumn(bool neg, const ConversionSpec conv) {
|
|||
return {};
|
||||
}
|
||||
|
||||
bool ConvertCharImpl(unsigned char v, const ConversionSpec conv,
|
||||
bool ConvertCharImpl(unsigned char v, const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
size_t fill = 0;
|
||||
if (conv.width() >= 0) fill = conv.width();
|
||||
|
@ -204,7 +206,8 @@ bool ConvertCharImpl(unsigned char v, const ConversionSpec conv,
|
|||
}
|
||||
|
||||
bool ConvertIntImplInnerSlow(const IntDigits &as_digits,
|
||||
const ConversionSpec conv, FormatSinkImpl *sink) {
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
// Print as a sequence of Substrings:
|
||||
// [left_spaces][sign][base_indicator][zeroes][formatted][right_spaces]
|
||||
size_t fill = 0;
|
||||
|
@ -224,7 +227,8 @@ bool ConvertIntImplInnerSlow(const IntDigits &as_digits,
|
|||
if (!precision_specified)
|
||||
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:
|
||||
// "For o conversion, it increases the precision (if necessary) to
|
||||
// force the first digit of the result to be zero."
|
||||
|
@ -258,42 +262,43 @@ bool ConvertIntImplInnerSlow(const IntDigits &as_digits,
|
|||
}
|
||||
|
||||
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;
|
||||
IntDigits as_digits;
|
||||
|
||||
switch (conv.conversion_char()) {
|
||||
case FormatConversionChar::c:
|
||||
case FormatConversionCharInternal::c:
|
||||
return ConvertCharImpl(static_cast<unsigned char>(v), conv, sink);
|
||||
|
||||
case FormatConversionChar::o:
|
||||
case FormatConversionCharInternal::o:
|
||||
as_digits.PrintAsOct(static_cast<U>(v));
|
||||
break;
|
||||
|
||||
case FormatConversionChar::x:
|
||||
case FormatConversionCharInternal::x:
|
||||
as_digits.PrintAsHexLower(static_cast<U>(v));
|
||||
break;
|
||||
case FormatConversionChar::X:
|
||||
case FormatConversionCharInternal::X:
|
||||
as_digits.PrintAsHexUpper(static_cast<U>(v));
|
||||
break;
|
||||
|
||||
case FormatConversionChar::u:
|
||||
case FormatConversionCharInternal::u:
|
||||
as_digits.PrintAsDec(static_cast<U>(v));
|
||||
break;
|
||||
|
||||
case FormatConversionChar::d:
|
||||
case FormatConversionChar::i:
|
||||
case FormatConversionCharInternal::d:
|
||||
case FormatConversionCharInternal::i:
|
||||
as_digits.PrintAsDec(v);
|
||||
break;
|
||||
|
||||
case FormatConversionChar::a:
|
||||
case FormatConversionChar::e:
|
||||
case FormatConversionChar::f:
|
||||
case FormatConversionChar::g:
|
||||
case FormatConversionChar::A:
|
||||
case FormatConversionChar::E:
|
||||
case FormatConversionChar::F:
|
||||
case FormatConversionChar::G:
|
||||
case FormatConversionCharInternal::a:
|
||||
case FormatConversionCharInternal::e:
|
||||
case FormatConversionCharInternal::f:
|
||||
case FormatConversionCharInternal::g:
|
||||
case FormatConversionCharInternal::A:
|
||||
case FormatConversionCharInternal::E:
|
||||
case FormatConversionCharInternal::F:
|
||||
case FormatConversionCharInternal::G:
|
||||
return ConvertFloatImpl(static_cast<double>(v), conv, sink);
|
||||
|
||||
default:
|
||||
|
@ -308,12 +313,13 @@ bool ConvertIntArg(T v, const ConversionSpec conv, FormatSinkImpl *sink) {
|
|||
}
|
||||
|
||||
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()) &&
|
||||
ConvertFloatImpl(v, conv, sink);
|
||||
}
|
||||
|
||||
inline bool ConvertStringArg(string_view v, const ConversionSpec conv,
|
||||
inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
if (conv.conversion_char() != FormatConversionCharInternal::s) return false;
|
||||
if (conv.is_basic()) {
|
||||
|
@ -328,19 +334,20 @@ inline bool ConvertStringArg(string_view v, const ConversionSpec conv,
|
|||
|
||||
// ==================== Strings ====================
|
||||
StringConvertResult FormatConvertImpl(const std::string &v,
|
||||
const ConversionSpec conv,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertStringArg(v, conv, sink)};
|
||||
}
|
||||
|
||||
StringConvertResult FormatConvertImpl(string_view v, const ConversionSpec conv,
|
||||
StringConvertResult FormatConvertImpl(string_view v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertStringArg(v, conv, sink)};
|
||||
}
|
||||
|
||||
ArgConvertResult<FormatConversionCharSetUnion(
|
||||
FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::p)>
|
||||
FormatConvertImpl(const char *v, const ConversionSpec conv,
|
||||
FormatConvertImpl(const char *v, const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
if (conv.conversion_char() == FormatConversionCharInternal::p)
|
||||
return {FormatConvertImpl(VoidPtr(v), conv, sink).value};
|
||||
|
@ -358,7 +365,7 @@ FormatConvertImpl(const char *v, const ConversionSpec conv,
|
|||
|
||||
// ==================== Raw pointers ====================
|
||||
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 (!v.value) {
|
||||
sink->Append("(nil)");
|
||||
|
@ -370,82 +377,87 @@ ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl(
|
|||
}
|
||||
|
||||
// ==================== Floats ====================
|
||||
FloatingConvertResult FormatConvertImpl(float v, const ConversionSpec conv,
|
||||
FloatingConvertResult FormatConvertImpl(float v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertFloatArg(v, conv, sink)};
|
||||
}
|
||||
FloatingConvertResult FormatConvertImpl(double v, const ConversionSpec conv,
|
||||
FloatingConvertResult FormatConvertImpl(double v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertFloatArg(v, conv, sink)};
|
||||
}
|
||||
FloatingConvertResult FormatConvertImpl(long double v,
|
||||
const ConversionSpec conv,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertFloatArg(v, conv, sink)};
|
||||
}
|
||||
|
||||
// ==================== Chars ====================
|
||||
IntegralConvertResult FormatConvertImpl(char v, const ConversionSpec conv,
|
||||
IntegralConvertResult FormatConvertImpl(char v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(signed char v,
|
||||
const ConversionSpec conv,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(unsigned char v,
|
||||
const ConversionSpec conv,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
|
||||
// ==================== Ints ====================
|
||||
IntegralConvertResult FormatConvertImpl(short v, // NOLINT
|
||||
const ConversionSpec conv,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(unsigned short v, // NOLINT
|
||||
const ConversionSpec conv,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(int v, const ConversionSpec conv,
|
||||
IntegralConvertResult FormatConvertImpl(int v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(unsigned v, const ConversionSpec conv,
|
||||
IntegralConvertResult FormatConvertImpl(unsigned v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(long v, // NOLINT
|
||||
const ConversionSpec conv,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(unsigned long v, // NOLINT
|
||||
const ConversionSpec conv,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(long long v, // NOLINT
|
||||
const ConversionSpec conv,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(unsigned long long v, // NOLINT
|
||||
const ConversionSpec conv,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(absl::int128 v,
|
||||
const ConversionSpec conv,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(absl::uint128 v,
|
||||
const ConversionSpec conv,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
|
|
|
@ -67,20 +67,24 @@ constexpr FormatConversionCharSet ExtractCharSet(ArgConvertResult<C>) {
|
|||
using StringConvertResult =
|
||||
ArgConvertResult<FormatConversionCharSetInternal::s>;
|
||||
ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl(
|
||||
VoidPtr v, ConversionSpec conv, FormatSinkImpl* sink);
|
||||
VoidPtr v, FormatConversionSpecImpl conv, FormatSinkImpl* sink);
|
||||
|
||||
// Strings.
|
||||
StringConvertResult FormatConvertImpl(const std::string& v, ConversionSpec conv,
|
||||
StringConvertResult FormatConvertImpl(const std::string& v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
StringConvertResult FormatConvertImpl(string_view v, ConversionSpec conv,
|
||||
StringConvertResult FormatConvertImpl(string_view v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
ArgConvertResult<FormatConversionCharSetUnion(
|
||||
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<
|
||||
AbslCord, absl::Cord>::value>::type* = nullptr>
|
||||
StringConvertResult FormatConvertImpl(const AbslCord& value,
|
||||
ConversionSpec conv,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink) {
|
||||
if (conv.conversion_char() != FormatConversionCharInternal::s) {
|
||||
return {false};
|
||||
|
@ -127,50 +131,55 @@ using FloatingConvertResult =
|
|||
ArgConvertResult<FormatConversionCharSetInternal::kFloating>;
|
||||
|
||||
// Floats.
|
||||
FloatingConvertResult FormatConvertImpl(float v, ConversionSpec conv,
|
||||
FloatingConvertResult FormatConvertImpl(float v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
FloatingConvertResult FormatConvertImpl(double v, ConversionSpec conv,
|
||||
FloatingConvertResult FormatConvertImpl(double v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
FloatingConvertResult FormatConvertImpl(long double v, ConversionSpec conv,
|
||||
FloatingConvertResult FormatConvertImpl(long double v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
|
||||
// Chars.
|
||||
IntegralConvertResult FormatConvertImpl(char v, ConversionSpec conv,
|
||||
IntegralConvertResult FormatConvertImpl(char v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(signed char v, ConversionSpec conv,
|
||||
IntegralConvertResult FormatConvertImpl(signed char v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(unsigned char v, ConversionSpec conv,
|
||||
IntegralConvertResult FormatConvertImpl(unsigned char v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
|
||||
// Ints.
|
||||
IntegralConvertResult FormatConvertImpl(short v, // NOLINT
|
||||
ConversionSpec conv,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(unsigned short v, // NOLINT
|
||||
ConversionSpec conv,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(int v, ConversionSpec conv,
|
||||
IntegralConvertResult FormatConvertImpl(int v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(unsigned v, ConversionSpec conv,
|
||||
IntegralConvertResult FormatConvertImpl(unsigned v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(long v, // NOLINT
|
||||
ConversionSpec conv,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(unsigned long v, // NOLINT
|
||||
ConversionSpec conv,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(long long v, // NOLINT
|
||||
ConversionSpec conv,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(unsigned long long v, // NOLINT
|
||||
ConversionSpec conv,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(int128 v, ConversionSpec conv,
|
||||
IntegralConvertResult FormatConvertImpl(int128 v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(uint128 v, ConversionSpec conv,
|
||||
IntegralConvertResult FormatConvertImpl(uint128 v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
template <typename T, enable_if_t<std::is_same<T, bool>::value, int> = 0>
|
||||
IntegralConvertResult FormatConvertImpl(T v, ConversionSpec conv,
|
||||
IntegralConvertResult FormatConvertImpl(T v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink) {
|
||||
return FormatConvertImpl(static_cast<int>(v), conv, sink);
|
||||
}
|
||||
|
@ -181,11 +190,11 @@ template <typename T>
|
|||
typename std::enable_if<std::is_enum<T>::value &&
|
||||
!HasUserDefinedConvert<T>::value,
|
||||
IntegralConvertResult>::type
|
||||
FormatConvertImpl(T v, ConversionSpec conv, FormatSinkImpl* sink);
|
||||
FormatConvertImpl(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink);
|
||||
|
||||
template <typename T>
|
||||
StringConvertResult FormatConvertImpl(const StreamedWrapper<T>& v,
|
||||
ConversionSpec conv,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* out) {
|
||||
std::ostringstream oss;
|
||||
oss << v.v_;
|
||||
|
@ -198,7 +207,8 @@ StringConvertResult FormatConvertImpl(const StreamedWrapper<T>& v,
|
|||
struct FormatCountCaptureHelper {
|
||||
template <class T = int>
|
||||
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;
|
||||
|
||||
if (conv.conversion_char() !=
|
||||
|
@ -212,7 +222,8 @@ struct FormatCountCaptureHelper {
|
|||
|
||||
template <class T = int>
|
||||
ArgConvertResult<FormatConversionCharSetInternal::n> FormatConvertImpl(
|
||||
const FormatCountCapture& v, ConversionSpec conv, FormatSinkImpl* sink) {
|
||||
const FormatCountCapture& v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink) {
|
||||
return FormatCountCaptureHelper::ConvertHelper(v, conv, sink);
|
||||
}
|
||||
|
||||
|
@ -221,13 +232,13 @@ ArgConvertResult<FormatConversionCharSetInternal::n> FormatConvertImpl(
|
|||
struct FormatArgImplFriend {
|
||||
template <typename Arg>
|
||||
static bool ToInt(Arg arg, int* out) {
|
||||
// A value initialized ConversionSpec has a `none` conv, which tells the
|
||||
// dispatcher to run the `int` conversion.
|
||||
// A value initialized FormatConversionSpecImpl has a `none` conv, which
|
||||
// tells the dispatcher to run the `int` conversion.
|
||||
return arg.dispatcher_(arg.data_, {}, out);
|
||||
}
|
||||
|
||||
template <typename Arg>
|
||||
static bool Convert(Arg arg, str_format_internal::ConversionSpec conv,
|
||||
static bool Convert(Arg arg, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* out) {
|
||||
return arg.dispatcher_(arg.data_, conv, out);
|
||||
}
|
||||
|
@ -251,7 +262,7 @@ class FormatArgImpl {
|
|||
char buf[kInlinedSpace];
|
||||
};
|
||||
|
||||
using Dispatcher = bool (*)(Data, ConversionSpec, void* out);
|
||||
using Dispatcher = bool (*)(Data, FormatConversionSpecImpl, void* out);
|
||||
|
||||
template <typename T>
|
||||
struct store_by_value
|
||||
|
@ -393,7 +404,7 @@ class FormatArgImpl {
|
|||
}
|
||||
|
||||
template <typename T>
|
||||
static bool Dispatch(Data arg, ConversionSpec spec, void* out) {
|
||||
static bool Dispatch(Data arg, FormatConversionSpecImpl spec, void* out) {
|
||||
// A `none` conv indicates that we want the `int` conversion.
|
||||
if (ABSL_PREDICT_FALSE(spec.conversion_char() ==
|
||||
FormatConversionCharInternal::kNone)) {
|
||||
|
@ -410,8 +421,9 @@ class FormatArgImpl {
|
|||
Dispatcher dispatcher_;
|
||||
};
|
||||
|
||||
#define ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(T, E) \
|
||||
E template bool FormatArgImpl::Dispatch<T>(Data, ConversionSpec, void*)
|
||||
#define ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(T, E) \
|
||||
E template bool FormatArgImpl::Dispatch<T>(Data, FormatConversionSpecImpl, \
|
||||
void*)
|
||||
|
||||
#define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(...) \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(str_format_internal::VoidPtr, \
|
||||
|
|
|
@ -95,8 +95,9 @@ TEST_F(FormatArgImplTest, OtherPtrDecayToVoidPtr) {
|
|||
TEST_F(FormatArgImplTest, WorksWithCharArraysOfUnknownSize) {
|
||||
std::string s;
|
||||
FormatSinkImpl sink(&s);
|
||||
ConversionSpec conv;
|
||||
FormatConversionSpecImplFriend::SetConversionChar(ConversionChar::s, &conv);
|
||||
FormatConversionSpecImpl conv;
|
||||
FormatConversionSpecImplFriend::SetConversionChar(FormatConversionChar::s,
|
||||
&conv);
|
||||
FormatConversionSpecImplFriend::SetFlags(Flags(), &conv);
|
||||
FormatConversionSpecImplFriend::SetWidth(-1, &conv);
|
||||
FormatConversionSpecImplFriend::SetPrecision(-1, &conv);
|
||||
|
|
|
@ -19,7 +19,7 @@ class UntypedFormatSpec;
|
|||
|
||||
namespace str_format_internal {
|
||||
|
||||
class BoundConversion : public ConversionSpec {
|
||||
class BoundConversion : public FormatConversionSpecImpl {
|
||||
public:
|
||||
const FormatArgImpl* arg() const { return arg_; }
|
||||
void set_arg(const FormatArgImpl* a) { arg_ = a; }
|
||||
|
@ -119,7 +119,7 @@ class FormatSpecTemplate
|
|||
|
||||
#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
|
||||
|
||||
template <Conv... C,
|
||||
template <FormatConversionCharSet... C,
|
||||
typename = typename std::enable_if<
|
||||
AllOf(sizeof...(C) == sizeof...(Args), Contains(Args,
|
||||
C)...)>::type>
|
||||
|
@ -190,7 +190,8 @@ class StreamedWrapper {
|
|||
private:
|
||||
template <typename S>
|
||||
friend ArgConvertResult<FormatConversionCharSetInternal::s> FormatConvertImpl(
|
||||
const StreamedWrapper<S>& v, ConversionSpec conv, FormatSinkImpl* out);
|
||||
const StreamedWrapper<S>& v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* out);
|
||||
const T& v_;
|
||||
};
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ std::string ConvToString(FormatConversionCharSet conv) {
|
|||
}
|
||||
|
||||
TEST(StrFormatChecker, ArgumentToConv) {
|
||||
Conv conv = ArgumentToConv<std::string>();
|
||||
FormatConversionCharSet conv = ArgumentToConv<std::string>();
|
||||
EXPECT_EQ(ConvToString(conv), "s");
|
||||
|
||||
conv = ArgumentToConv<const char*>();
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <thread> // NOLINT
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/base/internal/raw_logging.h"
|
||||
#include "absl/strings/internal/str_format/bind.h"
|
||||
#include "absl/types/optional.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
|
@ -57,7 +61,7 @@ std::string Esc(const T &v) {
|
|||
return oss.str();
|
||||
}
|
||||
|
||||
void StrAppend(std::string *dst, const char *format, va_list ap) {
|
||||
void StrAppendV(std::string *dst, const char *format, va_list ap) {
|
||||
// First try with a small fixed size buffer
|
||||
static const int kSpaceLength = 1024;
|
||||
char space[kSpaceLength];
|
||||
|
@ -98,11 +102,18 @@ void StrAppend(std::string *dst, const char *format, va_list ap) {
|
|||
delete[] buf;
|
||||
}
|
||||
|
||||
void StrAppend(std::string *out, const char *format, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
StrAppendV(out, format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
std::string StrPrint(const char *format, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
std::string result;
|
||||
StrAppend(&result, format, ap);
|
||||
StrAppendV(&result, format, ap);
|
||||
va_end(ap);
|
||||
return result;
|
||||
}
|
||||
|
@ -471,8 +482,8 @@ TEST_F(FormatConvertTest, Float) {
|
|||
#endif // _MSC_VER
|
||||
|
||||
const char *const kFormats[] = {
|
||||
"%", "%.3", "%8.5", "%9", "%.60", "%.30", "%03", "%+",
|
||||
"% ", "%-10", "%#15.3", "%#.0", "%.0", "%1$*2$", "%1$.*2$"};
|
||||
"%", "%.3", "%8.5", "%500", "%.5000", "%.60", "%.30", "%03",
|
||||
"%+", "% ", "%-10", "%#15.3", "%#.0", "%.0", "%1$*2$", "%1$.*2$"};
|
||||
|
||||
std::vector<double> doubles = {0.0,
|
||||
-0.0,
|
||||
|
@ -489,11 +500,6 @@ TEST_F(FormatConvertTest, Float) {
|
|||
std::numeric_limits<double>::infinity(),
|
||||
-std::numeric_limits<double>::infinity()};
|
||||
|
||||
#ifndef __APPLE__
|
||||
// Apple formats NaN differently (+nan) vs. (nan)
|
||||
doubles.push_back(std::nan(""));
|
||||
#endif
|
||||
|
||||
// Some regression tests.
|
||||
doubles.push_back(0.99999999999999989);
|
||||
|
||||
|
@ -512,43 +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 (char f : {'f', 'F', //
|
||||
'g', 'G', //
|
||||
'a', 'A', //
|
||||
'e', 'E'}) {
|
||||
std::string fmt_str = std::string(fmt) + f;
|
||||
|
||||
if (fmt == absl::string_view("%.5000") && f != 'f' && f != 'F') {
|
||||
// This particular test takes way too long with snprintf.
|
||||
// Disable for the case we are not implementing natively.
|
||||
continue;
|
||||
}
|
||||
|
||||
for (double d : doubles) {
|
||||
int i = -10;
|
||||
FormatArgImpl args[2] = {FormatArgImpl(d), FormatArgImpl(i)};
|
||||
UntypedFormatSpecImpl format(fmt_str);
|
||||
// We use ASSERT_EQ here because failures are usually correlated and a
|
||||
// bug would print way too many failed expectations causing the test to
|
||||
// time out.
|
||||
ASSERT_EQ(StrPrint(fmt_str.c_str(), d, i),
|
||||
FormatPack(format, absl::MakeSpan(args)))
|
||||
<< fmt_str << " " << StrPrint("%.18g", d) << " "
|
||||
<< StrPrint("%.999f", d);
|
||||
|
||||
string_printf_result.clear();
|
||||
StrAppend(&string_printf_result, fmt_str.c_str(), d, i);
|
||||
str_format_result.clear();
|
||||
|
||||
{
|
||||
AppendPack(&str_format_result, format, absl::MakeSpan(args));
|
||||
}
|
||||
|
||||
if (string_printf_result != str_format_result) {
|
||||
// We use ASSERT_EQ here because failures are usually correlated and a
|
||||
// bug would print way too many failed expectations causing the test
|
||||
// to time out.
|
||||
ASSERT_EQ(string_printf_result, str_format_result)
|
||||
<< fmt_str << " " << StrPrint("%.18g", d) << " "
|
||||
<< StrPrint("%a", d) << " " << StrPrint("%.1080f", d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(FormatConvertTest, LongDouble) {
|
||||
const char *const kFormats[] = {"%", "%.3", "%8.5", "%9",
|
||||
"%.60", "%+", "% ", "%-10"};
|
||||
TEST_F(FormatConvertTest, FloatRound) {
|
||||
std::string s;
|
||||
const auto format = [&](const char *fmt, double d) -> std::string & {
|
||||
s.clear();
|
||||
FormatArgImpl args[1] = {FormatArgImpl(d)};
|
||||
AppendPack(&s, UntypedFormatSpecImpl(fmt), absl::MakeSpan(args));
|
||||
#if !defined(_MSC_VER)
|
||||
// MSVC has a different rounding policy than us so we can't test our
|
||||
// implementation against the native one there.
|
||||
EXPECT_EQ(StrPrint(fmt, d), s);
|
||||
#endif // _MSC_VER
|
||||
|
||||
// This value is not representable in double, but it is in long double that
|
||||
// uses the extended format.
|
||||
// This is to verify that we are not truncating the value mistakenly through a
|
||||
// double.
|
||||
long double very_precise = 10000000000000000.25L;
|
||||
return s;
|
||||
};
|
||||
// All of these values have to be exactly represented.
|
||||
// Otherwise we might not be testing what we think we are testing.
|
||||
|
||||
// These values can fit in a 64bit "fast" representation.
|
||||
const double exact_value = 0.00000000000005684341886080801486968994140625;
|
||||
assert(exact_value == std::pow(2, -44));
|
||||
// Round up at a 5xx.
|
||||
EXPECT_EQ(format("%.13f", exact_value), "0.0000000000001");
|
||||
// Round up at a >5
|
||||
EXPECT_EQ(format("%.14f", exact_value), "0.00000000000006");
|
||||
// Round down at a <5
|
||||
EXPECT_EQ(format("%.16f", exact_value), "0.0000000000000568");
|
||||
// Nine handling
|
||||
EXPECT_EQ(format("%.35f", exact_value),
|
||||
"0.00000000000005684341886080801486969");
|
||||
EXPECT_EQ(format("%.36f", exact_value),
|
||||
"0.000000000000056843418860808014869690");
|
||||
// Round down the last nine.
|
||||
EXPECT_EQ(format("%.37f", exact_value),
|
||||
"0.0000000000000568434188608080148696899");
|
||||
EXPECT_EQ(format("%.10f", 0.000003814697265625), "0.0000038147");
|
||||
// Round up the last nine
|
||||
EXPECT_EQ(format("%.11f", 0.000003814697265625), "0.00000381470");
|
||||
EXPECT_EQ(format("%.12f", 0.000003814697265625), "0.000003814697");
|
||||
|
||||
// Round to even (down)
|
||||
EXPECT_EQ(format("%.43f", exact_value),
|
||||
"0.0000000000000568434188608080148696899414062");
|
||||
// Exact
|
||||
EXPECT_EQ(format("%.44f", exact_value),
|
||||
"0.00000000000005684341886080801486968994140625");
|
||||
// Round to even (up), let make the last digits 75 instead of 25
|
||||
EXPECT_EQ(format("%.43f", exact_value + std::pow(2, -43)),
|
||||
"0.0000000000001705302565824240446090698242188");
|
||||
// Exact, just to check.
|
||||
EXPECT_EQ(format("%.44f", exact_value + std::pow(2, -43)),
|
||||
"0.00000000000017053025658242404460906982421875");
|
||||
|
||||
// This value has to be small enough that it won't fit in the uint128
|
||||
// representation for printing.
|
||||
const double small_exact_value =
|
||||
0.000000000000000000000000000000000000752316384526264005099991383822237233803945956334136013765601092018187046051025390625; // NOLINT
|
||||
assert(small_exact_value == std::pow(2, -120));
|
||||
// Round up at a 5xx.
|
||||
EXPECT_EQ(format("%.37f", small_exact_value),
|
||||
"0.0000000000000000000000000000000000008");
|
||||
// Round down at a <5
|
||||
EXPECT_EQ(format("%.38f", small_exact_value),
|
||||
"0.00000000000000000000000000000000000075");
|
||||
// Round up at a >5
|
||||
EXPECT_EQ(format("%.41f", small_exact_value),
|
||||
"0.00000000000000000000000000000000000075232");
|
||||
// Nine handling
|
||||
EXPECT_EQ(format("%.55f", small_exact_value),
|
||||
"0.0000000000000000000000000000000000007523163845262640051");
|
||||
EXPECT_EQ(format("%.56f", small_exact_value),
|
||||
"0.00000000000000000000000000000000000075231638452626400510");
|
||||
EXPECT_EQ(format("%.57f", small_exact_value),
|
||||
"0.000000000000000000000000000000000000752316384526264005100");
|
||||
EXPECT_EQ(format("%.58f", small_exact_value),
|
||||
"0.0000000000000000000000000000000000007523163845262640051000");
|
||||
// Round down the last nine
|
||||
EXPECT_EQ(format("%.59f", small_exact_value),
|
||||
"0.00000000000000000000000000000000000075231638452626400509999");
|
||||
// Round up the last nine
|
||||
EXPECT_EQ(format("%.79f", small_exact_value),
|
||||
"0.000000000000000000000000000000000000"
|
||||
"7523163845262640050999913838222372338039460");
|
||||
|
||||
// Round to even (down)
|
||||
EXPECT_EQ(format("%.119f", small_exact_value),
|
||||
"0.000000000000000000000000000000000000"
|
||||
"75231638452626400509999138382223723380"
|
||||
"394595633413601376560109201818704605102539062");
|
||||
// Exact
|
||||
EXPECT_EQ(format("%.120f", small_exact_value),
|
||||
"0.000000000000000000000000000000000000"
|
||||
"75231638452626400509999138382223723380"
|
||||
"3945956334136013765601092018187046051025390625");
|
||||
// Round to even (up), let make the last digits 75 instead of 25
|
||||
EXPECT_EQ(format("%.119f", small_exact_value + std::pow(2, -119)),
|
||||
"0.000000000000000000000000000000000002"
|
||||
"25694915357879201529997415146671170141"
|
||||
"183786900240804129680327605456113815307617188");
|
||||
// Exact, just to check.
|
||||
EXPECT_EQ(format("%.120f", small_exact_value + std::pow(2, -119)),
|
||||
"0.000000000000000000000000000000000002"
|
||||
"25694915357879201529997415146671170141"
|
||||
"1837869002408041296803276054561138153076171875");
|
||||
}
|
||||
|
||||
TEST_F(FormatConvertTest, 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 = {
|
||||
0.0,
|
||||
-0.0,
|
||||
very_precise,
|
||||
1 / very_precise,
|
||||
std::numeric_limits<long double>::max(),
|
||||
-std::numeric_limits<long double>::max(),
|
||||
std::numeric_limits<long double>::min(),
|
||||
|
@ -556,22 +723,44 @@ TEST_F(FormatConvertTest, LongDouble) {
|
|||
std::numeric_limits<long double>::infinity(),
|
||||
-std::numeric_limits<long double>::infinity()};
|
||||
|
||||
for (long double base : {1.L, 12.L, 123.L, 1234.L, 12345.L, 123456.L,
|
||||
1234567.L, 12345678.L, 123456789.L, 1234567890.L,
|
||||
12345678901.L, 123456789012.L, 1234567890123.L,
|
||||
// This value is not representable in double, but it
|
||||
// is in long double that uses the extended format.
|
||||
// This is to verify that we are not truncating the
|
||||
// value mistakenly through a double.
|
||||
10000000000000000.25L}) {
|
||||
for (int exp : {-1000, -500, 0, 500, 1000}) {
|
||||
for (int sign : {1, -1}) {
|
||||
doubles.push_back(sign * std::ldexp(base, exp));
|
||||
doubles.push_back(sign / std::ldexp(base, exp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const char *fmt : kFormats) {
|
||||
for (char f : {'f', 'F', //
|
||||
'g', 'G', //
|
||||
'a', 'A', //
|
||||
'e', 'E'}) {
|
||||
std::string fmt_str = std::string(fmt) + 'L' + f;
|
||||
|
||||
if (fmt == absl::string_view("%.5000") && f != 'f' && f != 'F') {
|
||||
// This particular test takes way too long with snprintf.
|
||||
// Disable for the case we are not implementing natively.
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto d : doubles) {
|
||||
FormatArgImpl arg(d);
|
||||
UntypedFormatSpecImpl format(fmt_str);
|
||||
// We use ASSERT_EQ here because failures are usually correlated and a
|
||||
// bug would print way too many failed expectations causing the test to
|
||||
// time out.
|
||||
ASSERT_EQ(StrPrint(fmt_str.c_str(), d),
|
||||
FormatPack(format, {&arg, 1}))
|
||||
ASSERT_EQ(StrPrint(fmt_str.c_str(), d), FormatPack(format, {&arg, 1}))
|
||||
<< fmt_str << " " << StrPrint("%.18Lg", d) << " "
|
||||
<< StrPrint("%.999Lf", d);
|
||||
<< StrPrint("%La", d) << " " << StrPrint("%.1080Lf", d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -411,11 +411,6 @@ inline size_t Excess(size_t used, size_t capacity) {
|
|||
return used < capacity ? capacity - used : 0;
|
||||
}
|
||||
|
||||
// Type alias for use during migration.
|
||||
using ConversionChar = FormatConversionChar;
|
||||
using ConversionSpec = FormatConversionSpecImpl;
|
||||
using Conv = FormatConversionCharSet;
|
||||
|
||||
class FormatConversionSpec {
|
||||
public:
|
||||
// Width and precison are not specified, no flags are set.
|
||||
|
|
|
@ -1,12 +1,22 @@
|
|||
#include "absl/strings/internal/str_format/float_conversion.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
#include "absl/base/attributes.h"
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/internal/bits.h"
|
||||
#include "absl/base/optimization.h"
|
||||
#include "absl/functional/function_ref.h"
|
||||
#include "absl/meta/type_traits.h"
|
||||
#include "absl/numeric/int128.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "absl/types/span.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
|
@ -14,13 +24,640 @@ namespace str_format_internal {
|
|||
|
||||
namespace {
|
||||
|
||||
char *CopyStringTo(string_view v, char *out) {
|
||||
// The code below wants to avoid heap allocations.
|
||||
// To do so it needs to allocate memory on the stack.
|
||||
// `StackArray` will allocate memory on the stack in the form of a uint32_t
|
||||
// array and call the provided callback with said memory.
|
||||
// It will allocate memory in increments of 512 bytes. We could allocate the
|
||||
// largest needed unconditionally, but that is more than we need in most of
|
||||
// cases. This way we use less stack in the common cases.
|
||||
class StackArray {
|
||||
using Func = absl::FunctionRef<void(absl::Span<uint32_t>)>;
|
||||
static constexpr size_t kStep = 512 / sizeof(uint32_t);
|
||||
// 5 steps is 2560 bytes, which is enough to hold a long double with the
|
||||
// largest/smallest exponents.
|
||||
// The operations below will static_assert their particular maximum.
|
||||
static constexpr size_t kNumSteps = 5;
|
||||
|
||||
// We do not want this function to be inlined.
|
||||
// Otherwise the caller will allocate the stack space unnecessarily for all
|
||||
// the variants even though it only calls one.
|
||||
template <size_t steps>
|
||||
ABSL_ATTRIBUTE_NOINLINE static void RunWithCapacityImpl(Func f) {
|
||||
uint32_t values[steps * kStep]{};
|
||||
f(absl::MakeSpan(values));
|
||||
}
|
||||
|
||||
public:
|
||||
static constexpr size_t kMaxCapacity = kStep * kNumSteps;
|
||||
|
||||
static void RunWithCapacity(size_t capacity, Func f) {
|
||||
assert(capacity <= kMaxCapacity);
|
||||
const size_t step = (capacity + kStep - 1) / kStep;
|
||||
assert(step <= kNumSteps);
|
||||
switch (step) {
|
||||
case 1:
|
||||
return RunWithCapacityImpl<1>(f);
|
||||
case 2:
|
||||
return RunWithCapacityImpl<2>(f);
|
||||
case 3:
|
||||
return RunWithCapacityImpl<3>(f);
|
||||
case 4:
|
||||
return RunWithCapacityImpl<4>(f);
|
||||
case 5:
|
||||
return RunWithCapacityImpl<5>(f);
|
||||
}
|
||||
|
||||
assert(false && "Invalid capacity");
|
||||
}
|
||||
};
|
||||
|
||||
// Calculates `10 * (*v) + carry` and stores the result in `*v` and returns
|
||||
// the carry.
|
||||
template <typename Int>
|
||||
inline Int MultiplyBy10WithCarry(Int *v, Int carry) {
|
||||
using BiggerInt = absl::conditional_t<sizeof(Int) == 4, uint64_t, uint128>;
|
||||
BiggerInt tmp = 10 * static_cast<BiggerInt>(*v) + carry;
|
||||
*v = static_cast<Int>(tmp);
|
||||
return static_cast<Int>(tmp >> (sizeof(Int) * 8));
|
||||
}
|
||||
|
||||
// Calculates `(2^64 * carry + *v) / 10`.
|
||||
// Stores the quotient in `*v` and returns the remainder.
|
||||
// Requires: `0 <= carry <= 9`
|
||||
inline uint64_t DivideBy10WithCarry(uint64_t *v, uint64_t carry) {
|
||||
constexpr uint64_t divisor = 10;
|
||||
// 2^64 / divisor = chunk_quotient + chunk_remainder / divisor
|
||||
constexpr uint64_t chunk_quotient = (uint64_t{1} << 63) / (divisor / 2);
|
||||
constexpr uint64_t chunk_remainder = uint64_t{} - chunk_quotient * divisor;
|
||||
|
||||
const uint64_t mod = *v % divisor;
|
||||
const uint64_t next_carry = chunk_remainder * carry + mod;
|
||||
*v = *v / divisor + carry * chunk_quotient + next_carry / divisor;
|
||||
return next_carry % divisor;
|
||||
}
|
||||
|
||||
// Generates the decimal representation for an integer of the form `v * 2^exp`,
|
||||
// where `v` and `exp` are both positive integers.
|
||||
// It generates the digits from the left (ie the most significant digit first)
|
||||
// to allow for direct printing into the sink.
|
||||
//
|
||||
// Requires `0 <= exp` and `exp <= numeric_limits<long double>::max_exponent`.
|
||||
class BinaryToDecimal {
|
||||
static constexpr int ChunksNeeded(int exp) {
|
||||
// We will left shift a uint128 by `exp` bits, so we need `128+exp` total
|
||||
// bits. Round up to 32.
|
||||
// See constructor for details about adding `10%` to the value.
|
||||
return (128 + exp + 31) / 32 * 11 / 10;
|
||||
}
|
||||
|
||||
public:
|
||||
// Run the conversion for `v * 2^exp` and call `f(binary_to_decimal)`.
|
||||
// This function will allocate enough stack space to perform the conversion.
|
||||
static void RunConversion(uint128 v, int exp,
|
||||
absl::FunctionRef<void(BinaryToDecimal)> f) {
|
||||
assert(exp > 0);
|
||||
assert(exp <= std::numeric_limits<long double>::max_exponent);
|
||||
static_assert(
|
||||
StackArray::kMaxCapacity >=
|
||||
ChunksNeeded(std::numeric_limits<long double>::max_exponent),
|
||||
"");
|
||||
|
||||
StackArray::RunWithCapacity(
|
||||
ChunksNeeded(exp),
|
||||
[=](absl::Span<uint32_t> input) { f(BinaryToDecimal(input, v, exp)); });
|
||||
}
|
||||
|
||||
int TotalDigits() const {
|
||||
return static_cast<int>((decimal_end_ - decimal_start_) * kDigitsPerChunk +
|
||||
CurrentDigits().size());
|
||||
}
|
||||
|
||||
// See the current block of digits.
|
||||
absl::string_view CurrentDigits() const {
|
||||
return absl::string_view(digits_ + kDigitsPerChunk - size_, size_);
|
||||
}
|
||||
|
||||
// Advance the current view of digits.
|
||||
// Returns `false` when no more digits are available.
|
||||
bool AdvanceDigits() {
|
||||
if (decimal_start_ >= decimal_end_) return false;
|
||||
|
||||
uint32_t w = data_[decimal_start_++];
|
||||
for (size_ = 0; size_ < kDigitsPerChunk; w /= 10) {
|
||||
digits_[kDigitsPerChunk - ++size_] = w % 10 + '0';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
BinaryToDecimal(absl::Span<uint32_t> data, uint128 v, int exp) : data_(data) {
|
||||
// We need to print the digits directly into the sink object without
|
||||
// buffering them all first. To do this we need two things:
|
||||
// - to know the total number of digits to do padding when necessary
|
||||
// - to generate the decimal digits from the left.
|
||||
//
|
||||
// In order to do this, we do a two pass conversion.
|
||||
// On the first pass we convert the binary representation of the value into
|
||||
// a decimal representation in which each uint32_t chunk holds up to 9
|
||||
// decimal digits. In the second pass we take each decimal-holding-uint32_t
|
||||
// value and generate the ascii decimal digits into `digits_`.
|
||||
//
|
||||
// The binary and decimal representations actually share the same memory
|
||||
// region. As we go converting the chunks from binary to decimal we free
|
||||
// them up and reuse them for the decimal representation. One caveat is that
|
||||
// the decimal representation is around 7% less efficient in space than the
|
||||
// binary one. We allocate an extra 10% memory to account for this. See
|
||||
// ChunksNeeded for this calculation.
|
||||
int chunk_index = exp / 32;
|
||||
decimal_start_ = decimal_end_ = ChunksNeeded(exp);
|
||||
const int offset = exp % 32;
|
||||
// Left shift v by exp bits.
|
||||
data_[chunk_index] = static_cast<uint32_t>(v << offset);
|
||||
for (v >>= (32 - offset); v; v >>= 32)
|
||||
data_[++chunk_index] = static_cast<uint32_t>(v);
|
||||
|
||||
while (chunk_index >= 0) {
|
||||
// While we have more than one chunk available, go in steps of 1e9.
|
||||
// `data_[chunk_index]` holds the highest non-zero binary chunk, so keep
|
||||
// the variable updated.
|
||||
uint32_t carry = 0;
|
||||
for (int i = chunk_index; i >= 0; --i) {
|
||||
uint64_t tmp = uint64_t{data_[i]} + (uint64_t{carry} << 32);
|
||||
data_[i] = static_cast<uint32_t>(tmp / uint64_t{1000000000});
|
||||
carry = static_cast<uint32_t>(tmp % uint64_t{1000000000});
|
||||
}
|
||||
|
||||
// If the highest chunk is now empty, remove it from view.
|
||||
if (data_[chunk_index] == 0) --chunk_index;
|
||||
|
||||
--decimal_start_;
|
||||
assert(decimal_start_ != chunk_index);
|
||||
data_[decimal_start_] = carry;
|
||||
}
|
||||
|
||||
// Fill the first set of digits. The first chunk might not be complete, so
|
||||
// handle differently.
|
||||
for (uint32_t first = data_[decimal_start_++]; first != 0; first /= 10) {
|
||||
digits_[kDigitsPerChunk - ++size_] = first % 10 + '0';
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr 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());
|
||||
return out + v.size();
|
||||
}
|
||||
|
||||
template <typename Float>
|
||||
bool FallbackToSnprintf(const Float v, const ConversionSpec &conv,
|
||||
bool FallbackToSnprintf(const Float v, const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink) {
|
||||
int w = conv.width() >= 0 ? conv.width() : 0;
|
||||
int p = conv.precision() >= 0 ? conv.precision() : -1;
|
||||
|
@ -38,12 +675,12 @@ bool FallbackToSnprintf(const Float v, const ConversionSpec &conv,
|
|||
assert(fp < fmt + sizeof(fmt));
|
||||
}
|
||||
std::string space(512, '\0');
|
||||
string_view result;
|
||||
absl::string_view result;
|
||||
while (true) {
|
||||
int n = snprintf(&space[0], space.size(), fmt, w, p, v);
|
||||
if (n < 0) return false;
|
||||
if (static_cast<size_t>(n) < space.size()) {
|
||||
result = string_view(space.data(), n);
|
||||
result = absl::string_view(space.data(), n);
|
||||
break;
|
||||
}
|
||||
space.resize(n + 1);
|
||||
|
@ -96,9 +733,10 @@ enum class FormatStyle { Fixed, Precision };
|
|||
// Otherwise, return false.
|
||||
template <typename Float>
|
||||
bool ConvertNonNumericFloats(char sign_char, Float v,
|
||||
const ConversionSpec &conv, FormatSinkImpl *sink) {
|
||||
const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink) {
|
||||
char text[4], *ptr = text;
|
||||
if (sign_char) *ptr++ = sign_char;
|
||||
if (sign_char != '\0') *ptr++ = sign_char;
|
||||
if (std::isnan(v)) {
|
||||
ptr = std::copy_n(
|
||||
FormatConversionCharIsUpper(conv.conversion_char()) ? "NAN" : "nan", 3,
|
||||
|
@ -172,7 +810,12 @@ constexpr bool CanFitMantissa() {
|
|||
|
||||
template <typename Float>
|
||||
struct Decomposed {
|
||||
Float mantissa;
|
||||
using MantissaType =
|
||||
absl::conditional_t<std::is_same<long double, Float>::value, uint128,
|
||||
uint64_t>;
|
||||
static_assert(std::numeric_limits<Float>::digits <= sizeof(MantissaType) * 8,
|
||||
"");
|
||||
MantissaType mantissa;
|
||||
int exponent;
|
||||
};
|
||||
|
||||
|
@ -183,7 +826,8 @@ Decomposed<Float> Decompose(Float v) {
|
|||
Float m = std::frexp(v, &exp);
|
||||
m = std::ldexp(m, std::numeric_limits<Float>::digits);
|
||||
exp -= std::numeric_limits<Float>::digits;
|
||||
return {m, exp};
|
||||
|
||||
return {static_cast<typename Decomposed<Float>::MantissaType>(m), exp};
|
||||
}
|
||||
|
||||
// Print 'digits' as decimal.
|
||||
|
@ -352,8 +996,9 @@ bool FloatToBuffer(Decomposed<Float> decomposed, int precision, Buffer *out,
|
|||
return false;
|
||||
}
|
||||
|
||||
void WriteBufferToSink(char sign_char, string_view str,
|
||||
const ConversionSpec &conv, FormatSinkImpl *sink) {
|
||||
void WriteBufferToSink(char sign_char, absl::string_view str,
|
||||
const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink) {
|
||||
int left_spaces = 0, zeros = 0, right_spaces = 0;
|
||||
int missing_chars =
|
||||
conv.width() >= 0 ? std::max(conv.width() - static_cast<int>(str.size()) -
|
||||
|
@ -369,14 +1014,14 @@ void WriteBufferToSink(char sign_char, string_view str,
|
|||
}
|
||||
|
||||
sink->Append(left_spaces, ' ');
|
||||
if (sign_char) sink->Append(1, sign_char);
|
||||
if (sign_char != '\0') sink->Append(1, sign_char);
|
||||
sink->Append(zeros, '0');
|
||||
sink->Append(str);
|
||||
sink->Append(right_spaces, ' ');
|
||||
}
|
||||
|
||||
template <typename Float>
|
||||
bool FloatToSink(const Float v, const ConversionSpec &conv,
|
||||
bool FloatToSink(const Float v, const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink) {
|
||||
// Print the sign or the sign column.
|
||||
Float abs_v = v;
|
||||
|
@ -407,11 +1052,9 @@ bool FloatToSink(const Float v, const ConversionSpec &conv,
|
|||
|
||||
if (c == FormatConversionCharInternal::f ||
|
||||
c == FormatConversionCharInternal::F) {
|
||||
if (!FloatToBuffer<FormatStyle::Fixed>(decomposed, precision, &buffer,
|
||||
nullptr)) {
|
||||
return FallbackToSnprintf(v, conv, sink);
|
||||
}
|
||||
if (!conv.has_alt_flag() && buffer.back() == '.') buffer.pop_back();
|
||||
FormatF(decomposed.mantissa, decomposed.exponent,
|
||||
{sign_char, precision, conv, sink});
|
||||
return true;
|
||||
} else if (c == FormatConversionCharInternal::e ||
|
||||
c == FormatConversionCharInternal::E) {
|
||||
if (!FloatToBuffer<FormatStyle::Precision>(decomposed, precision, &buffer,
|
||||
|
@ -462,25 +1105,32 @@ bool FloatToSink(const Float v, const ConversionSpec &conv,
|
|||
}
|
||||
|
||||
WriteBufferToSink(sign_char,
|
||||
string_view(buffer.begin, buffer.end - buffer.begin), conv,
|
||||
sink);
|
||||
absl::string_view(buffer.begin, buffer.end - buffer.begin),
|
||||
conv, sink);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ConvertFloatImpl(long double v, const ConversionSpec &conv,
|
||||
bool ConvertFloatImpl(long double v, const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink) {
|
||||
if (std::numeric_limits<long double>::digits ==
|
||||
2 * std::numeric_limits<double>::digits) {
|
||||
// This is the `double-double` representation of `long double`.
|
||||
// We do not handle it natively. Fallback to snprintf.
|
||||
return FallbackToSnprintf(v, conv, sink);
|
||||
}
|
||||
|
||||
return FloatToSink(v, conv, sink);
|
||||
}
|
||||
|
||||
bool ConvertFloatImpl(float v, const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return FloatToSink(v, conv, sink);
|
||||
}
|
||||
|
||||
bool ConvertFloatImpl(float v, const ConversionSpec &conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return FloatToSink(v, conv, sink);
|
||||
}
|
||||
|
||||
bool ConvertFloatImpl(double v, const ConversionSpec &conv,
|
||||
bool ConvertFloatImpl(double v, const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return FloatToSink(v, conv, sink);
|
||||
}
|
||||
|
|
|
@ -7,13 +7,13 @@ namespace absl {
|
|||
ABSL_NAMESPACE_BEGIN
|
||||
namespace str_format_internal {
|
||||
|
||||
bool ConvertFloatImpl(float v, const ConversionSpec &conv,
|
||||
bool ConvertFloatImpl(float v, const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink);
|
||||
|
||||
bool ConvertFloatImpl(double v, const ConversionSpec &conv,
|
||||
bool ConvertFloatImpl(double v, const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink);
|
||||
|
||||
bool ConvertFloatImpl(long double v, const ConversionSpec &conv,
|
||||
bool ConvertFloatImpl(long double v, const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink);
|
||||
|
||||
} // namespace str_format_internal
|
||||
|
|
|
@ -83,7 +83,7 @@ const char* ConsumeUnboundConversion(const char* p, const char* end,
|
|||
// conversions.
|
||||
class ConvTag {
|
||||
public:
|
||||
constexpr ConvTag(ConversionChar conversion_char) // NOLINT
|
||||
constexpr ConvTag(FormatConversionChar conversion_char) // NOLINT
|
||||
: tag_(static_cast<int8_t>(conversion_char)) {}
|
||||
// We invert the length modifiers to make them negative so that we can easily
|
||||
// test for them.
|
||||
|
@ -94,9 +94,9 @@ class ConvTag {
|
|||
|
||||
bool is_conv() const { return tag_ >= 0; }
|
||||
bool is_length() const { return tag_ < 0 && tag_ != -128; }
|
||||
ConversionChar as_conv() const {
|
||||
FormatConversionChar as_conv() const {
|
||||
assert(is_conv());
|
||||
return static_cast<ConversionChar>(tag_);
|
||||
return static_cast<FormatConversionChar>(tag_);
|
||||
}
|
||||
LengthMod as_length() const {
|
||||
assert(is_length());
|
||||
|
@ -282,7 +282,7 @@ class ParsedFormatBase {
|
|||
// This is the only API that allows the user to pass a runtime specified format
|
||||
// string. These factory functions will return NULL if the format does not match
|
||||
// the conversions requested by the user.
|
||||
template <str_format_internal::Conv... C>
|
||||
template <FormatConversionCharSet... C>
|
||||
class ExtendedParsedFormat : public str_format_internal::ParsedFormatBase {
|
||||
public:
|
||||
explicit ExtendedParsedFormat(string_view format)
|
||||
|
|
|
@ -41,7 +41,7 @@ TEST(LengthModTest, Names) {
|
|||
|
||||
TEST(ConversionCharTest, Names) {
|
||||
struct Expectation {
|
||||
ConversionChar id;
|
||||
FormatConversionChar id;
|
||||
char name;
|
||||
};
|
||||
// clang-format off
|
||||
|
@ -57,7 +57,7 @@ TEST(ConversionCharTest, Names) {
|
|||
// clang-format on
|
||||
for (auto e : kExpect) {
|
||||
SCOPED_TRACE(e.name);
|
||||
ConversionChar v = e.id;
|
||||
FormatConversionChar v = e.id;
|
||||
EXPECT_EQ(e.name, FormatConversionCharToChar(v));
|
||||
}
|
||||
}
|
||||
|
@ -368,7 +368,7 @@ TEST_F(ParsedFormatTest, ValueSemantics) {
|
|||
|
||||
struct ExpectParse {
|
||||
const char* in;
|
||||
std::initializer_list<Conv> conv_set;
|
||||
std::initializer_list<FormatConversionCharSet> conv_set;
|
||||
const char* out;
|
||||
};
|
||||
|
||||
|
|
|
@ -532,76 +532,103 @@ TEST_F(ParsedFormatTest, SimpleUncheckedIncorrect) {
|
|||
EXPECT_FALSE((ParsedFormat<'s', 'd', 'g'>::New(format)));
|
||||
}
|
||||
|
||||
using str_format_internal::Conv;
|
||||
using absl::str_format_internal::FormatConversionCharSet;
|
||||
|
||||
TEST_F(ParsedFormatTest, UncheckedCorrect) {
|
||||
auto f = ExtendedParsedFormat<Conv::d>::New("ABC%dDEF");
|
||||
auto f = ExtendedParsedFormat<FormatConversionCharSet::d>::New("ABC%dDEF");
|
||||
ASSERT_TRUE(f);
|
||||
EXPECT_EQ("[ABC]{d:1$d}[DEF]", SummarizeParsedFormat(*f));
|
||||
|
||||
std::string format = "%sFFF%dZZZ%f";
|
||||
auto f2 = ExtendedParsedFormat<Conv::kString, Conv::d, Conv::kFloating>::New(
|
||||
format);
|
||||
auto f2 =
|
||||
ExtendedParsedFormat<FormatConversionCharSet::kString,
|
||||
FormatConversionCharSet::d,
|
||||
FormatConversionCharSet::kFloating>::New(format);
|
||||
|
||||
ASSERT_TRUE(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(
|
||||
"%s %d %f");
|
||||
f2 =
|
||||
ExtendedParsedFormat<FormatConversionCharSet::kString,
|
||||
FormatConversionCharSet::d,
|
||||
FormatConversionCharSet::kFloating>::New("%s %d %f");
|
||||
|
||||
ASSERT_TRUE(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);
|
||||
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);
|
||||
EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}", SummarizeParsedFormat(*dollar));
|
||||
// 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);
|
||||
EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}[ ]{1$d:1$d}",
|
||||
SummarizeParsedFormat(*dollar));
|
||||
}
|
||||
|
||||
TEST_F(ParsedFormatTest, UncheckedIgnoredArgs) {
|
||||
EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::s>::New("ABC")));
|
||||
EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::s>::New("%dABC")));
|
||||
EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::s>::New("ABC%2$s")));
|
||||
auto f = ExtendedParsedFormat<Conv::d, Conv::s>::NewAllowIgnored("ABC");
|
||||
EXPECT_FALSE((ExtendedParsedFormat<FormatConversionCharSet::d,
|
||||
FormatConversionCharSet::s>::New("ABC")));
|
||||
EXPECT_FALSE(
|
||||
(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);
|
||||
EXPECT_EQ("[ABC]", SummarizeParsedFormat(*f));
|
||||
f = ExtendedParsedFormat<Conv::d, Conv::s>::NewAllowIgnored("%dABC");
|
||||
f = ExtendedParsedFormat<
|
||||
FormatConversionCharSet::d,
|
||||
FormatConversionCharSet::s>::NewAllowIgnored("%dABC");
|
||||
ASSERT_TRUE(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);
|
||||
EXPECT_EQ("[ABC]{2$s:2$s}", SummarizeParsedFormat(*f));
|
||||
}
|
||||
|
||||
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_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_EQ("{1$d:1$d}", SummarizeParsedFormat(*dx));
|
||||
}
|
||||
|
||||
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";
|
||||
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) {
|
||||
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;
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
//
|
||||
// Supported types:
|
||||
// * 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
|
||||
// * bool (Printed as "true" or "false")
|
||||
// * pointer types other than char* (Printed as "0x<lower case hex string>",
|
||||
|
|
Loading…
Reference in a new issue