Implement fixed precision
This commit is contained in:
parent
187bd1b8b2
commit
8af651be39
@ -445,31 +445,21 @@ FMT_FUNC fp get_cached_power(int min_exponent, int& pow10_exponent) {
|
|||||||
return fp(data::POW10_SIGNIFICANDS[index], data::POW10_EXPONENTS[index]);
|
return fp(data::POW10_SIGNIFICANDS[index], data::POW10_EXPONENTS[index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_FUNC bool grisu2_round(char* buf, int& size, uint64_t delta,
|
|
||||||
uint64_t remainder, uint64_t exp, uint64_t diff) {
|
|
||||||
while (
|
|
||||||
remainder < diff && delta - remainder >= exp &&
|
|
||||||
(remainder + exp < diff || diff - remainder > remainder + exp - diff)) {
|
|
||||||
--buf[size - 1];
|
|
||||||
remainder += exp;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generates output using Grisu2 digit-gen algorithm.
|
// Generates output using Grisu2 digit-gen algorithm.
|
||||||
template <typename Stop>
|
template <typename Stop>
|
||||||
int grisu2_gen_digits(char* buf, fp upper, uint64_t error_ulp, int& exp,
|
int grisu2_gen_digits(char* buf, fp value, uint64_t error_ulp, int& exp,
|
||||||
Stop stop) {
|
Stop stop) {
|
||||||
fp one(1ull << -upper.e, upper.e);
|
fp one(1ull << -value.e, value.e);
|
||||||
// The integral part of scaled upper (p1 in Grisu) = upper / one. It cannot be
|
// 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
|
// 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.
|
// to normalization) - 1, shifted right by at most 60 bits.
|
||||||
uint32_t integral = static_cast<uint32_t>(upper.f >> -one.e);
|
uint32_t integral = static_cast<uint32_t>(value.f >> -one.e);
|
||||||
FMT_ASSERT(integral != 0, "");
|
FMT_ASSERT(integral != 0, "");
|
||||||
FMT_ASSERT(integral == upper.f >> -one.e, "");
|
FMT_ASSERT(integral == value.f >> -one.e, "");
|
||||||
// The fractional part of scaled upper (p2 in Grisu) c = upper % one.
|
// The fractional part of scaled value (p2 in Grisu) c = value % one.
|
||||||
uint64_t fractional = upper.f & (one.f - 1);
|
uint64_t fractional = value.f & (one.f - 1);
|
||||||
exp = count_digits(integral); // kappa in Grisu.
|
exp = count_digits(integral); // kappa in Grisu.
|
||||||
|
stop.on_exp(exp);
|
||||||
int size = 0;
|
int size = 0;
|
||||||
// Generate digits for the integral part. This can produce up to 10 digits.
|
// Generate digits for the integral part. This can produce up to 10 digits.
|
||||||
do {
|
do {
|
||||||
@ -524,7 +514,9 @@ int grisu2_gen_digits(char* buf, fp upper, uint64_t error_ulp, int& exp,
|
|||||||
--exp;
|
--exp;
|
||||||
uint64_t remainder =
|
uint64_t remainder =
|
||||||
(static_cast<uint64_t>(integral) << -one.e) + fractional;
|
(static_cast<uint64_t>(integral) << -one.e) + fractional;
|
||||||
if (stop(buf, size, remainder, one, error_ulp, exp, true)) return size;
|
if (stop(buf, size, remainder, data::POWERS_OF_10_64[exp] << -one.e,
|
||||||
|
error_ulp, exp, true))
|
||||||
|
return size;
|
||||||
} while (exp > 0);
|
} while (exp > 0);
|
||||||
// Generate digits for the fractional part.
|
// Generate digits for the fractional part.
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@ -534,21 +526,36 @@ int grisu2_gen_digits(char* buf, fp upper, uint64_t error_ulp, int& exp,
|
|||||||
buf[size++] = static_cast<char>('0' + digit);
|
buf[size++] = static_cast<char>('0' + digit);
|
||||||
fractional &= one.f - 1;
|
fractional &= one.f - 1;
|
||||||
--exp;
|
--exp;
|
||||||
if (stop(buf, size, fractional, one, error_ulp, exp, false)) return size;
|
if (stop(buf, size, fractional, one.f, error_ulp, exp, false)) return size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stopping condition for the fixed precision.
|
// Stopping condition for the fixed precision.
|
||||||
struct fixed_stop {
|
struct fixed_stop {
|
||||||
int precision;
|
int precision;
|
||||||
|
int exp10;
|
||||||
|
|
||||||
bool operator()(char* buf, int size, uint64_t remainder, fp,
|
void on_exp(int exp) { precision += exp + exp10; }
|
||||||
uint64_t error_ulp, int&, bool) {
|
|
||||||
|
bool operator()(char*, int& size, uint64_t remainder, uint64_t divisor,
|
||||||
|
uint64_t error, int&, bool integral) {
|
||||||
|
assert(remainder < divisor);
|
||||||
if (size != precision) return false;
|
if (size != precision) return false;
|
||||||
// TODO: pass correct arguments to round
|
if (!integral) {
|
||||||
if (!grisu2_round(buf, size, error_ulp, remainder, 0, 0)) {
|
// Check if error * 2 < divisor with overflow prevention.
|
||||||
size = -1;
|
// The check is not needed for the integral part because error = 1
|
||||||
}
|
// and divisor > (1 << 32) there.
|
||||||
|
if (error >= divisor || error >= divisor - error) {
|
||||||
|
size = -1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
assert(error == 1 && divisor > 2);
|
||||||
|
// Round down if (remainder + error) * 2 <= divisor.
|
||||||
|
if (remainder < divisor - remainder && error * 2 <= divisor - remainder * 2)
|
||||||
|
return true;
|
||||||
|
// TODO: round up
|
||||||
|
size = -1;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -557,14 +564,17 @@ struct fixed_stop {
|
|||||||
struct shortest_stop {
|
struct shortest_stop {
|
||||||
fp diff; // wp_w in Grisu.
|
fp diff; // wp_w in Grisu.
|
||||||
|
|
||||||
bool operator()(char* buf, int size, uint64_t remainder, fp one,
|
void on_exp(int) {}
|
||||||
uint64_t error_ulp, int& exp, bool integral) {
|
|
||||||
if (remainder > error_ulp) return false;
|
bool operator()(char* buf, int& size, uint64_t remainder, uint64_t divisor,
|
||||||
if (!grisu2_round(
|
uint64_t error, int& exp, bool integral) {
|
||||||
buf, size, error_ulp, remainder,
|
if (remainder > error) return false;
|
||||||
integral ? data::POWERS_OF_10_64[exp] << -one.e : one.f,
|
uint64_t d = integral ? diff.f : diff.f * data::POWERS_OF_10_64[-exp];
|
||||||
integral ? diff.f : diff.f * data::POWERS_OF_10_64[-exp])) {
|
while (
|
||||||
size = -1;
|
remainder < d && error - remainder >= divisor &&
|
||||||
|
(remainder + divisor < d || d - remainder > remainder + divisor - d)) {
|
||||||
|
--buf[size - 1];
|
||||||
|
remainder += divisor;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -572,25 +582,31 @@ struct shortest_stop {
|
|||||||
|
|
||||||
template <typename Double>
|
template <typename Double>
|
||||||
FMT_FUNC typename std::enable_if<sizeof(Double) == sizeof(uint64_t), bool>::type
|
FMT_FUNC typename std::enable_if<sizeof(Double) == sizeof(uint64_t), bool>::type
|
||||||
grisu2_format(Double value, buffer& buf, core_format_specs specs, int& exp) {
|
grisu2_format(Double value, buffer& buf, int precision, int& exp) {
|
||||||
FMT_ASSERT(value >= 0, "value is negative");
|
FMT_ASSERT(value >= 0, "value is negative");
|
||||||
if (value <= 0) { // <= instead of == to silence a warning.
|
if (value <= 0) { // <= instead of == to silence a warning.
|
||||||
buf.push_back('0');
|
if (precision < 0) {
|
||||||
exp = 0;
|
exp = 0;
|
||||||
|
buf.push_back('0');
|
||||||
|
} else {
|
||||||
|
exp = -precision;
|
||||||
|
buf.resize(precision);
|
||||||
|
std::uninitialized_fill_n(buf.data(), precision, '0');
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fp fp_value(value);
|
fp fp_value(value);
|
||||||
const int min_exp = -60; // alpha in Grisu.
|
const int min_exp = -60; // alpha in Grisu.
|
||||||
int cached_exp10 = 0; // K in Grisu.
|
int cached_exp10 = 0; // K in Grisu.
|
||||||
if (specs.precision != -1) {
|
if (precision != -1) {
|
||||||
if (specs.precision > 17) return false;
|
if (precision > 17) return false;
|
||||||
fp_value.normalize();
|
fp_value.normalize();
|
||||||
auto cached_pow = get_cached_power(
|
auto cached_pow = get_cached_power(
|
||||||
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;
|
||||||
int size = grisu2_gen_digits(buf.data(), fp_value, 1, exp,
|
int size = grisu2_gen_digits(buf.data(), fp_value, 1, exp,
|
||||||
fixed_stop{specs.precision});
|
fixed_stop{precision, -cached_exp10});
|
||||||
if (size < 0) return false;
|
if (size < 0) return false;
|
||||||
buf.resize(to_unsigned(size));
|
buf.resize(to_unsigned(size));
|
||||||
} else {
|
} else {
|
||||||
|
@ -1135,10 +1135,10 @@ namespace internal {
|
|||||||
// 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>
|
template <typename Double>
|
||||||
FMT_API typename std::enable_if<sizeof(Double) == sizeof(uint64_t), bool>::type
|
FMT_API typename std::enable_if<sizeof(Double) == sizeof(uint64_t), bool>::type
|
||||||
grisu2_format(Double value, buffer& buf, core_format_specs, int& exp);
|
grisu2_format(Double value, buffer& buf, int precision, int& exp);
|
||||||
template <typename Double>
|
template <typename Double>
|
||||||
inline typename std::enable_if<sizeof(Double) != sizeof(uint64_t), bool>::type
|
inline typename std::enable_if<sizeof(Double) != sizeof(uint64_t), bool>::type
|
||||||
grisu2_format(Double, buffer&, core_format_specs, int&) {
|
grisu2_format(Double, buffer&, int, int&) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2801,8 +2801,9 @@ template <typename Range> class basic_writer {
|
|||||||
struct float_spec_handler {
|
struct float_spec_handler {
|
||||||
char type;
|
char type;
|
||||||
bool upper;
|
bool upper;
|
||||||
|
bool fixed;
|
||||||
|
|
||||||
explicit float_spec_handler(char t) : type(t), upper(false) {}
|
explicit float_spec_handler(char t) : type(t), upper(false), fixed(false) {}
|
||||||
|
|
||||||
void on_general() {
|
void on_general() {
|
||||||
if (type == 'G') upper = true;
|
if (type == 'G') upper = true;
|
||||||
@ -2813,6 +2814,7 @@ struct float_spec_handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void on_fixed() {
|
void on_fixed() {
|
||||||
|
fixed = true;
|
||||||
if (type == 'F') upper = true;
|
if (type == 'F') upper = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2858,9 +2860,11 @@ void basic_writer<Range>::write_double(T value, const format_specs& spec) {
|
|||||||
|
|
||||||
memory_buffer buffer;
|
memory_buffer buffer;
|
||||||
int exp = 0;
|
int exp = 0;
|
||||||
bool use_grisu =
|
int precision = spec.has_precision() || !spec.type ? spec.precision : 6;
|
||||||
fmt::internal::use_grisu<T>() && !spec.type && !spec.has_precision() &&
|
bool use_grisu = fmt::internal::use_grisu<T>() &&
|
||||||
internal::grisu2_format(static_cast<double>(value), buffer, spec, exp);
|
(!spec.type || handler.fixed) && !spec.has_precision() &&
|
||||||
|
internal::grisu2_format(static_cast<double>(value), buffer,
|
||||||
|
precision, exp);
|
||||||
if (!use_grisu) internal::sprintf_format(value, buffer, spec);
|
if (!use_grisu) internal::sprintf_format(value, buffer, spec);
|
||||||
align_spec as = spec;
|
align_spec as = spec;
|
||||||
if (spec.align() == ALIGN_NUMERIC) {
|
if (spec.align() == ALIGN_NUMERIC) {
|
||||||
|
@ -11,7 +11,7 @@ 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 grisu2_format.
|
||||||
bool (*instantiate_grisu2_format)(double, internal::buffer&, core_format_specs,
|
bool (*instantiate_grisu2_format)(double, internal::buffer&, int,
|
||||||
int&) = internal::grisu2_format;
|
int&) = internal::grisu2_format;
|
||||||
|
|
||||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||||
|
@ -103,7 +103,7 @@ TEST(FPTest, GetCachedPower) {
|
|||||||
TEST(FPTest, Grisu2FormatCompilesWithNonIEEEDouble) {
|
TEST(FPTest, Grisu2FormatCompilesWithNonIEEEDouble) {
|
||||||
fmt::memory_buffer buf;
|
fmt::memory_buffer buf;
|
||||||
int exp = 0;
|
int exp = 0;
|
||||||
grisu2_format(4.2f, buf, fmt::core_format_specs(), exp);
|
grisu2_format(4.2f, buf, -1, exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> struct ValueExtractor : fmt::internal::function<T> {
|
template <typename T> struct ValueExtractor : fmt::internal::function<T> {
|
||||||
|
Loading…
Reference in New Issue
Block a user