Skip to content

Commit

Permalink
make FP formatting available to be used at compile-time
Browse files Browse the repository at this point in the history
* works only with FMT_HEADER_ONLY
* works only with float and double types (not long double)
  • Loading branch information
alexezeder committed Aug 28, 2021
1 parent 8ef22f7 commit ff0cf70
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 92 deletions.
4 changes: 2 additions & 2 deletions include/fmt/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -1180,8 +1180,8 @@ template <typename Context> 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) {}
Expand Down
146 changes: 82 additions & 64 deletions include/fmt/format-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,61 @@ template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) {
return i >= 0 ? i * char_digits + count_digits<4, unsigned>(n.value[i]) : 1;
}

template <typename T = void> struct basic_inl_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,
};

// 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};

static constexpr uint64_t power_of_10_64[20] = {
1, FMT_POWERS_OF_10(1), FMT_POWERS_OF_10(1000000000ULL),
10000000000000000000ULL};
};

// This is a struct rather than an alias to avoid shadowing warnings in gcc.
struct inl_data : basic_inl_data<> {};

#if __cplusplus < 201703L
template <typename T> constexpr const char basic_data<T>::digits[][2];
template <typename T> constexpr const char basic_data<T>::hex_digits[];
Expand All @@ -153,6 +208,11 @@ template <typename T> constexpr const unsigned basic_data<T>::prefixes[];
template <typename T> constexpr const char basic_data<T>::left_padding_shifts[];
template <typename T>
constexpr const char basic_data<T>::right_padding_shifts[];

template <typename T>
constexpr uint64_t basic_inl_data<T>::pow10_significands[];
template <typename T> constexpr int16_t basic_inl_data<T>::pow10_exponents[];
template <typename T> constexpr uint64_t basic_inl_data<T>::power_of_10_64[];
#endif

template <typename T> struct bits {
Expand Down Expand Up @@ -278,53 +338,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<int64_t>(data::log10_2_significand);
int index = static_cast<int>(
Expand All @@ -338,7 +353,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 {inl_data::pow10_significands[index],
inl_data::pow10_exponents[index]};
}

// A simple accumulator to hold the sums of terms in bigint::square if uint128_t
Expand Down Expand Up @@ -639,13 +655,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).
Expand All @@ -665,7 +674,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(inl_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.
Expand Down Expand Up @@ -715,8 +724,8 @@ FMT_INLINE FMT_CONSTEXPR digits::result grisu_gen_digits(fp value,
--exp;
auto remainder = (static_cast<uint64_t>(integral) << -one.e) + fractional;
result = handler.on_digit(static_cast<char>('0' + digit),
power_of_10_64(exp) << -one.e, remainder, error,
exp, true);
inl_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.
Expand Down Expand Up @@ -2230,8 +2239,8 @@ template <typename T> decimal_fp<T> to_decimal(T x) FMT_NOEXCEPT {
// Floating-Point Printout ((FPP)^2) algorithm by Steele & White:
// https://fmt.dev/papers/p372-steele.pdf.
template <typename Double>
void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf,
int& exp10) {
FMT_CONSTEXPR20 void fallback_format(Double d, int num_digits, bool binary32,
buffer<char>& 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.
Expand Down Expand Up @@ -2347,7 +2356,11 @@ void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf,
}

template <typename T>
int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
#ifdef FMT_HEADER_ONLY
FMT_CONSTEXPR20
#endif
int
format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
static_assert(!std::is_same<T, float>::value, "");
FMT_ASSERT(value >= 0, "value is negative");

Expand All @@ -2358,13 +2371,17 @@ int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
return 0;
}
buf.try_resize(to_unsigned(precision));
std::uninitialized_fill_n(buf.data(), precision, '0');
if (is_constant_evaluated()) {
fill_n(buf.data(), precision, '0');
} else {
std::uninitialized_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<float>(value));
Expand All @@ -2390,7 +2407,8 @@ int format_float(T value, int precision, float_specs specs, buffer<char>& 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 {
Expand Down
Loading

0 comments on commit ff0cf70

Please sign in to comment.