mirror of
https://github.com/fmtlib/fmt.git
synced 2025-07-30 10:47:35 +02:00
Use dragon in constexpr
This commit is contained in:
@ -1817,6 +1817,7 @@ constexpr uint32_t basic_data<T>::fractional_part_rounding_thresholds[];
|
|||||||
// This is a struct rather than an alias to avoid shadowing warnings in gcc.
|
// This is a struct rather than an alias to avoid shadowing warnings in gcc.
|
||||||
struct data : basic_data<> {};
|
struct data : basic_data<> {};
|
||||||
|
|
||||||
|
// DEPRECATED!
|
||||||
// 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`.
|
||||||
FMT_CONSTEXPR inline fp get_cached_power(int min_exponent,
|
FMT_CONSTEXPR inline fp get_cached_power(int min_exponent,
|
||||||
@ -2830,78 +2831,6 @@ FMT_INLINE FMT_CONSTEXPR bool signbit(T value) {
|
|||||||
return std::signbit(static_cast<double>(value));
|
return std::signbit(static_cast<double>(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class round_direction { unknown, up, down };
|
|
||||||
|
|
||||||
// Given the divisor (normally a power of 10), the remainder = v % divisor for
|
|
||||||
// some number v and the error, returns whether v should be rounded up, down, or
|
|
||||||
// whether the rounding direction can't be determined due to error.
|
|
||||||
// error should be less than divisor / 2.
|
|
||||||
FMT_CONSTEXPR inline round_direction get_round_direction(uint64_t divisor,
|
|
||||||
uint64_t remainder,
|
|
||||||
uint64_t error) {
|
|
||||||
FMT_ASSERT(remainder < divisor, ""); // divisor - remainder won't overflow.
|
|
||||||
FMT_ASSERT(error < divisor, ""); // divisor - error won't overflow.
|
|
||||||
FMT_ASSERT(error < divisor - error, ""); // error * 2 won't overflow.
|
|
||||||
// Round down if (remainder + error) * 2 <= divisor.
|
|
||||||
if (remainder <= divisor - remainder && error * 2 <= divisor - remainder * 2)
|
|
||||||
return round_direction::down;
|
|
||||||
// Round up if (remainder - error) * 2 >= divisor.
|
|
||||||
if (remainder >= error &&
|
|
||||||
remainder - error >= divisor - (remainder - error)) {
|
|
||||||
return round_direction::up;
|
|
||||||
}
|
|
||||||
return round_direction::unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace digits {
|
|
||||||
enum result {
|
|
||||||
more, // Generate more digits.
|
|
||||||
done, // Done generating digits.
|
|
||||||
error // Digit generation cancelled due to an error.
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
struct gen_digits_handler {
|
|
||||||
char* buf;
|
|
||||||
int size;
|
|
||||||
int precision;
|
|
||||||
int exp10;
|
|
||||||
bool fixed;
|
|
||||||
|
|
||||||
FMT_CONSTEXPR digits::result on_digit(char digit, uint64_t divisor,
|
|
||||||
uint64_t remainder, uint64_t error,
|
|
||||||
bool integral) {
|
|
||||||
FMT_ASSERT(remainder < divisor, "");
|
|
||||||
buf[size++] = digit;
|
|
||||||
if (!integral && error >= remainder) return digits::error;
|
|
||||||
if (size < precision) return digits::more;
|
|
||||||
if (!integral) {
|
|
||||||
// Check if error * 2 < divisor with overflow prevention.
|
|
||||||
// The check is not needed for the integral part because error = 1
|
|
||||||
// and divisor > (1 << 32) there.
|
|
||||||
if (error >= divisor || error >= divisor - error) return digits::error;
|
|
||||||
} else {
|
|
||||||
FMT_ASSERT(error == 1 && divisor > 2, "");
|
|
||||||
}
|
|
||||||
auto dir = get_round_direction(divisor, remainder, error);
|
|
||||||
if (dir != round_direction::up)
|
|
||||||
return dir == round_direction::down ? digits::done : digits::error;
|
|
||||||
++buf[size - 1];
|
|
||||||
for (int i = size - 1; i > 0 && buf[i] > '9'; --i) {
|
|
||||||
buf[i] = '0';
|
|
||||||
++buf[i - 1];
|
|
||||||
}
|
|
||||||
if (buf[0] > '9') {
|
|
||||||
buf[0] = '1';
|
|
||||||
if (fixed)
|
|
||||||
buf[size++] = '0';
|
|
||||||
else
|
|
||||||
++exp10;
|
|
||||||
}
|
|
||||||
return digits::done;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
inline FMT_CONSTEXPR20 void adjust_precision(int& precision, int exp10) {
|
inline FMT_CONSTEXPR20 void adjust_precision(int& precision, int exp10) {
|
||||||
// Adjust fixed precision by exponent because it is relative to decimal
|
// Adjust fixed precision by exponent because it is relative to decimal
|
||||||
// point.
|
// point.
|
||||||
@ -2910,101 +2839,6 @@ inline FMT_CONSTEXPR20 void adjust_precision(int& precision, int exp10) {
|
|||||||
precision += exp10;
|
precision += exp10;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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).
|
|
||||||
FMT_INLINE FMT_CONSTEXPR20 auto grisu_gen_digits(fp value, uint64_t error,
|
|
||||||
int& exp,
|
|
||||||
gen_digits_handler& handler)
|
|
||||||
-> digits::result {
|
|
||||||
const fp one(1ULL << -value.e, value.e);
|
|
||||||
// The integral part of scaled value (p1 in Grisu) = value / one. It cannot be
|
|
||||||
// zero because it contains a product of two 64-bit numbers with MSB set (due
|
|
||||||
// to normalization) - 1, shifted right by at most 60 bits.
|
|
||||||
auto integral = static_cast<uint32_t>(value.f >> -one.e);
|
|
||||||
FMT_ASSERT(integral != 0, "");
|
|
||||||
FMT_ASSERT(integral == value.f >> -one.e, "");
|
|
||||||
// The fractional part of scaled value (p2 in Grisu) c = value % one.
|
|
||||||
uint64_t fractional = value.f & (one.f - 1);
|
|
||||||
exp = count_digits(integral); // kappa in Grisu.
|
|
||||||
// Non-fixed formats require at least one digit and no precision adjustment.
|
|
||||||
if (handler.fixed) {
|
|
||||||
adjust_precision(handler.precision, exp + handler.exp10);
|
|
||||||
// Check if precision is satisfied just by leading zeros, e.g.
|
|
||||||
// format("{:.2f}", 0.001) gives "0.00" without generating any digits.
|
|
||||||
if (handler.precision <= 0) {
|
|
||||||
if (handler.precision < 0) return digits::done;
|
|
||||||
// Divide by 10 to prevent overflow.
|
|
||||||
uint64_t divisor = data::power_of_10_64[exp - 1] << -one.e;
|
|
||||||
auto dir = get_round_direction(divisor, value.f / 10, error * 10);
|
|
||||||
if (dir == round_direction::unknown) return digits::error;
|
|
||||||
handler.buf[handler.size++] = dir == round_direction::up ? '1' : '0';
|
|
||||||
return digits::done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Generate digits for the integral part. This can produce up to 10 digits.
|
|
||||||
do {
|
|
||||||
uint32_t digit = 0;
|
|
||||||
auto divmod_integral = [&](uint32_t divisor) {
|
|
||||||
digit = integral / divisor;
|
|
||||||
integral %= divisor;
|
|
||||||
};
|
|
||||||
// This optimization by Milo Yip reduces the number of integer divisions by
|
|
||||||
// one per iteration.
|
|
||||||
switch (exp) {
|
|
||||||
case 10:
|
|
||||||
divmod_integral(1000000000);
|
|
||||||
break;
|
|
||||||
case 9:
|
|
||||||
divmod_integral(100000000);
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
divmod_integral(10000000);
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
divmod_integral(1000000);
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
divmod_integral(100000);
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
divmod_integral(10000);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
divmod_integral(1000);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
divmod_integral(100);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
divmod_integral(10);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
digit = integral;
|
|
||||||
integral = 0;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
FMT_ASSERT(false, "invalid number of digits");
|
|
||||||
}
|
|
||||||
--exp;
|
|
||||||
auto remainder = (static_cast<uint64_t>(integral) << -one.e) + fractional;
|
|
||||||
auto result = handler.on_digit(static_cast<char>('0' + digit),
|
|
||||||
data::power_of_10_64[exp] << -one.e,
|
|
||||||
remainder, error, true);
|
|
||||||
if (result != digits::more) return result;
|
|
||||||
} while (exp > 0);
|
|
||||||
// Generate digits for the fractional part.
|
|
||||||
for (;;) {
|
|
||||||
fractional *= 10;
|
|
||||||
error *= 10;
|
|
||||||
char digit = static_cast<char>('0' + (fractional >> -one.e));
|
|
||||||
fractional &= one.f - 1;
|
|
||||||
--exp;
|
|
||||||
auto result = handler.on_digit(digit, one.f, fractional, error, false);
|
|
||||||
if (result != digits::more) return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class bigint {
|
class bigint {
|
||||||
private:
|
private:
|
||||||
// A bigint is stored as an array of bigits (big digits), with bigit at index
|
// A bigint is stored as an array of bigits (big digits), with bigit at index
|
||||||
@ -3505,7 +3339,7 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs,
|
|||||||
int exp = 0;
|
int exp = 0;
|
||||||
bool use_dragon = true;
|
bool use_dragon = true;
|
||||||
unsigned dragon_flags = 0;
|
unsigned dragon_flags = 0;
|
||||||
if (!is_fast_float<Float>()) {
|
if (!is_fast_float<Float>() || is_constant_evaluated()) {
|
||||||
const auto inv_log2_10 = 0.3010299956639812; // 1 / log2(10)
|
const auto inv_log2_10 = 0.3010299956639812; // 1 / log2(10)
|
||||||
using info = dragonbox::float_info<decltype(converted_value)>;
|
using info = dragonbox::float_info<decltype(converted_value)>;
|
||||||
const auto f = basic_fp<typename info::carrier_uint>(converted_value);
|
const auto f = basic_fp<typename info::carrier_uint>(converted_value);
|
||||||
@ -3516,7 +3350,7 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs,
|
|||||||
exp = static_cast<int>(
|
exp = static_cast<int>(
|
||||||
std::ceil((f.e + count_digits<1>(f.f) - 1) * inv_log2_10 - 1e-10));
|
std::ceil((f.e + count_digits<1>(f.f) - 1) * inv_log2_10 - 1e-10));
|
||||||
dragon_flags = dragon::fixup;
|
dragon_flags = dragon::fixup;
|
||||||
} else if (!is_constant_evaluated() && precision < 0) {
|
} else if (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));
|
||||||
@ -3526,25 +3360,6 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs,
|
|||||||
auto dec = dragonbox::to_decimal(static_cast<double>(value));
|
auto dec = dragonbox::to_decimal(static_cast<double>(value));
|
||||||
write<char>(buffer_appender<char>(buf), dec.significand);
|
write<char>(buffer_appender<char>(buf), dec.significand);
|
||||||
return dec.exponent;
|
return dec.exponent;
|
||||||
} else if (is_constant_evaluated()) {
|
|
||||||
// Use Grisu + Dragon4 for the given precision:
|
|
||||||
// https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf.
|
|
||||||
const int min_exp = -60; // alpha in Grisu.
|
|
||||||
int cached_exp10 = 0; // K in Grisu.
|
|
||||||
fp normalized = normalize(fp(converted_value));
|
|
||||||
const auto cached_pow = get_cached_power(
|
|
||||||
min_exp - (normalized.e + fp::num_significand_bits), cached_exp10);
|
|
||||||
normalized = normalized * cached_pow;
|
|
||||||
gen_digits_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
|
|
||||||
if (grisu_gen_digits(normalized, 1, exp, handler) != digits::error &&
|
|
||||||
!is_constant_evaluated()) {
|
|
||||||
exp += handler.exp10;
|
|
||||||
buf.try_resize(to_unsigned(handler.size));
|
|
||||||
use_dragon = false;
|
|
||||||
} else {
|
|
||||||
exp += handler.size - cached_exp10 - 1;
|
|
||||||
precision = handler.precision;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Extract significand bits and exponent bits.
|
// Extract significand bits and exponent bits.
|
||||||
using info = dragonbox::float_info<double>;
|
using info = dragonbox::float_info<double>;
|
||||||
|
@ -10,8 +10,9 @@
|
|||||||
|
|
||||||
#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806 && \
|
#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806 && \
|
||||||
defined(__cpp_constexpr) && __cpp_constexpr >= 201907 && \
|
defined(__cpp_constexpr) && __cpp_constexpr >= 201907 && \
|
||||||
defined(__cpp_constexpr_dynamic_alloc) && \
|
defined(__cpp_constexpr_dynamic_alloc) && !FMT_MSC_VERSION && \
|
||||||
__cpp_constexpr_dynamic_alloc >= 201907 && FMT_CPLUSPLUS >= 202002L
|
__cpp_constexpr_dynamic_alloc >= 201907 && FMT_CPLUSPLUS >= 202002L
|
||||||
|
|
||||||
template <size_t max_string_length, typename Char = char> struct test_string {
|
template <size_t max_string_length, typename Char = char> struct test_string {
|
||||||
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
|
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
|
||||||
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
|
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
|
||||||
|
@ -227,14 +227,12 @@ TEST(compile_test, format_to_n) {
|
|||||||
EXPECT_STREQ("2a", buffer);
|
EXPECT_STREQ("2a", buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __cpp_lib_bit_cast
|
# ifdef __cpp_lib_bit_cast
|
||||||
TEST(compile_test, constexpr_formatted_size) {
|
TEST(compile_test, constexpr_formatted_size) {
|
||||||
FMT_CONSTEXPR20 size_t s1 = fmt::formatted_size(FMT_COMPILE("{0}"), 42);
|
FMT_CONSTEXPR20 size_t size = fmt::formatted_size(FMT_COMPILE("{}"), 42);
|
||||||
EXPECT_EQ(2, s1);
|
EXPECT_EQ(size, 2);
|
||||||
FMT_CONSTEXPR20 size_t s2 = fmt::formatted_size(FMT_COMPILE("{0:<4.2f}"), 42.0);
|
|
||||||
EXPECT_EQ(5, s2);
|
|
||||||
}
|
}
|
||||||
#endif
|
# endif
|
||||||
|
|
||||||
TEST(compile_test, text_and_arg) {
|
TEST(compile_test, text_and_arg) {
|
||||||
EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42));
|
EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42));
|
||||||
|
@ -249,49 +249,6 @@ TEST(fp_test, dragonbox_max_k) {
|
|||||||
2 * fmt::detail::num_significand_bits<double>() - 1));
|
2 * fmt::detail::num_significand_bits<double>() - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(fp_test, get_round_direction) {
|
|
||||||
using fmt::detail::get_round_direction;
|
|
||||||
using fmt::detail::round_direction;
|
|
||||||
EXPECT_EQ(get_round_direction(100, 50, 0), round_direction::down);
|
|
||||||
EXPECT_EQ(get_round_direction(100, 51, 0), round_direction::up);
|
|
||||||
EXPECT_EQ(get_round_direction(100, 40, 10), round_direction::down);
|
|
||||||
EXPECT_EQ(get_round_direction(100, 60, 10), round_direction::up);
|
|
||||||
for (size_t i = 41; i < 60; ++i)
|
|
||||||
EXPECT_EQ(get_round_direction(100, i, 10), round_direction::unknown);
|
|
||||||
uint64_t max = max_value<uint64_t>();
|
|
||||||
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, 50), assertion_failure);
|
|
||||||
// Check that remainder + error doesn't overflow.
|
|
||||||
EXPECT_EQ(get_round_direction(max, max - 1, 2), round_direction::up);
|
|
||||||
// Check that 2 * (remainder + error) doesn't overflow.
|
|
||||||
EXPECT_EQ(get_round_direction(max, max / 2 + 1, max / 2),
|
|
||||||
round_direction::unknown);
|
|
||||||
// Check that remainder - error doesn't overflow.
|
|
||||||
EXPECT_EQ(get_round_direction(100, 40, 41), round_direction::unknown);
|
|
||||||
// Check that 2 * (remainder - error) doesn't overflow.
|
|
||||||
EXPECT_EQ(get_round_direction(max, max - 1, 1), round_direction::up);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(fp_test, fixed_handler) {
|
|
||||||
struct handler : fmt::detail::gen_digits_handler {
|
|
||||||
char buffer[10];
|
|
||||||
handler(int prec = 0) : fmt::detail::gen_digits_handler() {
|
|
||||||
buf = buffer;
|
|
||||||
precision = prec;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
handler().on_digit('0', 100, 99, 0, false);
|
|
||||||
EXPECT_THROW(handler().on_digit('0', 100, 100, 0, false), assertion_failure);
|
|
||||||
namespace digits = fmt::detail::digits;
|
|
||||||
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, false), digits::error);
|
|
||||||
// Check that divisor - error doesn't overflow.
|
|
||||||
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, false), digits::error);
|
|
||||||
// Check that 2 * error doesn't overflow.
|
|
||||||
uint64_t max = max_value<uint64_t>();
|
|
||||||
EXPECT_EQ(handler(1).on_digit('0', max, 10, max - 1, false), digits::error);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(fp_test, grisu_format_compiles_with_on_ieee_double) {
|
TEST(fp_test, grisu_format_compiles_with_on_ieee_double) {
|
||||||
auto buf = fmt::memory_buffer();
|
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);
|
||||||
|
Reference in New Issue
Block a user