Refactor digit generation

This commit is contained in:
Victor Zverovich 2019-03-10 15:07:46 -07:00
parent b1f7cca89e
commit 4c66dad8c1

View File

@ -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;