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
6 changed files with 278 additions and 109 deletions

View File

@@ -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<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.
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");
if (exp < 0) {
*it++ = static_cast<Char>('-');
@@ -1268,16 +1274,19 @@ auto write_exponent(int exp, It it) -> It {
}
template <typename T>
auto format_float(T value, int precision, float_specs specs, buffer<char>& buf)
-> int;
FMT_HEADER_ONLY_CONSTEXPR20 auto format_float(T value, int precision,
float_specs specs,
buffer<char>& buf) -> int;
// Formats a floating-point number with snprintf.
template <typename T>
auto snprintf_float(T value, int precision, float_specs specs,
buffer<char>& buf) -> int;
template <typename T> auto promote_float(T value) -> T { return value; }
inline auto promote_float(float value) -> double {
template <typename T> constexpr auto promote_float(T value) -> T {
return value;
}
constexpr auto promote_float(float value) -> double {
return static_cast<double>(value);
}
@@ -1649,8 +1658,9 @@ FMT_CONSTEXPR auto write(OutputIt out, const Char* s,
}
template <typename Char, typename OutputIt>
auto write_nonfinite(OutputIt out, bool isinf, basic_format_specs<Char> specs,
const float_specs& fspecs) -> OutputIt {
FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isinf,
basic_format_specs<Char> 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 <typename T>
@@ -1682,8 +1692,8 @@ inline auto get_significand_size(const dragonbox::decimal_fp<T>& fp) -> int {
}
template <typename Char, typename OutputIt>
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<Char>(significand, significand + significand_size, out);
}
template <typename Char, typename OutputIt, typename UInt>
@@ -1691,11 +1701,10 @@ inline auto write_significand(OutputIt out, UInt significand,
int significand_size) -> OutputIt {
return format_decimal<Char>(out, significand, significand_size).end;
}
template <typename Char, typename OutputIt, typename T>
inline auto write_significand(OutputIt out, T significand, int significand_size,
int exponent,
const digit_grouping<Char>& grouping)
-> OutputIt {
template <typename Char, typename OutputIt, typename T, typename Grouping>
FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand,
int significand_size, int exponent,
const Grouping& grouping) -> OutputIt {
if (!grouping.separator()) {
out = write_significand<Char>(out, significand, significand_size);
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>
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<Char>(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 <typename OutputIt, typename Char, typename T>
inline auto write_significand(OutputIt out, T significand, int significand_size,
int integral_size, Char decimal_point,
const digit_grouping<Char>& grouping)
-> OutputIt {
template <typename OutputIt, typename Char, typename T, typename Grouping>
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 <typename OutputIt, typename DecimalFP, typename Char>
auto write_float(OutputIt out, const DecimalFP& fp,
const basic_format_specs<Char>& specs, float_specs fspecs,
locale_ref loc) -> OutputIt {
template <typename OutputIt, typename DecimalFP, typename Char,
typename Grouping = digit_grouping<Char>>
FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& fp,
const basic_format_specs<Char>& 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<Char>('0');
constexpr Char zero = static_cast<Char>('0');
auto sign = fspecs.sign;
size_t size = to_unsigned(significand_size) + (sign ? 1 : 0);
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) 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));
return write_padded<align::right>(out, specs, size, [&](iterator it) {
if (sign) *it++ = detail::sign<Char>(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<Char>(loc, fspecs.locale);
auto grouping = Grouping(loc, fspecs.locale);
size += to_unsigned(grouping.count_separators(significand_size));
return write_padded<align::right>(out, specs, size, [&](iterator it) {
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,
FMT_ENABLE_IF(std::is_floating_point<T>::value)>
auto write(OutputIt out, T value, basic_format_specs<Char> specs,
locale_ref loc = {}) -> OutputIt {
FMT_CONSTEXPR20 auto write(OutputIt out, T value,
basic_format_specs<Char> 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<Char> specs,
template <typename Char, typename OutputIt, typename T,
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;
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 fspecs = float_specs();
auto sign_bit = bits & (uint(1) << (num_bits<uint>() - 1));
if (sign_bit != 0) {
if (detail::signbit(value)) {
fspecs.sign = sign::minus;
value = -value;
}
static const auto specs = basic_format_specs<Char>();
constexpr auto specs = basic_format_specs<Char>();
uint mask = exponent_mask<floaty>();
if ((bits & mask) == mask)
return write_nonfinite(out, std::isinf(value), specs, fspecs);