Compute output size for grisu

This commit is contained in:
Victor Zverovich 2018-10-17 20:13:38 -07:00
parent b71d3fe7ab
commit 13d472bd8c
2 changed files with 152 additions and 106 deletions

View File

@ -528,29 +528,6 @@ FMT_FUNC bool grisu2_gen_digits(
}
}
// Writes the exponent exp in the form "[+-]d{2,3}" to buffer.
FMT_FUNC char *write_exponent(char *buffer, int exp) {
FMT_ASSERT(-1000 < exp && exp < 1000, "exponent out of range");
if (exp < 0) {
*buffer++ = '-';
exp = -exp;
} else {
*buffer++ = '+';
}
if (exp >= 100) {
*buffer++ = static_cast<char>('0' + exp / 100);
exp %= 100;
const char *d = data::DIGITS + exp * 2;
*buffer++ = d[0];
*buffer++ = d[1];
} else {
const char *d = data::DIGITS + exp * 2;
*buffer++ = d[0];
*buffer++ = d[1];
}
return buffer;
}
#if FMT_CLANG_VERSION
# define FMT_FALLTHROUGH [[clang::fallthrough]];
#elif FMT_GCC_VERSION >= 700
@ -564,61 +541,80 @@ struct gen_digits_params {
bool fixed;
bool upper;
bool trailing_zeros;
};
// Creates digit generation parameters from format specifiers for a number in
// the range [pow(10, exp - 1), pow(10, exp) or 0 if exp == 1.
gen_digits_params(const core_format_specs &specs, int exp)
: num_digits(specs.precision >= 0 ? to_unsigned(specs.precision) : 6),
fixed(false), upper(false), trailing_zeros(false) {
switch (specs.type) {
case 'G':
upper = true;
FMT_FALLTHROUGH
case '\0': case 'g':
trailing_zeros = (specs.flags & HASH_FLAG) != 0;
if (-4 <= exp && exp < static_cast<int>(num_digits) + 1) {
fixed = true;
if (!specs.type && trailing_zeros && exp >= 0)
num_digits = to_unsigned(exp) + 1;
}
break;
case 'F':
upper = true;
FMT_FALLTHROUGH
case 'f': {
fixed = true;
trailing_zeros = true;
int adjusted_min_digits = static_cast<int>(num_digits) + exp;
if (adjusted_min_digits > 0)
num_digits = to_unsigned(adjusted_min_digits);
break;
}
case 'E':
upper = true;
FMT_FALLTHROUGH
case 'e':
++num_digits;
break;
}
struct prettify_handler {
char *data;
size_t size;
buffer &buf;
explicit prettify_handler(buffer &b, size_t n)
: data(b.data()), size(n), buf(b) {}
~prettify_handler() {
assert(buf.size() >= size);
buf.resize(size);
}
template <typename F>
void insert(size_t pos, size_t n, F f) {
std::memmove(data + pos + n, data + pos, size - pos);
f(data + pos);
size += n;
}
void insert(size_t pos, char c) {
std::memmove(data + pos + 1, data + pos, size - pos);
data[pos] = c;
++size;
}
void append(size_t n, char c) {
std::uninitialized_fill_n(data + size, n, c);
size += n;
}
void append(char c) { data[size++] = c; }
void remove_trailing(char c) {
while (data[size - 1] == c) --size;
}
};
// The number is given as v = buffer * pow(10, exp).
FMT_FUNC void format_float(char *buffer, size_t &size, int exp,
const gen_digits_params &params) {
// Writes the exponent exp in the form "[+-]d{2,3}" to buffer.
template <typename Handler>
FMT_FUNC void write_exponent(int exp, Handler &&h) {
FMT_ASSERT(-1000 < exp && exp < 1000, "exponent out of range");
if (exp < 0) {
h.append('-');
exp = -exp;
} else {
h.append('+');
}
if (exp >= 100) {
h.append(static_cast<char>('0' + exp / 100));
exp %= 100;
const char *d = data::DIGITS + exp * 2;
h.append(d[0]);
h.append(d[1]);
} else {
const char *d = data::DIGITS + exp * 2;
h.append(d[0]);
h.append(d[1]);
}
}
// The number is given as v = f * pow(10, exp), where f has size digits.
template <typename Handler>
FMT_FUNC void grisu2_prettify(const gen_digits_params &params,
size_t size, int exp, Handler &&handler) {
if (!params.fixed) {
// Insert a decimal point after the first digit and add an exponent.
std::memmove(buffer + 2, buffer + 1, size - 1);
buffer[1] = '.';
handler.insert(1, '.');
exp += static_cast<int>(size) - 1;
if (size < params.num_digits) {
std::uninitialized_fill_n(buffer + size + 1,
params.num_digits - size, '0');
size = params.num_digits;
}
char *p = buffer + size + 1;
*p++ = params.upper ? 'E' : 'e';
size = to_unsigned(write_exponent(p, exp) - buffer);
if (size < params.num_digits)
handler.append(params.num_digits - size, '0');
handler.append(params.upper ? 'E' : 'e');
write_exponent(exp, handler);
return;
}
// pow(10, full_exp - 1) <= v <= pow(10, full_exp).
@ -627,53 +623,101 @@ FMT_FUNC void format_float(char *buffer, size_t &size, int exp,
const int exp_threshold = 21;
if (int_size <= full_exp && full_exp <= exp_threshold) {
// 1234e7 -> 12340000000[.0+]
std::uninitialized_fill_n(buffer + int_size, full_exp - int_size, '0');
char *p = buffer + full_exp;
handler.append(full_exp - int_size, '0');
int num_zeros = static_cast<int>(params.num_digits) - full_exp;
if (num_zeros > 0 && params.trailing_zeros) {
*p++ = '.';
std::uninitialized_fill_n(p, num_zeros, '0');
p += num_zeros;
handler.append('.');
handler.append(num_zeros, '0');
}
size = to_unsigned(p - buffer);
} else if (full_exp > 0) {
// 1234e-2 -> 12.34[0+]
int fractional_size = -exp;
std::memmove(buffer + full_exp + 1, buffer + full_exp,
to_unsigned(fractional_size));
buffer[full_exp] = '.';
++size;
handler.insert(full_exp, '.');
if (!params.trailing_zeros) {
// Remove trailing zeros.
while (buffer[size - 1] == '0') --size;
} else if (params.num_digits >= size) {
handler.remove_trailing('0');
} else if (params.num_digits > size) {
// Add trailing zeros.
size_t num_zeros = params.num_digits - size + 1;
std::uninitialized_fill_n(buffer + size, num_zeros, '0');
size += to_unsigned(num_zeros);
size_t num_zeros = params.num_digits - size;
handler.append(num_zeros, '0');
}
} else {
// 1234e-6 -> 0.001234
int offset = 2 - full_exp;
std::memmove(buffer + offset, buffer, size);
buffer[0] = '0';
buffer[1] = '.';
std::uninitialized_fill_n(buffer + 2, -full_exp, '0');
size = to_unsigned(int_size + offset);
struct fill {
size_t n;
void operator()(char *buffer) const {
buffer[0] = '0';
buffer[1] = '.';
std::uninitialized_fill_n(buffer + 2, n, '0');
}
};
handler.insert(0, 2 - full_exp, fill{to_unsigned(-full_exp)});
}
}
struct char_counter {
size_t size;
template <typename F>
void insert(size_t, size_t n, F) { size += n; }
void insert(size_t, char) { ++size; }
void append(size_t n, char) { size += n; }
void append(char) { ++size; }
void remove_trailing(char) {}
};
// Converts format specifiers into parameters for digit generation and computes
// output buffer size for a number in the range [pow(10, exp - 1), pow(10, exp)
// or 0 if exp == 1.
FMT_FUNC gen_digits_params process_specs(const core_format_specs &specs,
int exp, buffer &buf) {
auto params = gen_digits_params();
int num_digits = specs.precision >= 0 ? specs.precision : 6;
switch (specs.type) {
case 'G':
params.upper = true;
FMT_FALLTHROUGH
case '\0': case 'g':
params.trailing_zeros = (specs.flags & HASH_FLAG) != 0;
if (-4 <= exp && exp < num_digits + 1) {
params.fixed = true;
if (!specs.type && params.trailing_zeros && exp >= 0)
num_digits = exp + 1;
}
break;
case 'F':
params.upper = true;
FMT_FALLTHROUGH
case 'f': {
params.fixed = true;
params.trailing_zeros = true;
int adjusted_min_digits = num_digits + exp;
if (adjusted_min_digits > 0)
num_digits = adjusted_min_digits;
break;
}
case 'E':
params.upper = true;
FMT_FALLTHROUGH
case 'e':
++num_digits;
break;
}
params.num_digits = to_unsigned(num_digits);
char_counter counter{params.num_digits};
grisu2_prettify(params, params.num_digits, exp - num_digits, counter);
buf.resize(counter.size);
return params;
}
template <typename Double>
FMT_FUNC typename std::enable_if<sizeof(Double) == sizeof(uint64_t), bool>::type
grisu2_format(Double value, buffer &buf, core_format_specs specs) {
FMT_ASSERT(value >= 0, "value is negative");
char *buffer = buf.data();
if (value == 0) {
*buffer = '0';
size_t size = 1;
format_float(buffer, size, 0, gen_digits_params(specs, 1));
FMT_ASSERT(buf.capacity() >= size, "");
buf.resize(size);
gen_digits_params params = process_specs(specs, 1, buf);
const size_t size = 1;
buf[0] = '0';
grisu2_prettify(params, size, 0, prettify_handler(buf, size));
return true;
}
@ -694,7 +738,7 @@ FMT_FUNC typename std::enable_if<sizeof(Double) == sizeof(uint64_t), bool>::type
// hi = floor(upper / one).
uint32_t hi = static_cast<uint32_t>(upper.f >> -one.e);
int exp = static_cast<int>(count_digits(hi)); // kappa in Grisu.
gen_digits_params params(specs, cached_exp + exp);
gen_digits_params params = process_specs(specs, cached_exp + exp, buf);
fp_value.normalize();
fp scaled_value = fp_value * cached_pow;
lower = lower * cached_pow; // \tilde{M}^- in Grisu.
@ -705,20 +749,18 @@ FMT_FUNC typename std::enable_if<sizeof(Double) == sizeof(uint64_t), bool>::type
// lo = supper % one.
uint64_t lo = upper.f & (one.f - 1);
size_t size = 0;
if (!grisu2_gen_digits(buffer, size, hi, lo, exp, delta, one, diff,
if (!grisu2_gen_digits(buf.data(), size, hi, lo, exp, delta, one, diff,
params.num_digits)) {
buf.clear();
return false;
}
format_float(buffer, size, cached_exp + exp, params);
FMT_ASSERT(buf.capacity() >= size, "");
buf.resize(size);
grisu2_prettify(params, size, cached_exp + exp, prettify_handler(buf, size));
return true;
}
template <typename Double>
void sprintf_format(
Double value, internal::buffer &buffer, core_format_specs spec) {
void sprintf_format(Double value, internal::buffer &buffer,
core_format_specs spec) {
// Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
FMT_ASSERT(buffer.capacity() != 0, "empty buffer");

View File

@ -1415,6 +1415,10 @@ TEST(FormatterTest, FormatDouble) {
EXPECT_EQ(buffer, format("{:A}", -42.0));
}
TEST(FormatterTest, FormatDoubleBigPrecision) {
EXPECT_EQ(format("0.{:0<1000}", ""), format("{:.1000f}", 0.0));
}
TEST(FormatterTest, FormatNaN) {
double nan = std::numeric_limits<double>::quiet_NaN();
EXPECT_EQ("nan", format("{}", nan));