mirror of
https://github.com/fmtlib/fmt.git
synced 2025-06-26 09:51:40 +02:00
Partially implement Grisu3
This commit is contained in:
@ -494,9 +494,11 @@ enum result {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generates output using Grisu2 digit-gen algorithm.
|
// 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).
|
||||||
template <typename Handler>
|
template <typename Handler>
|
||||||
digits::result grisu2_gen_digits(fp value, uint64_t error, int& exp,
|
digits::result grisu_gen_digits(fp value, uint64_t error, int& exp,
|
||||||
Handler& handler) {
|
Handler& handler) {
|
||||||
fp one(1ull << -value.e, value.e);
|
fp one(1ull << -value.e, value.e);
|
||||||
// The integral part of scaled value (p1 in Grisu) = value / one. It cannot be
|
// The integral part of scaled value (p1 in Grisu) = value / one. It cannot be
|
||||||
@ -635,34 +637,57 @@ struct fixed_handler {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// The shortest representation digit handler.
|
// The shortest representation digit handler.
|
||||||
struct shortest_handler {
|
template <int GRISU_VERSION> struct grisu_shortest_handler {
|
||||||
char* buf;
|
char* buf;
|
||||||
int size;
|
int size;
|
||||||
fp diff; // wp_w in Grisu.
|
// Distance between scaled value and upper bound (wp_W in Grisu3).
|
||||||
|
uint64_t diff;
|
||||||
|
|
||||||
digits::result on_start(uint64_t, uint64_t, uint64_t, int&) {
|
digits::result on_start(uint64_t, uint64_t, uint64_t, int&) {
|
||||||
return digits::more;
|
return digits::more;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This implements Grisu3's round_weed.
|
||||||
digits::result on_digit(char digit, uint64_t divisor, uint64_t remainder,
|
digits::result on_digit(char digit, uint64_t divisor, uint64_t remainder,
|
||||||
uint64_t error, int exp, bool integral) {
|
uint64_t error, int exp, bool integral) {
|
||||||
buf[size++] = digit;
|
buf[size++] = digit;
|
||||||
if (remainder > error) return digits::more;
|
if (remainder >= error) return digits::more;
|
||||||
uint64_t d = integral ? diff.f : diff.f * data::POWERS_OF_10_64[-exp];
|
if (GRISU_VERSION != 3) {
|
||||||
while (
|
uint64_t d = integral ? diff : diff * data::POWERS_OF_10_64[-exp];
|
||||||
remainder < d && error - remainder >= divisor &&
|
while (remainder < d && error - remainder >= divisor &&
|
||||||
(remainder + divisor < d || d - remainder > remainder + divisor - d)) {
|
(remainder + divisor < d ||
|
||||||
|
d - remainder > remainder + divisor - d)) {
|
||||||
--buf[size - 1];
|
--buf[size - 1];
|
||||||
remainder += divisor;
|
remainder += divisor;
|
||||||
}
|
}
|
||||||
return digits::done;
|
return digits::done;
|
||||||
}
|
}
|
||||||
|
uint64_t unit = integral ? 1 : data::POWERS_OF_10_64[-exp];
|
||||||
|
uint64_t up = diff + unit; // wp_Wup
|
||||||
|
while (remainder < up && error - remainder >= divisor &&
|
||||||
|
(remainder + divisor < up ||
|
||||||
|
up - remainder > remainder + divisor - up)) {
|
||||||
|
--buf[size - 1];
|
||||||
|
remainder += divisor;
|
||||||
|
}
|
||||||
|
uint64_t down = diff - unit; // wp_Wdown
|
||||||
|
if (remainder < down && error - remainder >= divisor &&
|
||||||
|
(remainder + divisor < down ||
|
||||||
|
down - remainder > remainder + divisor - down)) {
|
||||||
|
return digits::error;
|
||||||
|
}
|
||||||
|
return 2 * unit <= remainder && remainder <= error - 4 * unit
|
||||||
|
? digits::done
|
||||||
|
: digits::error;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Double, typename std::enable_if<
|
template <typename Double, typename std::enable_if<
|
||||||
sizeof(Double) == sizeof(uint64_t), int>::type>
|
sizeof(Double) == sizeof(uint64_t), int>::type>
|
||||||
FMT_FUNC bool grisu2_format(Double value, buffer<char>& buf, int precision,
|
FMT_FUNC bool grisu_format(Double value, buffer<char>& buf, int precision,
|
||||||
bool fixed, int& exp) {
|
unsigned options, int& exp) {
|
||||||
FMT_ASSERT(value >= 0, "value is negative");
|
FMT_ASSERT(value >= 0, "value is negative");
|
||||||
|
bool fixed = (options & grisu_options::fixed) != 0;
|
||||||
if (value <= 0) { // <= instead of == to silence a warning.
|
if (value <= 0) { // <= instead of == to silence a warning.
|
||||||
if (precision < 0 || !fixed) {
|
if (precision < 0 || !fixed) {
|
||||||
exp = 0;
|
exp = 0;
|
||||||
@ -685,7 +710,7 @@ FMT_FUNC bool grisu2_format(Double value, buffer<char>& buf, int precision,
|
|||||||
min_exp - (fp_value.e + fp::significand_size), cached_exp10);
|
min_exp - (fp_value.e + fp::significand_size), cached_exp10);
|
||||||
fp_value = fp_value * cached_pow;
|
fp_value = fp_value * cached_pow;
|
||||||
fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
|
fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
|
||||||
if (grisu2_gen_digits(fp_value, 1, exp, handler) == digits::error)
|
if (grisu_gen_digits(fp_value, 1, exp, handler) == digits::error)
|
||||||
return false;
|
return false;
|
||||||
buf.resize(to_unsigned(handler.size));
|
buf.resize(to_unsigned(handler.size));
|
||||||
} else {
|
} else {
|
||||||
@ -695,17 +720,29 @@ FMT_FUNC bool grisu2_format(Double value, buffer<char>& buf, int precision,
|
|||||||
// the exponent in the range [min_exp, -32].
|
// the exponent in the range [min_exp, -32].
|
||||||
auto cached_pow = get_cached_power( // \tilde{c}_{-k} in Grisu.
|
auto cached_pow = get_cached_power( // \tilde{c}_{-k} in Grisu.
|
||||||
min_exp - (upper.e + fp::significand_size), cached_exp10);
|
min_exp - (upper.e + fp::significand_size), cached_exp10);
|
||||||
upper = upper * cached_pow; // \tilde{M}^+ in Grisu.
|
|
||||||
--upper.f; // \tilde{M}^+ - 1 ulp -> M^+_{\downarrow}.
|
|
||||||
assert(min_exp <= upper.e && upper.e <= -32);
|
|
||||||
fp_value.normalize();
|
fp_value.normalize();
|
||||||
fp_value = fp_value * cached_pow;
|
fp_value = fp_value * cached_pow;
|
||||||
lower = lower * cached_pow; // \tilde{M}^- in Grisu.
|
lower = lower * cached_pow; // \tilde{M}^- in Grisu.
|
||||||
|
upper = upper * cached_pow; // \tilde{M}^+ in Grisu.
|
||||||
|
assert(min_exp <= upper.e && upper.e <= -32);
|
||||||
|
auto result = digits::result();
|
||||||
|
int size = 0;
|
||||||
|
if ((options & grisu_options::grisu3) != 0) {
|
||||||
|
--lower.f; // \tilde{M}^- - 1 ulp -> M^-_{\downarrow}.
|
||||||
|
++upper.f; // \tilde{M}^+ + 1 ulp -> M^+_{\uparrow}.
|
||||||
|
// Numbers outside of (lower, upper) definitely do not round to value.
|
||||||
|
grisu_shortest_handler<3> handler{buf.data(), 0, (upper - fp_value).f};
|
||||||
|
result = grisu_gen_digits(upper, upper.f - lower.f, exp, handler);
|
||||||
|
size = handler.size;
|
||||||
|
} else {
|
||||||
++lower.f; // \tilde{M}^- + 1 ulp -> M^-_{\uparrow}.
|
++lower.f; // \tilde{M}^- + 1 ulp -> M^-_{\uparrow}.
|
||||||
shortest_handler handler{buf.data(), 0, upper - fp_value};
|
--upper.f; // \tilde{M}^+ - 1 ulp -> M^+_{\downarrow}.
|
||||||
auto result = grisu2_gen_digits(upper, upper.f - lower.f, exp, handler);
|
grisu_shortest_handler<2> handler{buf.data(), 0, (upper - fp_value).f};
|
||||||
|
result = grisu_gen_digits(upper, upper.f - lower.f, exp, handler);
|
||||||
|
size = handler.size;
|
||||||
|
}
|
||||||
if (result == digits::error) return false;
|
if (result == digits::error) return false;
|
||||||
buf.resize(to_unsigned(handler.size));
|
buf.resize(to_unsigned(size));
|
||||||
}
|
}
|
||||||
exp -= cached_exp10;
|
exp -= cached_exp10;
|
||||||
return true;
|
return true;
|
||||||
|
@ -1124,13 +1124,19 @@ FMT_CONSTEXPR unsigned basic_parse_context<Char, ErrorHandler>::next_arg_id() {
|
|||||||
|
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
// Formats value using Grisu2 algorithm:
|
namespace grisu_options {
|
||||||
|
enum {
|
||||||
|
fixed = 1,
|
||||||
|
grisu3 = 2
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formats value using the Grisu algorithm:
|
||||||
// https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf
|
// https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf
|
||||||
template <typename Double, FMT_ENABLE_IF(sizeof(Double) == sizeof(uint64_t))>
|
template <typename Double, FMT_ENABLE_IF(sizeof(Double) == sizeof(uint64_t))>
|
||||||
FMT_API bool grisu2_format(Double value, buffer<char>& buf, int precision,
|
FMT_API bool grisu_format(Double, buffer<char>&, int, unsigned, int&);
|
||||||
bool fixed, int& exp);
|
|
||||||
template <typename Double, FMT_ENABLE_IF(sizeof(Double) != sizeof(uint64_t))>
|
template <typename Double, FMT_ENABLE_IF(sizeof(Double) != sizeof(uint64_t))>
|
||||||
inline bool grisu2_format(Double, buffer<char>&, int, bool, int&) {
|
inline bool grisu_format(Double, buffer<char>&, int, unsigned, int&) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1166,7 +1172,7 @@ template <typename Char, typename It> It write_exponent(int exp, It it) {
|
|||||||
|
|
||||||
// The number is given as v = digits * pow(10, exp).
|
// The number is given as v = digits * pow(10, exp).
|
||||||
template <typename Char, typename It>
|
template <typename Char, typename It>
|
||||||
It grisu2_prettify(const char* digits, int size, int exp, It it,
|
It grisu_prettify(const char* digits, int size, int exp, It it,
|
||||||
gen_digits_params params) {
|
gen_digits_params params) {
|
||||||
// pow(10, full_exp - 1) <= v <= pow(10, full_exp).
|
// pow(10, full_exp - 1) <= v <= pow(10, full_exp).
|
||||||
int full_exp = size + exp;
|
int full_exp = size + exp;
|
||||||
@ -2663,7 +2669,7 @@ template <typename Range> class basic_writer {
|
|||||||
int full_exp = num_digits + exp - 1;
|
int full_exp = num_digits + exp - 1;
|
||||||
int precision = params.num_digits > 0 ? params.num_digits : 11;
|
int precision = params.num_digits > 0 ? params.num_digits : 11;
|
||||||
params_.fixed |= full_exp >= -4 && full_exp < precision;
|
params_.fixed |= full_exp >= -4 && full_exp < precision;
|
||||||
auto it = internal::grisu2_prettify<char>(
|
auto it = internal::grisu_prettify<char>(
|
||||||
digits.data(), num_digits, exp, internal::counting_iterator<char>(),
|
digits.data(), num_digits, exp, internal::counting_iterator<char>(),
|
||||||
params_);
|
params_);
|
||||||
size_ = it.count();
|
size_ = it.count();
|
||||||
@ -2675,8 +2681,8 @@ template <typename Range> class basic_writer {
|
|||||||
template <typename It> void operator()(It&& it) {
|
template <typename It> void operator()(It&& it) {
|
||||||
if (sign_) *it++ = static_cast<char_type>(sign_);
|
if (sign_) *it++ = static_cast<char_type>(sign_);
|
||||||
int num_digits = static_cast<int>(digits_.size());
|
int num_digits = static_cast<int>(digits_.size());
|
||||||
it = internal::grisu2_prettify<char_type>(digits_.data(), num_digits,
|
it = internal::grisu_prettify<char_type>(digits_.data(), num_digits, exp_,
|
||||||
exp_, it, params_);
|
it, params_);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2876,11 +2882,12 @@ void basic_writer<Range>::write_double(T value, const format_specs& spec) {
|
|||||||
memory_buffer buffer;
|
memory_buffer buffer;
|
||||||
int exp = 0;
|
int exp = 0;
|
||||||
int precision = spec.has_precision() || !spec.type ? spec.precision : 6;
|
int precision = spec.has_precision() || !spec.type ? spec.precision : 6;
|
||||||
|
unsigned options = handler.fixed ? internal::grisu_options::fixed : 0;
|
||||||
bool use_grisu = fmt::internal::use_grisu<T>() &&
|
bool use_grisu = fmt::internal::use_grisu<T>() &&
|
||||||
(spec.type != 'a' && spec.type != 'A' && spec.type != 'e' &&
|
(spec.type != 'a' && spec.type != 'A' && spec.type != 'e' &&
|
||||||
spec.type != 'E') &&
|
spec.type != 'E') &&
|
||||||
internal::grisu2_format(static_cast<double>(value), buffer,
|
internal::grisu_format(static_cast<double>(value), buffer,
|
||||||
precision, handler.fixed, exp);
|
precision, options, exp);
|
||||||
if (!use_grisu) internal::sprintf_format(value, buffer, spec);
|
if (!use_grisu) internal::sprintf_format(value, buffer, spec);
|
||||||
|
|
||||||
if (handler.as_percentage) {
|
if (handler.as_percentage) {
|
||||||
|
@ -10,9 +10,9 @@
|
|||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
template struct internal::basic_data<void>;
|
template struct internal::basic_data<void>;
|
||||||
|
|
||||||
// Workaround a bug in MSVC2013 that prevents instantiation of grisu2_format.
|
// Workaround a bug in MSVC2013 that prevents instantiation of grisu_format.
|
||||||
bool (*instantiate_grisu2_format)(double, internal::buffer<char>&, int, bool,
|
bool (*instantiate_grisu_format)(double, internal::buffer<char>&, int, unsigned,
|
||||||
int&) = internal::grisu2_format;
|
int&) = internal::grisu_format;
|
||||||
|
|
||||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||||
template FMT_API internal::locale_ref::locale_ref(const std::locale& loc);
|
template FMT_API internal::locale_ref::locale_ref(const std::locale& loc);
|
||||||
|
@ -137,10 +137,10 @@ TEST(FPTest, FixedHandler) {
|
|||||||
digits::error);
|
digits::error);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FPTest, Grisu2FormatCompilesWithNonIEEEDouble) {
|
TEST(FPTest, GrisuFormatCompilesWithNonIEEEDouble) {
|
||||||
fmt::memory_buffer buf;
|
fmt::memory_buffer buf;
|
||||||
int exp = 0;
|
int exp = 0;
|
||||||
grisu2_format(4.2f, buf, -1, false, exp);
|
grisu_format(4.2f, buf, -1, false, exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> struct ValueExtractor : fmt::internal::function<T> {
|
template <typename T> struct ValueExtractor : fmt::internal::function<T> {
|
||||||
|
Reference in New Issue
Block a user