make FP formatting available to be used at compile-time (#2426)

* works only with FMT_HEADER_ONLY
* works only with float and double types (not long double)
This commit is contained in:
Alexey Ochapov 2021-09-18 18:03:23 +03:00 committed by GitHub
parent d9fd695ac7
commit b4d9d82e1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 278 additions and 109 deletions

View File

@ -864,10 +864,6 @@ template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
inline bool isfinite(T) { inline bool isfinite(T) {
return true; return true;
} }
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
inline bool isfinite(T value) {
return std::isfinite(value);
}
// Converts value to int and checks that it's in the range [0, upper). // Converts value to int and checks that it's in the range [0, upper).
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>

View File

@ -1178,8 +1178,8 @@ template <typename Context> class value {
constexpr FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {} constexpr FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {}
FMT_INLINE value(int128_t val) : int128_value(val) {} FMT_INLINE value(int128_t val) : int128_value(val) {}
FMT_INLINE value(uint128_t val) : uint128_value(val) {} FMT_INLINE value(uint128_t val) : uint128_value(val) {}
FMT_INLINE value(float val) : float_value(val) {} constexpr FMT_INLINE value(float val) : float_value(val) {}
FMT_INLINE value(double val) : double_value(val) {} constexpr FMT_INLINE value(double val) : double_value(val) {}
FMT_INLINE value(long double val) : long_double_value(val) {} FMT_INLINE value(long double val) : long_double_value(val) {}
constexpr FMT_INLINE value(bool val) : bool_value(val) {} constexpr FMT_INLINE value(bool val) : bool_value(val) {}
constexpr FMT_INLINE value(char_type val) : char_value(val) {} constexpr FMT_INLINE value(char_type val) : char_value(val) {}

View File

@ -152,6 +152,75 @@ template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) {
// log10(2) = 0x0.4d104d427de7fbcc... // log10(2) = 0x0.4d104d427de7fbcc...
static constexpr uint64_t log10_2_significand = 0x4d104d427de7fbcc; static constexpr uint64_t log10_2_significand = 0x4d104d427de7fbcc;
template <typename T = void> struct basic_impl_data {
// Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340.
// These are generated by support/compute-powers.py.
static constexpr uint64_t pow10_significands[87] = {
0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76,
0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df,
0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c,
0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5,
0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57,
0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7,
0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e,
0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996,
0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126,
0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053,
0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f,
0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b,
0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06,
0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb,
0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000,
0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984,
0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068,
0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8,
0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758,
0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85,
0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d,
0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25,
0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2,
0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a,
0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410,
0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129,
0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85,
0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841,
0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b,
};
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
FMT_GCC_PRAGMA("GCC diagnostic push")
FMT_GCC_PRAGMA("GCC diagnostic ignored \"-Wnarrowing\"")
#endif
// Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding
// to significands above.
static constexpr int16_t pow10_exponents[87] = {
-1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954,
-927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661,
-635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369,
-343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77,
-50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216,
242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508,
534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800,
827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066};
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
FMT_GCC_PRAGMA("GCC diagnostic pop")
#endif
static constexpr uint64_t power_of_10_64[20] = {
1, FMT_POWERS_OF_10(1ULL), FMT_POWERS_OF_10(1000000000ULL),
10000000000000000000ULL};
};
// This is a struct rather than an alias to avoid shadowing warnings in gcc.
struct impl_data : basic_impl_data<> {};
#if __cplusplus < 201703L
template <typename T>
constexpr uint64_t basic_impl_data<T>::pow10_significands[];
template <typename T> constexpr int16_t basic_impl_data<T>::pow10_exponents[];
template <typename T> constexpr uint64_t basic_impl_data<T>::power_of_10_64[];
#endif
template <typename T> struct bits { template <typename T> struct bits {
static FMT_CONSTEXPR_DECL const int value = static FMT_CONSTEXPR_DECL const int value =
static_cast<int>(sizeof(T) * std::numeric_limits<unsigned char>::digits); static_cast<int>(sizeof(T) * std::numeric_limits<unsigned char>::digits);
@ -275,53 +344,8 @@ FMT_CONSTEXPR inline fp operator*(fp x, fp y) {
// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its // Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its
// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`. // (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`.
inline fp get_cached_power(int min_exponent, int& pow10_exponent) { FMT_CONSTEXPR inline fp get_cached_power(int min_exponent,
// Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. int& pow10_exponent) {
// These are generated by support/compute-powers.py.
static constexpr const uint64_t pow10_significands[] = {
0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76,
0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df,
0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c,
0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5,
0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57,
0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7,
0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e,
0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996,
0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126,
0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053,
0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f,
0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b,
0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06,
0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb,
0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000,
0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984,
0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068,
0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8,
0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758,
0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85,
0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d,
0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25,
0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2,
0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a,
0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410,
0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129,
0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85,
0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841,
0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b,
};
// Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding
// to significands above.
static constexpr const int16_t pow10_exponents[] = {
-1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954,
-927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661,
-635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369,
-343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77,
-50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216,
242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508,
534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800,
827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066};
const int shift = 32; const int shift = 32;
const auto significand = static_cast<int64_t>(log10_2_significand); const auto significand = static_cast<int64_t>(log10_2_significand);
int index = static_cast<int>( int index = static_cast<int>(
@ -335,7 +359,8 @@ inline fp get_cached_power(int min_exponent, int& pow10_exponent) {
const int dec_exp_step = 8; const int dec_exp_step = 8;
index = (index - first_dec_exp - 1) / dec_exp_step + 1; index = (index - first_dec_exp - 1) / dec_exp_step + 1;
pow10_exponent = first_dec_exp + index * dec_exp_step; pow10_exponent = first_dec_exp + index * dec_exp_step;
return {pow10_significands[index], pow10_exponents[index]}; return {impl_data::pow10_significands[index],
impl_data::pow10_exponents[index]};
} }
// A simple accumulator to hold the sums of terms in bigint::square if uint128_t // A simple accumulator to hold the sums of terms in bigint::square if uint128_t
@ -636,13 +661,6 @@ enum result {
}; };
} }
inline uint64_t power_of_10_64(int exp) {
static constexpr const uint64_t data[] = {1, FMT_POWERS_OF_10(1),
FMT_POWERS_OF_10(1000000000ULL),
10000000000000000000ULL};
return data[exp];
}
// Generates output using the Grisu digit-gen algorithm. // Generates output using the Grisu digit-gen algorithm.
// error: the size of the region (lower, upper) outside of which numbers // error: the size of the region (lower, upper) outside of which numbers
// definitely do not round to value (Delta in Grisu3). // definitely do not round to value (Delta in Grisu3).
@ -662,7 +680,7 @@ FMT_INLINE FMT_CONSTEXPR digits::result grisu_gen_digits(fp value,
uint64_t fractional = value.f & (one.f - 1); uint64_t fractional = value.f & (one.f - 1);
exp = count_digits(integral); // kappa in Grisu. exp = count_digits(integral); // kappa in Grisu.
// Divide by 10 to prevent overflow. // Divide by 10 to prevent overflow.
auto result = handler.on_start(power_of_10_64(exp - 1) << -one.e, auto result = handler.on_start(impl_data::power_of_10_64[exp - 1] << -one.e,
value.f / 10, error * 10, exp); value.f / 10, error * 10, exp);
if (result != digits::more) return result; if (result != digits::more) return result;
// Generate digits for the integral part. This can produce up to 10 digits. // Generate digits for the integral part. This can produce up to 10 digits.
@ -712,8 +730,8 @@ FMT_INLINE FMT_CONSTEXPR digits::result grisu_gen_digits(fp value,
--exp; --exp;
auto remainder = (static_cast<uint64_t>(integral) << -one.e) + fractional; auto remainder = (static_cast<uint64_t>(integral) << -one.e) + fractional;
result = handler.on_digit(static_cast<char>('0' + digit), result = handler.on_digit(static_cast<char>('0' + digit),
power_of_10_64(exp) << -one.e, remainder, error, impl_data::power_of_10_64[exp] << -one.e,
exp, true); remainder, error, exp, true);
if (result != digits::more) return result; if (result != digits::more) return result;
} while (exp > 0); } while (exp > 0);
// Generate digits for the fractional part. // Generate digits for the fractional part.
@ -2225,8 +2243,8 @@ small_divisor_case_label:
// Floating-Point Printout ((FPP)^2) algorithm by Steele & White: // Floating-Point Printout ((FPP)^2) algorithm by Steele & White:
// https://fmt.dev/papers/p372-steele.pdf. // https://fmt.dev/papers/p372-steele.pdf.
template <typename Double> template <typename Double>
void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf, FMT_CONSTEXPR20 void fallback_format(Double d, int num_digits, bool binary32,
int& exp10) { buffer<char>& buf, int& exp10) {
bigint numerator; // 2 * R in (FPP)^2. bigint numerator; // 2 * R in (FPP)^2.
bigint denominator; // 2 * S in (FPP)^2. bigint denominator; // 2 * S in (FPP)^2.
// lower and upper are differences between value and corresponding boundaries. // lower and upper are differences between value and corresponding boundaries.
@ -2342,7 +2360,9 @@ void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf,
} }
template <typename T> template <typename T>
int format_float(T value, int precision, float_specs specs, buffer<char>& buf) { FMT_HEADER_ONLY_CONSTEXPR20 int format_float(T value, int precision,
float_specs specs,
buffer<char>& buf) {
static_assert(!std::is_same<T, float>::value, ""); static_assert(!std::is_same<T, float>::value, "");
FMT_ASSERT(value >= 0, "value is negative"); FMT_ASSERT(value >= 0, "value is negative");
@ -2353,13 +2373,13 @@ int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
return 0; return 0;
} }
buf.try_resize(to_unsigned(precision)); buf.try_resize(to_unsigned(precision));
std::uninitialized_fill_n(buf.data(), precision, '0'); fill_n(buf.data(), precision, '0');
return -precision; return -precision;
} }
if (!specs.use_grisu) return snprintf_float(value, precision, specs, buf); if (!specs.use_grisu) return snprintf_float(value, precision, specs, buf);
if (precision < 0) { if (!is_constant_evaluated() && precision < 0) {
// Use Dragonbox for the shortest format. // Use Dragonbox for the shortest format.
if (specs.binary32) { if (specs.binary32) {
auto dec = dragonbox::to_decimal(static_cast<float>(value)); auto dec = dragonbox::to_decimal(static_cast<float>(value));
@ -2385,7 +2405,8 @@ int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
const int max_double_digits = 767; const int max_double_digits = 767;
if (precision > max_double_digits) precision = max_double_digits; if (precision > max_double_digits) precision = max_double_digits;
fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed}; fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error) { if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error ||
is_constant_evaluated()) {
exp += handler.size - cached_exp10 - 1; exp += handler.size - cached_exp10 - 1;
fallback_format(value, handler.precision, specs.binary32, buf, exp); fallback_format(value, handler.precision, specs.binary32, buf, exp);
} else { } else {

View File

@ -261,6 +261,12 @@ inline auto ctzll(uint64_t x) -> int {
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif #endif
#ifdef FMT_HEADER_ONLY
# define FMT_HEADER_ONLY_CONSTEXPR20 FMT_CONSTEXPR20
#else
# define FMT_HEADER_ONLY_CONSTEXPR20
#endif
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
// An equivalent of `*reinterpret_cast<Dest*>(&source)` that doesn't have // An equivalent of `*reinterpret_cast<Dest*>(&source)` that doesn't have
@ -1247,7 +1253,7 @@ constexpr auto exponent_mask() ->
// Writes the exponent exp in the form "[+-]d{2,3}" to buffer. // Writes the exponent exp in the form "[+-]d{2,3}" to buffer.
template <typename Char, typename It> template <typename Char, typename It>
auto write_exponent(int exp, It it) -> It { FMT_CONSTEXPR auto write_exponent(int exp, It it) -> It {
FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range"); FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range");
if (exp < 0) { if (exp < 0) {
*it++ = static_cast<Char>('-'); *it++ = static_cast<Char>('-');
@ -1268,16 +1274,19 @@ auto write_exponent(int exp, It it) -> It {
} }
template <typename T> template <typename T>
auto format_float(T value, int precision, float_specs specs, buffer<char>& buf) FMT_HEADER_ONLY_CONSTEXPR20 auto format_float(T value, int precision,
-> int; float_specs specs,
buffer<char>& buf) -> int;
// Formats a floating-point number with snprintf. // Formats a floating-point number with snprintf.
template <typename T> template <typename T>
auto snprintf_float(T value, int precision, float_specs specs, auto snprintf_float(T value, int precision, float_specs specs,
buffer<char>& buf) -> int; buffer<char>& buf) -> int;
template <typename T> auto promote_float(T value) -> T { return value; } template <typename T> constexpr auto promote_float(T value) -> T {
inline auto promote_float(float value) -> double { return value;
}
constexpr auto promote_float(float value) -> double {
return static_cast<double>(value); return static_cast<double>(value);
} }
@ -1649,8 +1658,9 @@ FMT_CONSTEXPR auto write(OutputIt out, const Char* s,
} }
template <typename Char, typename OutputIt> template <typename Char, typename OutputIt>
auto write_nonfinite(OutputIt out, bool isinf, basic_format_specs<Char> specs, FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isinf,
const float_specs& fspecs) -> OutputIt { basic_format_specs<Char> specs,
const float_specs& fspecs) -> OutputIt {
auto str = auto str =
isinf ? (fspecs.upper ? "INF" : "inf") : (fspecs.upper ? "NAN" : "nan"); isinf ? (fspecs.upper ? "INF" : "inf") : (fspecs.upper ? "NAN" : "nan");
constexpr size_t str_size = 3; constexpr size_t str_size = 3;
@ -1673,7 +1683,7 @@ struct big_decimal_fp {
int exponent; int exponent;
}; };
inline auto get_significand_size(const big_decimal_fp& fp) -> int { constexpr auto get_significand_size(const big_decimal_fp& fp) -> int {
return fp.significand_size; return fp.significand_size;
} }
template <typename T> template <typename T>
@ -1682,8 +1692,8 @@ inline auto get_significand_size(const dragonbox::decimal_fp<T>& fp) -> int {
} }
template <typename Char, typename OutputIt> template <typename Char, typename OutputIt>
inline auto write_significand(OutputIt out, const char* significand, constexpr auto write_significand(OutputIt out, const char* significand,
int significand_size) -> OutputIt { int significand_size) -> OutputIt {
return copy_str<Char>(significand, significand + significand_size, out); return copy_str<Char>(significand, significand + significand_size, out);
} }
template <typename Char, typename OutputIt, typename UInt> template <typename Char, typename OutputIt, typename UInt>
@ -1691,11 +1701,10 @@ inline auto write_significand(OutputIt out, UInt significand,
int significand_size) -> OutputIt { int significand_size) -> OutputIt {
return format_decimal<Char>(out, significand, significand_size).end; return format_decimal<Char>(out, significand, significand_size).end;
} }
template <typename Char, typename OutputIt, typename T> template <typename Char, typename OutputIt, typename T, typename Grouping>
inline auto write_significand(OutputIt out, T significand, int significand_size, FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand,
int exponent, int significand_size, int exponent,
const digit_grouping<Char>& grouping) const Grouping& grouping) -> OutputIt {
-> OutputIt {
if (!grouping.separator()) { if (!grouping.separator()) {
out = write_significand<Char>(out, significand, significand_size); out = write_significand<Char>(out, significand, significand_size);
return detail::fill_n(out, exponent, static_cast<Char>('0')); return detail::fill_n(out, exponent, static_cast<Char>('0'));
@ -1736,9 +1745,9 @@ inline auto write_significand(OutputIt out, UInt significand,
} }
template <typename OutputIt, typename Char> template <typename OutputIt, typename Char>
inline auto write_significand(OutputIt out, const char* significand, FMT_CONSTEXPR auto write_significand(OutputIt out, const char* significand,
int significand_size, int integral_size, int significand_size, int integral_size,
Char decimal_point) -> OutputIt { Char decimal_point) -> OutputIt {
out = detail::copy_str_noinline<Char>(significand, out = detail::copy_str_noinline<Char>(significand,
significand + integral_size, out); significand + integral_size, out);
if (!decimal_point) return out; if (!decimal_point) return out;
@ -1747,11 +1756,11 @@ inline auto write_significand(OutputIt out, const char* significand,
significand + significand_size, out); significand + significand_size, out);
} }
template <typename OutputIt, typename Char, typename T> template <typename OutputIt, typename Char, typename T, typename Grouping>
inline auto write_significand(OutputIt out, T significand, int significand_size, FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand,
int integral_size, Char decimal_point, int significand_size, int integral_size,
const digit_grouping<Char>& grouping) Char decimal_point,
-> OutputIt { const Grouping& grouping) -> OutputIt {
if (!grouping.separator()) { if (!grouping.separator()) {
return write_significand(out, significand, significand_size, integral_size, return write_significand(out, significand, significand_size, integral_size,
decimal_point); decimal_point);
@ -1765,13 +1774,15 @@ inline auto write_significand(OutputIt out, T significand, int significand_size,
buffer.end(), out); buffer.end(), out);
} }
template <typename OutputIt, typename DecimalFP, typename Char> template <typename OutputIt, typename DecimalFP, typename Char,
auto write_float(OutputIt out, const DecimalFP& fp, typename Grouping = digit_grouping<Char>>
const basic_format_specs<Char>& specs, float_specs fspecs, FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& fp,
locale_ref loc) -> OutputIt { const basic_format_specs<Char>& specs,
float_specs fspecs, locale_ref loc)
-> OutputIt {
auto significand = fp.significand; auto significand = fp.significand;
int significand_size = get_significand_size(fp); int significand_size = get_significand_size(fp);
static const Char zero = static_cast<Char>('0'); constexpr Char zero = static_cast<Char>('0');
auto sign = fspecs.sign; auto sign = fspecs.sign;
size_t size = to_unsigned(significand_size) + (sign ? 1 : 0); size_t size = to_unsigned(significand_size) + (sign ? 1 : 0);
using iterator = reserve_iterator<OutputIt>; using iterator = reserve_iterator<OutputIt>;
@ -1830,7 +1841,7 @@ auto write_float(OutputIt out, const DecimalFP& fp,
if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 1; if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 1;
if (num_zeros > 0) size += to_unsigned(num_zeros) + 1; if (num_zeros > 0) size += to_unsigned(num_zeros) + 1;
} }
auto grouping = digit_grouping<Char>(loc, fspecs.locale); auto grouping = Grouping(loc, fspecs.locale);
size += to_unsigned(grouping.count_separators(significand_size)); size += to_unsigned(grouping.count_separators(significand_size));
return write_padded<align::right>(out, specs, size, [&](iterator it) { return write_padded<align::right>(out, specs, size, [&](iterator it) {
if (sign) *it++ = detail::sign<Char>(sign); if (sign) *it++ = detail::sign<Char>(sign);
@ -1844,7 +1855,7 @@ auto write_float(OutputIt out, const DecimalFP& fp,
// 1234e-2 -> 12.34[0+] // 1234e-2 -> 12.34[0+]
int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0; int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0;
size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0); size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0);
auto grouping = digit_grouping<Char>(loc, fspecs.locale); auto grouping = Grouping(loc, fspecs.locale);
size += to_unsigned(grouping.count_separators(significand_size)); size += to_unsigned(grouping.count_separators(significand_size));
return write_padded<align::right>(out, specs, size, [&](iterator it) { return write_padded<align::right>(out, specs, size, [&](iterator it) {
if (sign) *it++ = detail::sign<Char>(sign); if (sign) *it++ = detail::sign<Char>(sign);
@ -1871,22 +1882,93 @@ auto write_float(OutputIt out, const DecimalFP& fp,
}); });
} }
template <typename Char> class fallback_digit_grouping {
public:
constexpr fallback_digit_grouping(locale_ref, bool) {}
constexpr Char separator() const { return Char(); }
constexpr int count_separators(int) const { return 0; }
template <typename Out, typename C>
constexpr Out apply(Out out, basic_string_view<C>) const {
return out;
}
};
template <typename OutputIt, typename DecimalFP, typename Char>
FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& fp,
const basic_format_specs<Char>& specs,
float_specs fspecs, locale_ref loc)
-> OutputIt {
if (is_constant_evaluated()) {
return do_write_float<OutputIt, DecimalFP, Char,
fallback_digit_grouping<Char>>(out, fp, specs, fspecs,
loc);
} else {
return do_write_float(out, fp, specs, fspecs, loc);
}
}
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
FMT_CONSTEXPR20 bool isinf(T value) {
if (is_constant_evaluated()) {
#if defined(__cpp_if_constexpr)
if constexpr (std::numeric_limits<double>::is_iec559) {
auto bits = detail::bit_cast<uint64_t>(static_cast<double>(value));
constexpr auto significand_bits =
dragonbox::float_info<double>::significand_bits;
return (bits & exponent_mask<double>()) &&
!(bits & ((uint64_t(1) << significand_bits) - 1));
}
#endif
}
return std::isinf(value);
}
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
FMT_CONSTEXPR20 bool isfinite(T value) {
if (is_constant_evaluated()) {
#if defined(__cpp_if_constexpr)
if constexpr (std::numeric_limits<double>::is_iec559) {
auto bits = detail::bit_cast<uint64_t>(static_cast<double>(value));
return (bits & exponent_mask<double>()) != exponent_mask<double>();
}
#endif
}
return std::isfinite(value);
}
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
FMT_INLINE FMT_CONSTEXPR bool signbit(T value) {
if (is_constant_evaluated()) {
#ifdef __cpp_if_constexpr
if constexpr (std::numeric_limits<double>::is_iec559) {
auto bits = detail::bit_cast<uint64_t>(static_cast<double>(value));
return (bits & (uint64_t(1) << (num_bits<uint64_t>() - 1))) != 0;
}
#endif
}
return std::signbit(value);
}
template <typename Char, typename OutputIt, typename T, template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(std::is_floating_point<T>::value)> FMT_ENABLE_IF(std::is_floating_point<T>::value)>
auto write(OutputIt out, T value, basic_format_specs<Char> specs, FMT_CONSTEXPR20 auto write(OutputIt out, T value,
locale_ref loc = {}) -> OutputIt { basic_format_specs<Char> specs, locale_ref loc = {})
-> OutputIt {
if (const_check(!is_supported_floating_point(value))) return out; if (const_check(!is_supported_floating_point(value))) return out;
float_specs fspecs = parse_float_type_spec(specs); float_specs fspecs = parse_float_type_spec(specs);
fspecs.sign = specs.sign; fspecs.sign = specs.sign;
if (std::signbit(value)) { // value < 0 is false for NaN so use signbit. if (detail::signbit(value)) { // value < 0 is false for NaN so use signbit.
fspecs.sign = sign::minus; fspecs.sign = sign::minus;
value = -value; value = -value;
} else if (fspecs.sign == sign::minus) { } else if (fspecs.sign == sign::minus) {
fspecs.sign = sign::none; fspecs.sign = sign::none;
} }
if (!std::isfinite(value)) if (!detail::isfinite(value))
return write_nonfinite(out, std::isinf(value), specs, fspecs); return write_nonfinite(out, detail::isinf(value), specs, fspecs);
if (specs.align == align::numeric && fspecs.sign) { if (specs.align == align::numeric && fspecs.sign) {
auto it = reserve(out, 1); auto it = reserve(out, 1);
@ -1922,7 +2004,11 @@ auto write(OutputIt out, T value, basic_format_specs<Char> specs,
template <typename Char, typename OutputIt, typename T, template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(is_fast_float<T>::value)> FMT_ENABLE_IF(is_fast_float<T>::value)>
auto write(OutputIt out, T value) -> OutputIt { FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt {
if (is_constant_evaluated()) {
return write(out, value, basic_format_specs<Char>());
}
if (const_check(!is_supported_floating_point(value))) return out; if (const_check(!is_supported_floating_point(value))) return out;
using floaty = conditional_t<std::is_same<T, long double>::value, double, T>; using floaty = conditional_t<std::is_same<T, long double>::value, double, T>;
@ -1930,13 +2016,12 @@ auto write(OutputIt out, T value) -> OutputIt {
auto bits = bit_cast<uint>(value); auto bits = bit_cast<uint>(value);
auto fspecs = float_specs(); auto fspecs = float_specs();
auto sign_bit = bits & (uint(1) << (num_bits<uint>() - 1)); if (detail::signbit(value)) {
if (sign_bit != 0) {
fspecs.sign = sign::minus; fspecs.sign = sign::minus;
value = -value; value = -value;
} }
static const auto specs = basic_format_specs<Char>(); constexpr auto specs = basic_format_specs<Char>();
uint mask = exponent_mask<floaty>(); uint mask = exponent_mask<floaty>();
if ((bits & mask) == mask) if ((bits & mask) == mask)
return write_nonfinite(out, std::isinf(value), specs, fspecs); return write_nonfinite(out, std::isinf(value), specs, fspecs);

View File

@ -84,6 +84,11 @@ if (NOT (MSVC AND BUILD_SHARED_LIBS))
endif () endif ()
add_fmt_test(ostream-test) add_fmt_test(ostream-test)
add_fmt_test(compile-test) add_fmt_test(compile-test)
add_fmt_test(compile-fp-test HEADER_ONLY)
if (MSVC)
# Without this option, MSVC returns 199711L for the __cplusplus macro.
target_compile_options(compile-fp-test PRIVATE /Zc:__cplusplus)
endif()
add_fmt_test(printf-test) add_fmt_test(printf-test)
add_fmt_test(ranges-test ranges-odr-test.cc) add_fmt_test(ranges-test ranges-odr-test.cc)
add_fmt_test(scan-test) add_fmt_test(scan-test)

62
test/compile-fp-test.cc Normal file
View File

@ -0,0 +1,62 @@
// Formatting library for C++ - formatting library tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include "fmt/compile.h"
#include "gmock/gmock.h"
#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806 && \
defined(__cpp_constexpr) && __cpp_constexpr >= 201907 && \
defined(__cpp_constexpr_dynamic_alloc) && \
__cpp_constexpr_dynamic_alloc >= 201907 && __cplusplus >= 202002L
template <size_t max_string_length, typename Char = char> struct test_string {
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
}
Char buffer[max_string_length]{};
};
template <size_t max_string_length, typename Char = char, typename... Args>
consteval auto test_format(auto format, const Args&... args) {
test_string<max_string_length, Char> string{};
fmt::format_to(string.buffer, format, args...);
return string;
}
TEST(compile_time_formatting_test, floating_point) {
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{}"), 0.0f));
EXPECT_EQ("392.500000", test_format<11>(FMT_COMPILE("{0:f}"), 392.5f));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:}"), 0.0));
EXPECT_EQ("0.000000", test_format<9>(FMT_COMPILE("{:f}"), 0.0));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:g}"), 0.0));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:g}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:G}"), 392.65));
EXPECT_EQ("4.9014e+06", test_format<11>(FMT_COMPILE("{:g}"), 4.9014e6));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:f}"), -392.65));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:F}"), -392.65));
EXPECT_EQ("3.926500e+02", test_format<13>(FMT_COMPILE("{0:e}"), 392.65));
EXPECT_EQ("3.926500E+02", test_format<13>(FMT_COMPILE("{0:E}"), 392.65));
EXPECT_EQ("+0000392.6", test_format<11>(FMT_COMPILE("{0:+010.4g}"), 392.65));
EXPECT_EQ("9223372036854775808.000000",
test_format<27>(FMT_COMPILE("{:f}"), 9223372036854775807.0));
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
EXPECT_EQ("nan", test_format<4>(FMT_COMPILE("{}"), nan));
EXPECT_EQ("+nan", test_format<5>(FMT_COMPILE("{:+}"), nan));
if (std::signbit(-nan))
EXPECT_EQ("-nan", test_format<5>(FMT_COMPILE("{}"), -nan));
else
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
constexpr double inf = std::numeric_limits<double>::infinity();
EXPECT_EQ("inf", test_format<4>(FMT_COMPILE("{}"), inf));
EXPECT_EQ("+inf", test_format<5>(FMT_COMPILE("{:+}"), inf));
EXPECT_EQ("-inf", test_format<5>(FMT_COMPILE("{}"), -inf));
}
#endif