mirror of
https://github.com/fmtlib/fmt.git
synced 2024-12-02 14:30:10 +00:00
Refactor digit generation
This commit is contained in:
parent
b1f7cca89e
commit
4c66dad8c1
@ -467,10 +467,18 @@ inline round_direction get_round_direction(uint64_t divisor, uint64_t remainder,
|
|||||||
return unknown;
|
return unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace digits {
|
||||||
|
enum result {
|
||||||
|
more, // Generate more digits.
|
||||||
|
done, // Done generating digits.
|
||||||
|
error // Digit generation cancelled due to an error.
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Generates output using Grisu2 digit-gen algorithm.
|
// Generates output using Grisu2 digit-gen algorithm.
|
||||||
template <typename Handler>
|
template <typename Handler>
|
||||||
int grisu2_gen_digits(char* buf, fp value, uint64_t error, int& exp,
|
digits::result grisu2_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
|
||||||
// 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
|
||||||
@ -481,11 +489,9 @@ int grisu2_gen_digits(char* buf, fp value, uint64_t error, int& exp,
|
|||||||
// The fractional part of scaled value (p2 in Grisu) c = value % one.
|
// The fractional part of scaled value (p2 in Grisu) c = value % one.
|
||||||
uint64_t fractional = value.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.
|
||||||
int size = 0;
|
auto result = handler.on_start(data::POWERS_OF_10_64[exp] << -one.e, value.f,
|
||||||
if (handler.on_start(buf, size, data::POWERS_OF_10_64[exp] << -one.e, value.f,
|
error, exp);
|
||||||
error, exp)) {
|
if (result != digits::more) return result;
|
||||||
return size;
|
|
||||||
}
|
|
||||||
// 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 {
|
||||||
uint32_t digit = 0;
|
uint32_t digit = 0;
|
||||||
@ -535,29 +541,31 @@ int grisu2_gen_digits(char* buf, fp value, uint64_t error, int& exp,
|
|||||||
default:
|
default:
|
||||||
FMT_ASSERT(false, "invalid number of digits");
|
FMT_ASSERT(false, "invalid number of digits");
|
||||||
}
|
}
|
||||||
buf[size++] = static_cast<char>('0' + digit);
|
|
||||||
--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 (handler(buf, size, data::POWERS_OF_10_64[exp] << -one.e, remainder,
|
auto result = handler.on_digit(static_cast<char>('0' + digit),
|
||||||
error, exp, true)) {
|
data::POWERS_OF_10_64[exp] << -one.e, remainder,
|
||||||
return size;
|
error, exp, true);
|
||||||
}
|
if (result != digits::more) return result;
|
||||||
} while (exp > 0);
|
} while (exp > 0);
|
||||||
// Generate digits for the fractional part.
|
// Generate digits for the fractional part.
|
||||||
for (;;) {
|
for (;;) {
|
||||||
fractional *= 10;
|
fractional *= 10;
|
||||||
error *= 10;
|
error *= 10;
|
||||||
char digit = static_cast<char>(fractional >> -one.e);
|
char digit =
|
||||||
buf[size++] = static_cast<char>('0' + digit);
|
static_cast<char>('0' + static_cast<char>(fractional >> -one.e));
|
||||||
fractional &= one.f - 1;
|
fractional &= one.f - 1;
|
||||||
--exp;
|
--exp;
|
||||||
if (handler(buf, size, one.f, fractional, error, exp, false)) return size;
|
auto result = handler.on_digit(digit, one.f, fractional, error, exp, false);
|
||||||
|
if (result != digits::more) return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The fixed precision digit handler.
|
// The fixed precision digit handler.
|
||||||
struct fixed_handler {
|
struct fixed_handler {
|
||||||
|
char* buf;
|
||||||
|
int size;
|
||||||
int precision;
|
int precision;
|
||||||
int exp10;
|
int exp10;
|
||||||
bool fixed;
|
bool fixed;
|
||||||
@ -566,74 +574,61 @@ struct fixed_handler {
|
|||||||
return full_exp <= 0 && -full_exp >= precision;
|
return full_exp <= 0 && -full_exp >= precision;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool on_start(char* buf, int& size, uint64_t divisor, uint64_t remainder,
|
digits::result on_start(uint64_t divisor, uint64_t remainder, uint64_t error,
|
||||||
uint64_t error, int& exp) {
|
int& exp) {
|
||||||
if (!fixed) return false;
|
if (!fixed) return digits::more;
|
||||||
int full_exp = exp + exp10;
|
int full_exp = exp + exp10;
|
||||||
if (full_exp >= 0) precision += full_exp;
|
if (full_exp >= 0) precision += full_exp;
|
||||||
if (!enough_precision(full_exp)) return false;
|
if (!enough_precision(full_exp)) return digits::more;
|
||||||
switch (get_round_direction(divisor, remainder, error)) {
|
auto dir = get_round_direction(divisor, remainder, error);
|
||||||
case unknown:
|
if (dir == up) buf[size++] = '1';
|
||||||
size = -1;
|
return dir != unknown ? digits::done : digits::error;
|
||||||
break;
|
|
||||||
case up:
|
|
||||||
buf[size++] = '1';
|
|
||||||
break;
|
|
||||||
case down:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test
|
// TODO: test
|
||||||
bool operator()(char* buf, int& size, 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) const {
|
uint64_t error, int& exp, bool integral) {
|
||||||
assert(remainder < divisor);
|
assert(remainder < divisor);
|
||||||
if (size != precision && !enough_precision(exp + exp10)) return false;
|
buf[size++] = digit;
|
||||||
|
if (size != precision && !enough_precision(exp + exp10))
|
||||||
|
return digits::more;
|
||||||
if (!integral) {
|
if (!integral) {
|
||||||
// Check if error * 2 < divisor with overflow prevention.
|
// Check if error * 2 < divisor with overflow prevention.
|
||||||
// The check is not needed for the integral part because error = 1
|
// The check is not needed for the integral part because error = 1
|
||||||
// and divisor > (1 << 32) there.
|
// and divisor > (1 << 32) there.
|
||||||
if (error >= divisor || error >= divisor - error) {
|
if (error >= divisor || error >= divisor - error)
|
||||||
size = -1;
|
return digits::error;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
assert(error == 1 && divisor > 2);
|
assert(error == 1 && divisor > 2);
|
||||||
}
|
}
|
||||||
switch (get_round_direction(divisor, remainder, error)) {
|
auto dir = get_round_direction(divisor, remainder, error);
|
||||||
case unknown:
|
if (dir != up) return dir == down ? digits::done : digits::error;
|
||||||
size = -1;
|
++buf[size - 1];
|
||||||
break;
|
for (int i = size - 1; i > 0 && buf[i] > '9'; --i) {
|
||||||
case up:
|
buf[i] = '0';
|
||||||
++buf[size - 1];
|
++buf[i - 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';
|
|
||||||
++exp;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case down:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return true;
|
if (buf[0] > '9') {
|
||||||
|
buf[0] = '1';
|
||||||
|
++exp;
|
||||||
|
}
|
||||||
|
return digits::done;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// The shortest representation digit handler.
|
// The shortest representation digit handler.
|
||||||
struct shortest_handler {
|
struct shortest_handler {
|
||||||
|
char* buf;
|
||||||
|
int size;
|
||||||
fp diff; // wp_w in Grisu.
|
fp diff; // wp_w in Grisu.
|
||||||
|
|
||||||
bool on_start(char*, int&, uint64_t, uint64_t, uint64_t, int&) {
|
digits::result on_start(uint64_t, uint64_t, uint64_t, int&) {
|
||||||
return false;
|
return digits::more;
|
||||||
}
|
}
|
||||||
|
digits::result on_digit(char digit, uint64_t divisor, uint64_t remainder,
|
||||||
bool operator()(char* buf, int& size, uint64_t divisor, uint64_t remainder,
|
uint64_t error, int exp, bool integral) {
|
||||||
uint64_t error, int& exp, bool integral) {
|
buf[size++] = digit;
|
||||||
if (remainder > error) return false;
|
if (remainder > error) return digits::more;
|
||||||
uint64_t d = integral ? diff.f : diff.f * data::POWERS_OF_10_64[-exp];
|
uint64_t d = integral ? diff.f : diff.f * data::POWERS_OF_10_64[-exp];
|
||||||
while (
|
while (
|
||||||
remainder < d && error - remainder >= divisor &&
|
remainder < d && error - remainder >= divisor &&
|
||||||
@ -641,7 +636,7 @@ struct shortest_handler {
|
|||||||
--buf[size - 1];
|
--buf[size - 1];
|
||||||
remainder += divisor;
|
remainder += divisor;
|
||||||
}
|
}
|
||||||
return true;
|
return digits::done;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -670,11 +665,10 @@ grisu2_format(Double value, buffer& buf, int precision, bool fixed, int& exp) {
|
|||||||
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 =
|
fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
|
||||||
grisu2_gen_digits(buf.data(), fp_value, 1, exp,
|
if (grisu2_gen_digits(fp_value, 1, exp, handler) == digits::error)
|
||||||
fixed_handler{precision, -cached_exp10, fixed});
|
return false;
|
||||||
if (size < 0) return false;
|
buf.resize(to_unsigned(handler.size));
|
||||||
buf.resize(to_unsigned(size));
|
|
||||||
} else {
|
} else {
|
||||||
fp lower, upper; // w^- and w^+ in the Grisu paper.
|
fp lower, upper; // w^- and w^+ in the Grisu paper.
|
||||||
fp_value.compute_boundaries(lower, upper);
|
fp_value.compute_boundaries(lower, upper);
|
||||||
@ -689,10 +683,10 @@ grisu2_format(Double value, buffer& buf, int precision, bool fixed, int& exp) {
|
|||||||
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.
|
||||||
++lower.f; // \tilde{M}^- + 1 ulp -> M^-_{\uparrow}.
|
++lower.f; // \tilde{M}^- + 1 ulp -> M^-_{\uparrow}.
|
||||||
int size = grisu2_gen_digits(buf.data(), upper, upper.f - lower.f, exp,
|
shortest_handler handler{buf.data(), 0, upper - fp_value};
|
||||||
shortest_handler{upper - fp_value});
|
auto result = grisu2_gen_digits(upper, upper.f - lower.f, exp, handler);
|
||||||
if (size < 0) return false;
|
if (result == digits::error) return false;
|
||||||
buf.resize(to_unsigned(size));
|
buf.resize(to_unsigned(handler.size));
|
||||||
}
|
}
|
||||||
exp -= cached_exp10;
|
exp -= cached_exp10;
|
||||||
return true;
|
return true;
|
||||||
|
Loading…
Reference in New Issue
Block a user