Add 128-bit operations to bigint

This commit is contained in:
Victor Zverovich
2022-04-02 07:40:52 -07:00
parent ef54f9aa38
commit b4dc7a1d34
3 changed files with 113 additions and 83 deletions

View File

@ -368,23 +368,36 @@ class bigint {
if (carry != 0) bigits_.push_back(carry); if (carry != 0) bigits_.push_back(carry);
} }
FMT_CONSTEXPR20 void multiply(uint64_t value) { template <typename UInt, FMT_ENABLE_IF(std::is_same<UInt, uint64_t>::value ||
const bigit mask = ~bigit(0); std::is_same<UInt, uint128_t>::value)>
const double_bigit lower = value & mask; FMT_CONSTEXPR20 void multiply(UInt value) {
const double_bigit upper = value >> bigit_bits; const UInt lower = static_cast<bigit>(value);
double_bigit carry = 0; const UInt upper = value >> bigit_bits;
UInt carry = 0;
for (size_t i = 0, n = bigits_.size(); i < n; ++i) { for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
double_bigit result = bigits_[i] * lower + (carry & mask); UInt result = lower * bigits_[i] + static_cast<bigit>(carry);
carry = carry =
bigits_[i] * upper + (result >> bigit_bits) + (carry >> bigit_bits); upper * bigits_[i] + (result >> bigit_bits) + (carry >> bigit_bits);
bigits_[i] = static_cast<bigit>(result); bigits_[i] = static_cast<bigit>(result);
} }
while (carry != 0) { while (carry != 0) {
bigits_.push_back(carry & mask); bigits_.push_back(static_cast<bigit>(carry));
carry >>= bigit_bits; carry >>= bigit_bits;
} }
} }
template <typename UInt, FMT_ENABLE_IF(std::is_same<UInt, uint64_t>::value ||
std::is_same<UInt, uint128_t>::value)>
FMT_CONSTEXPR20 void assign(UInt n) {
size_t num_bigits = 0;
do {
bigits_[num_bigits++] = static_cast<bigit>(n);
n >>= bigit_bits;
} while (n != 0);
bigits_.resize(num_bigits);
exp_ = 0;
}
public: public:
FMT_CONSTEXPR20 bigint() : exp_(0) {} FMT_CONSTEXPR20 bigint() : exp_(0) {}
explicit bigint(uint64_t n) { assign(n); } explicit bigint(uint64_t n) { assign(n); }
@ -400,14 +413,9 @@ class bigint {
exp_ = other.exp_; exp_ = other.exp_;
} }
FMT_CONSTEXPR20 void assign(uint64_t n) { template <typename Int> FMT_CONSTEXPR20 void operator=(Int n) {
size_t num_bigits = 0; FMT_ASSERT(n > 0, "");
do { assign(uint64_or_128_t<Int>(n));
bigits_[num_bigits++] = n & ~bigit(0);
n >>= bigit_bits;
} while (n != 0);
bigits_.resize(num_bigits);
exp_ = 0;
} }
FMT_CONSTEXPR20 int num_bigits() const { FMT_CONSTEXPR20 int num_bigits() const {
@ -478,14 +486,14 @@ class bigint {
// Assigns pow(10, exp) to this bigint. // Assigns pow(10, exp) to this bigint.
FMT_CONSTEXPR20 void assign_pow10(int exp) { FMT_CONSTEXPR20 void assign_pow10(int exp) {
FMT_ASSERT(exp >= 0, ""); FMT_ASSERT(exp >= 0, "");
if (exp == 0) return assign(1); if (exp == 0) return *this = 1;
// Find the top bit. // Find the top bit.
int bitmask = 1; int bitmask = 1;
while (exp >= bitmask) bitmask <<= 1; while (exp >= bitmask) bitmask <<= 1;
bitmask >>= 1; bitmask >>= 1;
// pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by
// repeated squaring and multiplication. // repeated squaring and multiplication.
assign(5); *this = 5;
bitmask >>= 1; bitmask >>= 1;
while (bitmask != 0) { while (bitmask != 0) {
square(); square();
@ -2061,12 +2069,12 @@ FMT_CONSTEXPR20 inline void format_dragon(fp value, unsigned flags,
bool is_predecessor_closer = (flags & dragon::predecessor_closer) != 0; bool is_predecessor_closer = (flags & dragon::predecessor_closer) != 0;
int shift = is_predecessor_closer ? 2 : 1; int shift = is_predecessor_closer ? 2 : 1;
if (value.e >= 0) { if (value.e >= 0) {
numerator.assign(value.f); numerator = value.f;
numerator <<= value.e + shift; numerator <<= value.e + shift;
lower.assign(1); lower = 1;
lower <<= value.e; lower <<= value.e;
if (is_predecessor_closer) { if (is_predecessor_closer) {
upper_store.assign(1); upper_store = 1;
upper_store <<= value.e + 1; upper_store <<= value.e + 1;
upper = &upper_store; upper = &upper_store;
} }
@ -2082,16 +2090,16 @@ FMT_CONSTEXPR20 inline void format_dragon(fp value, unsigned flags,
} }
numerator *= value.f; numerator *= value.f;
numerator <<= shift; numerator <<= shift;
denominator.assign(1); denominator = 1;
denominator <<= shift - value.e; denominator <<= shift - value.e;
} else { } else {
numerator.assign(value.f); numerator = value.f;
numerator <<= shift; numerator <<= shift;
denominator.assign_pow10(exp10); denominator.assign_pow10(exp10);
denominator <<= shift - value.e; denominator <<= shift - value.e;
lower.assign(1); lower = 1;
if (is_predecessor_closer) { if (is_predecessor_closer) {
upper_store.assign(1ULL << 1); upper_store = 1ULL << 1;
upper = &upper_store; upper = &upper_store;
} }
} }
@ -2278,13 +2286,14 @@ FMT_HEADER_ONLY_CONSTEXPR20 int format_float(Float value, int precision,
} // namespace detail } // namespace detail
template <> struct formatter<detail::bigint> { template <> struct formatter<detail::bigint> {
FMT_CONSTEXPR format_parse_context::iterator parse( FMT_CONSTEXPR auto parse(format_parse_context& ctx)
format_parse_context& ctx) { -> format_parse_context::iterator {
return ctx.begin(); return ctx.begin();
} }
format_context::iterator format(const detail::bigint& n, template <typename FormatContext>
format_context& ctx) { auto format(const detail::bigint& n, FormatContext& ctx) const ->
typename FormatContext::iterator {
auto out = ctx.out(); auto out = ctx.out();
bool first = true; bool first = true;
for (auto i = n.bigits_.size(); i > 0; --i) { for (auto i = n.bigits_.size(); i > 0; --i) {

View File

@ -339,6 +339,10 @@ class uint128_fallback {
const uint128_fallback& rhs) -> bool { const uint128_fallback& rhs) -> bool {
return !(lhs == rhs); return !(lhs == rhs);
} }
friend auto operator>(const uint128_fallback& lhs,
const uint128_fallback& rhs) -> bool {
return lhs.hi_ != rhs.hi_ ? lhs.hi_ > rhs.hi_ : lhs.lo_ > rhs.lo_;
}
friend auto operator|(const uint128_fallback& lhs, friend auto operator|(const uint128_fallback& lhs,
const uint128_fallback& rhs) -> uint128_fallback { const uint128_fallback& rhs) -> uint128_fallback {
return {lhs.hi_ | rhs.hi_, lhs.lo_ | rhs.lo_}; return {lhs.hi_ | rhs.hi_, lhs.lo_ | rhs.lo_};
@ -347,6 +351,16 @@ class uint128_fallback {
const uint128_fallback& rhs) -> uint128_fallback { const uint128_fallback& rhs) -> uint128_fallback {
return {lhs.hi_ & rhs.hi_, lhs.lo_ & rhs.lo_}; return {lhs.hi_ & rhs.hi_, lhs.lo_ & rhs.lo_};
} }
friend auto operator+(const uint128_fallback& lhs,
const uint128_fallback& rhs) -> uint128_fallback {
auto result = uint128_fallback(lhs);
result += rhs;
return result;
}
friend auto operator*(const uint128_fallback&, uint32_t) -> uint128_fallback {
FMT_ASSERT(false, "");
return {};
}
friend auto operator-(const uint128_fallback& lhs, uint64_t rhs) friend auto operator-(const uint128_fallback& lhs, uint64_t rhs)
-> uint128_fallback { -> uint128_fallback {
FMT_ASSERT(lhs.lo_ >= rhs, ""); FMT_ASSERT(lhs.lo_ >= rhs, "");
@ -973,10 +987,9 @@ template <typename T>
using uint32_or_64_or_128_t = using uint32_or_64_or_128_t =
conditional_t<num_bits<T>() <= 32 && !FMT_REDUCE_INT_INSTANTIATIONS, conditional_t<num_bits<T>() <= 32 && !FMT_REDUCE_INT_INSTANTIATIONS,
uint32_t, uint32_t,
conditional_t<num_bits<T>() <= 64, uint64_t, uint128_opt>>; conditional_t<num_bits<T>() <= 64, uint64_t, uint128_t>>;
template <typename T> template <typename T>
using uint64_or_128_t = using uint64_or_128_t = conditional_t<num_bits<T>() <= 64, uint64_t, uint128_t>;
conditional_t<num_bits<T>() <= 64, uint64_t, uint128_opt>;
#define FMT_POWERS_OF_10(factor) \ #define FMT_POWERS_OF_10(factor) \
factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \ factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \

View File

@ -24,9 +24,9 @@ static_assert(!std::is_copy_constructible<bigint>::value, "");
static_assert(!std::is_copy_assignable<bigint>::value, ""); static_assert(!std::is_copy_assignable<bigint>::value, "");
TEST(bigint_test, construct) { TEST(bigint_test, construct) {
EXPECT_EQ("", fmt::format("{}", bigint())); EXPECT_EQ(fmt::to_string(bigint()), "");
EXPECT_EQ("42", fmt::format("{}", bigint(0x42))); EXPECT_EQ(fmt::to_string(bigint(0x42)), "42");
EXPECT_EQ("123456789abcedf0", fmt::format("{}", bigint(0x123456789abcedf0))); EXPECT_EQ(fmt::to_string(bigint(0x123456789abcedf0)), "123456789abcedf0");
} }
TEST(bigint_test, compare) { TEST(bigint_test, compare) {
@ -72,46 +72,56 @@ TEST(bigint_test, add_compare) {
TEST(bigint_test, shift_left) { TEST(bigint_test, shift_left) {
bigint n(0x42); bigint n(0x42);
n <<= 0; n <<= 0;
EXPECT_EQ("42", fmt::format("{}", n)); EXPECT_EQ(fmt::to_string(n), "42");
n <<= 1; n <<= 1;
EXPECT_EQ("84", fmt::format("{}", n)); EXPECT_EQ(fmt::to_string(n), "84");
n <<= 25; n <<= 25;
EXPECT_EQ("108000000", fmt::format("{}", n)); EXPECT_EQ(fmt::to_string(n), "108000000");
} }
TEST(bigint_test, multiply) { TEST(bigint_test, multiply) {
bigint n(0x42); bigint n(0x42);
EXPECT_THROW(n *= 0, assertion_failure); EXPECT_THROW(n *= 0, assertion_failure);
n *= 1; n *= 1;
EXPECT_EQ("42", fmt::format("{}", n)); EXPECT_EQ(fmt::to_string(n), "42");
n *= 2; n *= 2;
EXPECT_EQ("84", fmt::format("{}", n)); EXPECT_EQ(fmt::to_string(n), "84");
n *= 0x12345678; n *= 0x12345678;
EXPECT_EQ("962fc95e0", fmt::format("{}", n)); EXPECT_EQ(fmt::to_string(n), "962fc95e0");
bigint bigmax(max_value<uint32_t>()); bigint bigmax(max_value<uint32_t>());
bigmax *= max_value<uint32_t>(); bigmax *= max_value<uint32_t>();
EXPECT_EQ("fffffffe00000001", fmt::format("{}", bigmax)); EXPECT_EQ(fmt::to_string(bigmax), "fffffffe00000001");
bigmax.assign(max_value<uint64_t>());
bigmax *= max_value<uint64_t>(); const auto max64 = max_value<uint64_t>();
EXPECT_EQ("fffffffffffffffe0000000000000001", fmt::format("{}", bigmax)); bigmax = max64;
bigmax *= max64;
EXPECT_EQ(fmt::to_string(bigmax), "fffffffffffffffe0000000000000001");
const auto max128 = (fmt::detail::uint128_t(max64) << 64) | max64;
bigmax = max128;
// bigmax *= max128;
// EXPECT_EQ(fmt::to_string(bigmax),
// "fffffffffffffffffffffffffffffffe00000000000000000000000000000001");
} }
TEST(bigint_test, square) { TEST(bigint_test, square) {
bigint n0(0); bigint n0(0);
n0.square(); n0.square();
EXPECT_EQ("0", fmt::format("{}", n0)); EXPECT_EQ(fmt::to_string(n0), "0");
bigint n1(0x100); bigint n1(0x100);
n1.square(); n1.square();
EXPECT_EQ("10000", fmt::format("{}", n1)); EXPECT_EQ(fmt::to_string(n1), "10000");
bigint n2(0xfffffffff); bigint n2(0xfffffffff);
n2.square(); n2.square();
EXPECT_EQ("ffffffffe000000001", fmt::format("{}", n2)); EXPECT_EQ(fmt::to_string(n2), "ffffffffe000000001");
bigint n3(max_value<uint64_t>()); bigint n3(max_value<uint64_t>());
n3.square(); n3.square();
EXPECT_EQ("fffffffffffffffe0000000000000001", fmt::format("{}", n3)); EXPECT_EQ(fmt::to_string(n3), "fffffffffffffffe0000000000000001");
bigint n4; bigint n4;
n4.assign_pow10(10); n4.assign_pow10(10);
EXPECT_EQ("2540be400", fmt::format("{}", n4)); EXPECT_EQ(fmt::to_string(n4), "2540be400");
} }
TEST(bigint_test, divmod_assign_zero_divisor) { TEST(bigint_test, divmod_assign_zero_divisor) {
@ -133,8 +143,8 @@ TEST(bigint_test, divmod_assign_unaligned) {
n2.assign_pow10(100); n2.assign_pow10(100);
int result = n1.divmod_assign(n2); int result = n1.divmod_assign(n2);
EXPECT_EQ(result, 9406); EXPECT_EQ(result, 9406);
EXPECT_EQ("10f8353019583bfc29ffc8f564e1b9f9d819dbb4cf783e4507eca1539220p96", EXPECT_EQ(fmt::to_string(n1),
fmt::format("{}", n1)); "10f8353019583bfc29ffc8f564e1b9f9d819dbb4cf783e4507eca1539220p96");
} }
TEST(bigint_test, divmod_assign) { TEST(bigint_test, divmod_assign) {
@ -142,19 +152,19 @@ TEST(bigint_test, divmod_assign) {
bigint n1(100); bigint n1(100);
int result = n1.divmod_assign(bigint(10)); int result = n1.divmod_assign(bigint(10));
EXPECT_EQ(result, 10); EXPECT_EQ(result, 10);
EXPECT_EQ("0", fmt::format("{}", n1)); EXPECT_EQ(fmt::to_string(n1), "0");
// pow(10, 100) / (42 << 320): // pow(10, 100) / (42 << 320):
n1.assign_pow10(100); n1.assign_pow10(100);
result = n1.divmod_assign(bigint(42) <<= 320); result = n1.divmod_assign(bigint(42) <<= 320);
EXPECT_EQ(result, 111); EXPECT_EQ(result, 111);
EXPECT_EQ("13ad2594c37ceb0b2784c4ce0bf38ace408e211a7caab24308a82e8f10p96", EXPECT_EQ(fmt::to_string(n1),
fmt::format("{}", n1)); "13ad2594c37ceb0b2784c4ce0bf38ace408e211a7caab24308a82e8f10p96");
// 42 / 100: // 42 / 100:
bigint n2(42); bigint n2(42);
n1.assign_pow10(2); n1.assign_pow10(2);
result = n2.divmod_assign(n1); result = n2.divmod_assign(n1);
EXPECT_EQ(result, 0); EXPECT_EQ(result, 0);
EXPECT_EQ("2a", fmt::format("{}", n2)); EXPECT_EQ(fmt::to_string(n2), "2a");
} }
template <bool is_iec559> void run_double_tests() { template <bool is_iec559> void run_double_tests() {
@ -173,8 +183,8 @@ TEST(fp_test, double_tests) {
TEST(fp_test, normalize) { TEST(fp_test, normalize) {
const auto v = fp(0xbeef, 42); const auto v = fp(0xbeef, 42);
auto normalized = normalize(v); auto normalized = normalize(v);
EXPECT_EQ(0xbeef000000000000, normalized.f); EXPECT_EQ(normalized.f, 0xbeef000000000000);
EXPECT_EQ(-6, normalized.e); EXPECT_EQ(normalized.e, -6);
} }
TEST(fp_test, multiply) { TEST(fp_test, multiply) {
@ -200,8 +210,8 @@ TEST(fp_test, get_cached_power) {
cache <<= power.e; cache <<= power.e;
exact.align(cache); exact.align(cache);
cache.align(exact); cache.align(exact);
auto exact_str = fmt::format("{}", exact); auto exact_str = fmt::to_string(exact);
auto cache_str = fmt::format("{}", cache); auto cache_str = fmt::to_string(cache);
EXPECT_EQ(exact_str.size(), cache_str.size()); EXPECT_EQ(exact_str.size(), cache_str.size());
EXPECT_EQ(exact_str.substr(0, 15), cache_str.substr(0, 15)); EXPECT_EQ(exact_str.substr(0, 15), cache_str.substr(0, 15));
int diff = cache_str[15] - exact_str[15]; int diff = cache_str[15] - exact_str[15];
@ -212,11 +222,11 @@ TEST(fp_test, get_cached_power) {
} else { } else {
cache.assign_pow10(-dec_exp); cache.assign_pow10(-dec_exp);
cache *= power.f + 1; // Inexact check. cache *= power.f + 1; // Inexact check.
exact.assign(1); exact = 1;
exact <<= -power.e; exact <<= -power.e;
exact.align(cache); exact.align(cache);
auto exact_str = fmt::format("{}", exact); auto exact_str = fmt::to_string(exact);
auto cache_str = fmt::format("{}", cache); auto cache_str = fmt::to_string(cache);
EXPECT_EQ(exact_str.size(), cache_str.size()); EXPECT_EQ(exact_str.size(), cache_str.size());
EXPECT_EQ(exact_str.substr(0, 16), cache_str.substr(0, 16)); EXPECT_EQ(exact_str.substr(0, 16), cache_str.substr(0, 16));
} }
@ -242,25 +252,25 @@ TEST(fp_test, dragonbox_max_k) {
TEST(fp_test, get_round_direction) { TEST(fp_test, get_round_direction) {
using fmt::detail::get_round_direction; using fmt::detail::get_round_direction;
using fmt::detail::round_direction; using fmt::detail::round_direction;
EXPECT_EQ(round_direction::down, get_round_direction(100, 50, 0)); EXPECT_EQ(get_round_direction(100, 50, 0), round_direction::down);
EXPECT_EQ(round_direction::up, get_round_direction(100, 51, 0)); EXPECT_EQ(get_round_direction(100, 51, 0), round_direction::up);
EXPECT_EQ(round_direction::down, get_round_direction(100, 40, 10)); EXPECT_EQ(get_round_direction(100, 40, 10), round_direction::down);
EXPECT_EQ(round_direction::up, get_round_direction(100, 60, 10)); EXPECT_EQ(get_round_direction(100, 60, 10), round_direction::up);
for (size_t i = 41; i < 60; ++i) for (size_t i = 41; i < 60; ++i)
EXPECT_EQ(round_direction::unknown, get_round_direction(100, i, 10)); EXPECT_EQ(get_round_direction(100, i, 10), round_direction::unknown);
uint64_t max = max_value<uint64_t>(); uint64_t max = max_value<uint64_t>();
EXPECT_THROW(get_round_direction(100, 100, 0), assertion_failure); EXPECT_THROW(get_round_direction(100, 100, 0), assertion_failure);
EXPECT_THROW(get_round_direction(100, 0, 100), assertion_failure); EXPECT_THROW(get_round_direction(100, 0, 100), assertion_failure);
EXPECT_THROW(get_round_direction(100, 0, 50), assertion_failure); EXPECT_THROW(get_round_direction(100, 0, 50), assertion_failure);
// Check that remainder + error doesn't overflow. // Check that remainder + error doesn't overflow.
EXPECT_EQ(round_direction::up, get_round_direction(max, max - 1, 2)); EXPECT_EQ(get_round_direction(max, max - 1, 2), round_direction::up);
// Check that 2 * (remainder + error) doesn't overflow. // Check that 2 * (remainder + error) doesn't overflow.
EXPECT_EQ(round_direction::unknown, EXPECT_EQ(get_round_direction(max, max / 2 + 1, max / 2),
get_round_direction(max, max / 2 + 1, max / 2)); round_direction::unknown);
// Check that remainder - error doesn't overflow. // Check that remainder - error doesn't overflow.
EXPECT_EQ(round_direction::unknown, get_round_direction(100, 40, 41)); EXPECT_EQ(get_round_direction(100, 40, 41), round_direction::unknown);
// Check that 2 * (remainder - error) doesn't overflow. // Check that 2 * (remainder - error) doesn't overflow.
EXPECT_EQ(round_direction::up, get_round_direction(max, max - 1, 1)); EXPECT_EQ(get_round_direction(max, max - 1, 1), round_direction::up);
} }
TEST(fp_test, fixed_handler) { TEST(fp_test, fixed_handler) {
@ -283,20 +293,20 @@ TEST(fp_test, fixed_handler) {
} }
TEST(fp_test, grisu_format_compiles_with_on_ieee_double) { TEST(fp_test, grisu_format_compiles_with_on_ieee_double) {
fmt::memory_buffer buf; auto buf = fmt::memory_buffer();
format_float(0.42, -1, fmt::detail::float_specs(), buf); format_float(0.42, -1, fmt::detail::float_specs(), buf);
} }
TEST(format_impl_test, format_error_code) { TEST(format_impl_test, format_error_code) {
std::string msg = "error 42", sep = ": "; std::string msg = "error 42", sep = ": ";
{ {
fmt::memory_buffer buffer; auto buffer = fmt::memory_buffer();
format_to(fmt::appender(buffer), "garbage"); format_to(fmt::appender(buffer), "garbage");
fmt::detail::format_error_code(buffer, 42, "test"); fmt::detail::format_error_code(buffer, 42, "test");
EXPECT_EQ("test: " + msg, to_string(buffer)); EXPECT_EQ(to_string(buffer), "test: " + msg);
} }
{ {
fmt::memory_buffer buffer; auto buffer = fmt::memory_buffer();
auto prefix = auto prefix =
std::string(fmt::inline_buffer_size - msg.size() - sep.size() + 1, 'x'); std::string(fmt::inline_buffer_size - msg.size() - sep.size() + 1, 'x');
fmt::detail::format_error_code(buffer, 42, prefix); fmt::detail::format_error_code(buffer, 42, prefix);
@ -317,7 +327,7 @@ TEST(format_impl_test, format_error_code) {
// Test with a message that doesn't fit into the buffer. // Test with a message that doesn't fit into the buffer.
prefix += 'x'; prefix += 'x';
fmt::detail::format_error_code(buffer, codes[i], prefix); fmt::detail::format_error_code(buffer, codes[i], prefix);
EXPECT_EQ(msg, to_string(buffer)); EXPECT_EQ(to_string(buffer), msg);
} }
} }
@ -333,8 +343,8 @@ template <typename Int> void test_count_digits() {
for (Int i = 0; i < 10; ++i) EXPECT_EQ(1u, fmt::detail::count_digits(i)); for (Int i = 0; i < 10; ++i) EXPECT_EQ(1u, fmt::detail::count_digits(i));
for (Int i = 1, n = 1, end = max_value<Int>() / 10; n <= end; ++i) { for (Int i = 1, n = 1, end = max_value<Int>() / 10; n <= end; ++i) {
n *= 10; n *= 10;
EXPECT_EQ(i, fmt::detail::count_digits(n - 1)); EXPECT_EQ(fmt::detail::count_digits(n - 1), i);
EXPECT_EQ(i + 1, fmt::detail::count_digits(n)); EXPECT_EQ(fmt::detail::count_digits(n), i + 1);
} }
} }
@ -345,11 +355,9 @@ TEST(format_impl_test, count_digits) {
#ifdef _WIN32 #ifdef _WIN32
# include <windows.h> # include <windows.h>
#endif
#ifdef _WIN32
TEST(format_impl_test, write_console_signature) { TEST(format_impl_test, write_console_signature) {
decltype(WriteConsoleW)* p = fmt::detail::WriteConsoleW; decltype(::WriteConsoleW)* p = fmt::detail::WriteConsoleW;
(void)p; (void)p;
} }
#endif #endif