diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index e9c2e506..61602f68 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -864,10 +864,6 @@ template ::value)> inline bool isfinite(T) { return true; } -template ::value)> -inline bool isfinite(T value) { - return std::isfinite(value); -} // Converts value to int and checks that it's in the range [0, upper). template ::value)> diff --git a/include/fmt/core.h b/include/fmt/core.h index 1b8118d4..41748c24 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1178,8 +1178,8 @@ template class value { constexpr FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {} FMT_INLINE value(int128_t val) : int128_value(val) {} FMT_INLINE value(uint128_t val) : uint128_value(val) {} - FMT_INLINE value(float val) : float_value(val) {} - FMT_INLINE value(double val) : double_value(val) {} + constexpr FMT_INLINE value(float val) : float_value(val) {} + constexpr FMT_INLINE value(double val) : 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(char_type val) : char_value(val) {} diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index f90782b4..b7d88270 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -152,6 +152,75 @@ template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) { // log10(2) = 0x0.4d104d427de7fbcc... static constexpr uint64_t log10_2_significand = 0x4d104d427de7fbcc; +template 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 +constexpr uint64_t basic_impl_data::pow10_significands[]; +template constexpr int16_t basic_impl_data::pow10_exponents[]; +template constexpr uint64_t basic_impl_data::power_of_10_64[]; +#endif + template struct bits { static FMT_CONSTEXPR_DECL const int value = static_cast(sizeof(T) * std::numeric_limits::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 // (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`. -inline fp get_cached_power(int min_exponent, int& pow10_exponent) { - // Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. - // 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}; - +FMT_CONSTEXPR inline fp get_cached_power(int min_exponent, + int& pow10_exponent) { const int shift = 32; const auto significand = static_cast(log10_2_significand); int index = static_cast( @@ -335,7 +359,8 @@ inline fp get_cached_power(int min_exponent, int& pow10_exponent) { const int dec_exp_step = 8; index = (index - first_dec_exp - 1) / dec_exp_step + 1; 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 @@ -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. // error: the size of the region (lower, upper) outside of which numbers // 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); exp = count_digits(integral); // kappa in Grisu. // 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); if (result != digits::more) return result; // 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; auto remainder = (static_cast(integral) << -one.e) + fractional; result = handler.on_digit(static_cast('0' + digit), - power_of_10_64(exp) << -one.e, remainder, error, - exp, true); + impl_data::power_of_10_64[exp] << -one.e, + remainder, error, exp, true); if (result != digits::more) return result; } while (exp > 0); // Generate digits for the fractional part. @@ -2225,8 +2243,8 @@ small_divisor_case_label: // Floating-Point Printout ((FPP)^2) algorithm by Steele & White: // https://fmt.dev/papers/p372-steele.pdf. template -void fallback_format(Double d, int num_digits, bool binary32, buffer& buf, - int& exp10) { +FMT_CONSTEXPR20 void fallback_format(Double d, int num_digits, bool binary32, + buffer& buf, int& exp10) { bigint numerator; // 2 * R in (FPP)^2. bigint denominator; // 2 * S in (FPP)^2. // 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& buf, } template -int format_float(T value, int precision, float_specs specs, buffer& buf) { +FMT_HEADER_ONLY_CONSTEXPR20 int format_float(T value, int precision, + float_specs specs, + buffer& buf) { static_assert(!std::is_same::value, ""); FMT_ASSERT(value >= 0, "value is negative"); @@ -2353,13 +2373,13 @@ int format_float(T value, int precision, float_specs specs, buffer& buf) { return 0; } buf.try_resize(to_unsigned(precision)); - std::uninitialized_fill_n(buf.data(), precision, '0'); + fill_n(buf.data(), precision, '0'); return -precision; } 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. if (specs.binary32) { auto dec = dragonbox::to_decimal(static_cast(value)); @@ -2385,7 +2405,8 @@ int format_float(T value, int precision, float_specs specs, buffer& buf) { const int max_double_digits = 767; if (precision > max_double_digits) precision = max_double_digits; 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; fallback_format(value, handler.precision, specs.binary32, buf, exp); } else { diff --git a/include/fmt/format.h b/include/fmt/format.h index 79d01d69..32febe1e 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -261,6 +261,12 @@ inline auto ctzll(uint64_t x) -> int { FMT_END_NAMESPACE #endif +#ifdef FMT_HEADER_ONLY +# define FMT_HEADER_ONLY_CONSTEXPR20 FMT_CONSTEXPR20 +#else +# define FMT_HEADER_ONLY_CONSTEXPR20 +#endif + FMT_BEGIN_NAMESPACE namespace detail { // An equivalent of `*reinterpret_cast(&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. template -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"); if (exp < 0) { *it++ = static_cast('-'); @@ -1268,16 +1274,19 @@ auto write_exponent(int exp, It it) -> It { } template -auto format_float(T value, int precision, float_specs specs, buffer& buf) - -> int; +FMT_HEADER_ONLY_CONSTEXPR20 auto format_float(T value, int precision, + float_specs specs, + buffer& buf) -> int; // Formats a floating-point number with snprintf. template auto snprintf_float(T value, int precision, float_specs specs, buffer& buf) -> int; -template auto promote_float(T value) -> T { return value; } -inline auto promote_float(float value) -> double { +template constexpr auto promote_float(T value) -> T { + return value; +} +constexpr auto promote_float(float value) -> double { return static_cast(value); } @@ -1649,8 +1658,9 @@ FMT_CONSTEXPR auto write(OutputIt out, const Char* s, } template -auto write_nonfinite(OutputIt out, bool isinf, basic_format_specs specs, - const float_specs& fspecs) -> OutputIt { +FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isinf, + basic_format_specs specs, + const float_specs& fspecs) -> OutputIt { auto str = isinf ? (fspecs.upper ? "INF" : "inf") : (fspecs.upper ? "NAN" : "nan"); constexpr size_t str_size = 3; @@ -1673,7 +1683,7 @@ struct big_decimal_fp { 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; } template @@ -1682,8 +1692,8 @@ inline auto get_significand_size(const dragonbox::decimal_fp& fp) -> int { } template -inline auto write_significand(OutputIt out, const char* significand, - int significand_size) -> OutputIt { +constexpr auto write_significand(OutputIt out, const char* significand, + int significand_size) -> OutputIt { return copy_str(significand, significand + significand_size, out); } template @@ -1691,11 +1701,10 @@ inline auto write_significand(OutputIt out, UInt significand, int significand_size) -> OutputIt { return format_decimal(out, significand, significand_size).end; } -template -inline auto write_significand(OutputIt out, T significand, int significand_size, - int exponent, - const digit_grouping& grouping) - -> OutputIt { +template +FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, + int significand_size, int exponent, + const Grouping& grouping) -> OutputIt { if (!grouping.separator()) { out = write_significand(out, significand, significand_size); return detail::fill_n(out, exponent, static_cast('0')); @@ -1736,9 +1745,9 @@ inline auto write_significand(OutputIt out, UInt significand, } template -inline auto write_significand(OutputIt out, const char* significand, - int significand_size, int integral_size, - Char decimal_point) -> OutputIt { +FMT_CONSTEXPR auto write_significand(OutputIt out, const char* significand, + int significand_size, int integral_size, + Char decimal_point) -> OutputIt { out = detail::copy_str_noinline(significand, significand + integral_size, out); if (!decimal_point) return out; @@ -1747,11 +1756,11 @@ inline auto write_significand(OutputIt out, const char* significand, significand + significand_size, out); } -template -inline auto write_significand(OutputIt out, T significand, int significand_size, - int integral_size, Char decimal_point, - const digit_grouping& grouping) - -> OutputIt { +template +FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, + int significand_size, int integral_size, + Char decimal_point, + const Grouping& grouping) -> OutputIt { if (!grouping.separator()) { return write_significand(out, significand, significand_size, integral_size, decimal_point); @@ -1765,13 +1774,15 @@ inline auto write_significand(OutputIt out, T significand, int significand_size, buffer.end(), out); } -template -auto write_float(OutputIt out, const DecimalFP& fp, - const basic_format_specs& specs, float_specs fspecs, - locale_ref loc) -> OutputIt { +template > +FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& fp, + const basic_format_specs& specs, + float_specs fspecs, locale_ref loc) + -> OutputIt { auto significand = fp.significand; int significand_size = get_significand_size(fp); - static const Char zero = static_cast('0'); + constexpr Char zero = static_cast('0'); auto sign = fspecs.sign; size_t size = to_unsigned(significand_size) + (sign ? 1 : 0); using iterator = reserve_iterator; @@ -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) size += to_unsigned(num_zeros) + 1; } - auto grouping = digit_grouping(loc, fspecs.locale); + auto grouping = Grouping(loc, fspecs.locale); size += to_unsigned(grouping.count_separators(significand_size)); return write_padded(out, specs, size, [&](iterator it) { if (sign) *it++ = detail::sign(sign); @@ -1844,7 +1855,7 @@ auto write_float(OutputIt out, const DecimalFP& fp, // 1234e-2 -> 12.34[0+] int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0; size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0); - auto grouping = digit_grouping(loc, fspecs.locale); + auto grouping = Grouping(loc, fspecs.locale); size += to_unsigned(grouping.count_separators(significand_size)); return write_padded(out, specs, size, [&](iterator it) { if (sign) *it++ = detail::sign(sign); @@ -1871,22 +1882,93 @@ auto write_float(OutputIt out, const DecimalFP& fp, }); } +template 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 + constexpr Out apply(Out out, basic_string_view) const { + return out; + } +}; + +template +FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& fp, + const basic_format_specs& specs, + float_specs fspecs, locale_ref loc) + -> OutputIt { + if (is_constant_evaluated()) { + return do_write_float>(out, fp, specs, fspecs, + loc); + } else { + return do_write_float(out, fp, specs, fspecs, loc); + } +} + +template ::value)> +FMT_CONSTEXPR20 bool isinf(T value) { + if (is_constant_evaluated()) { +#if defined(__cpp_if_constexpr) + if constexpr (std::numeric_limits::is_iec559) { + auto bits = detail::bit_cast(static_cast(value)); + constexpr auto significand_bits = + dragonbox::float_info::significand_bits; + return (bits & exponent_mask()) && + !(bits & ((uint64_t(1) << significand_bits) - 1)); + } +#endif + } + return std::isinf(value); +} + +template ::value)> +FMT_CONSTEXPR20 bool isfinite(T value) { + if (is_constant_evaluated()) { +#if defined(__cpp_if_constexpr) + if constexpr (std::numeric_limits::is_iec559) { + auto bits = detail::bit_cast(static_cast(value)); + return (bits & exponent_mask()) != exponent_mask(); + } +#endif + } + return std::isfinite(value); +} + +template ::value)> +FMT_INLINE FMT_CONSTEXPR bool signbit(T value) { + if (is_constant_evaluated()) { +#ifdef __cpp_if_constexpr + if constexpr (std::numeric_limits::is_iec559) { + auto bits = detail::bit_cast(static_cast(value)); + return (bits & (uint64_t(1) << (num_bits() - 1))) != 0; + } +#endif + } + return std::signbit(value); +} + template ::value)> -auto write(OutputIt out, T value, basic_format_specs specs, - locale_ref loc = {}) -> OutputIt { +FMT_CONSTEXPR20 auto write(OutputIt out, T value, + basic_format_specs specs, locale_ref loc = {}) + -> OutputIt { if (const_check(!is_supported_floating_point(value))) return out; float_specs fspecs = parse_float_type_spec(specs); 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; value = -value; } else if (fspecs.sign == sign::minus) { fspecs.sign = sign::none; } - if (!std::isfinite(value)) - return write_nonfinite(out, std::isinf(value), specs, fspecs); + if (!detail::isfinite(value)) + return write_nonfinite(out, detail::isinf(value), specs, fspecs); if (specs.align == align::numeric && fspecs.sign) { auto it = reserve(out, 1); @@ -1922,7 +2004,11 @@ auto write(OutputIt out, T value, basic_format_specs specs, template ::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()); + } + if (const_check(!is_supported_floating_point(value))) return out; using floaty = conditional_t::value, double, T>; @@ -1930,13 +2016,12 @@ auto write(OutputIt out, T value) -> OutputIt { auto bits = bit_cast(value); auto fspecs = float_specs(); - auto sign_bit = bits & (uint(1) << (num_bits() - 1)); - if (sign_bit != 0) { + if (detail::signbit(value)) { fspecs.sign = sign::minus; value = -value; } - static const auto specs = basic_format_specs(); + constexpr auto specs = basic_format_specs(); uint mask = exponent_mask(); if ((bits & mask) == mask) return write_nonfinite(out, std::isinf(value), specs, fspecs); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6bf68fb3..6a27e33f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -84,6 +84,11 @@ if (NOT (MSVC AND BUILD_SHARED_LIBS)) endif () add_fmt_test(ostream-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(ranges-test ranges-odr-test.cc) add_fmt_test(scan-test) diff --git a/test/compile-fp-test.cc b/test/compile-fp-test.cc new file mode 100644 index 00000000..afedc26d --- /dev/null +++ b/test/compile-fp-test.cc @@ -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 struct test_string { + template constexpr bool operator==(const T& rhs) const noexcept { + return fmt::basic_string_view(rhs).compare(buffer) == 0; + } + Char buffer[max_string_length]{}; +}; + +template +consteval auto test_format(auto format, const Args&... args) { + test_string 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::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::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