mirror of
https://github.com/fmtlib/fmt.git
synced 2025-08-03 12:44:49 +02:00
Compute output size for grisu
This commit is contained in:
@@ -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
|
#if FMT_CLANG_VERSION
|
||||||
# define FMT_FALLTHROUGH [[clang::fallthrough]];
|
# define FMT_FALLTHROUGH [[clang::fallthrough]];
|
||||||
#elif FMT_GCC_VERSION >= 700
|
#elif FMT_GCC_VERSION >= 700
|
||||||
@@ -564,61 +541,80 @@ struct gen_digits_params {
|
|||||||
bool fixed;
|
bool fixed;
|
||||||
bool upper;
|
bool upper;
|
||||||
bool trailing_zeros;
|
bool trailing_zeros;
|
||||||
|
};
|
||||||
|
|
||||||
// Creates digit generation parameters from format specifiers for a number in
|
struct prettify_handler {
|
||||||
// the range [pow(10, exp - 1), pow(10, exp) or 0 if exp == 1.
|
char *data;
|
||||||
gen_digits_params(const core_format_specs &specs, int exp)
|
size_t size;
|
||||||
: num_digits(specs.precision >= 0 ? to_unsigned(specs.precision) : 6),
|
buffer &buf;
|
||||||
fixed(false), upper(false), trailing_zeros(false) {
|
|
||||||
switch (specs.type) {
|
explicit prettify_handler(buffer &b, size_t n)
|
||||||
case 'G':
|
: data(b.data()), size(n), buf(b) {}
|
||||||
upper = true;
|
~prettify_handler() {
|
||||||
FMT_FALLTHROUGH
|
assert(buf.size() >= size);
|
||||||
case '\0': case 'g':
|
buf.resize(size);
|
||||||
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':
|
template <typename F>
|
||||||
upper = true;
|
void insert(size_t pos, size_t n, F f) {
|
||||||
FMT_FALLTHROUGH
|
std::memmove(data + pos + n, data + pos, size - pos);
|
||||||
case 'f': {
|
f(data + pos);
|
||||||
fixed = true;
|
size += n;
|
||||||
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;
|
void insert(size_t pos, char c) {
|
||||||
FMT_FALLTHROUGH
|
std::memmove(data + pos + 1, data + pos, size - pos);
|
||||||
case 'e':
|
data[pos] = c;
|
||||||
++num_digits;
|
++size;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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).
|
// Writes the exponent exp in the form "[+-]d{2,3}" to buffer.
|
||||||
FMT_FUNC void format_float(char *buffer, size_t &size, int exp,
|
template <typename Handler>
|
||||||
const gen_digits_params ¶ms) {
|
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 ¶ms,
|
||||||
|
size_t size, int exp, Handler &&handler) {
|
||||||
if (!params.fixed) {
|
if (!params.fixed) {
|
||||||
// Insert a decimal point after the first digit and add an exponent.
|
// Insert a decimal point after the first digit and add an exponent.
|
||||||
std::memmove(buffer + 2, buffer + 1, size - 1);
|
handler.insert(1, '.');
|
||||||
buffer[1] = '.';
|
|
||||||
exp += static_cast<int>(size) - 1;
|
exp += static_cast<int>(size) - 1;
|
||||||
if (size < params.num_digits) {
|
if (size < params.num_digits)
|
||||||
std::uninitialized_fill_n(buffer + size + 1,
|
handler.append(params.num_digits - size, '0');
|
||||||
params.num_digits - size, '0');
|
handler.append(params.upper ? 'E' : 'e');
|
||||||
size = params.num_digits;
|
write_exponent(exp, handler);
|
||||||
}
|
|
||||||
char *p = buffer + size + 1;
|
|
||||||
*p++ = params.upper ? 'E' : 'e';
|
|
||||||
size = to_unsigned(write_exponent(p, exp) - buffer);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// pow(10, full_exp - 1) <= v <= pow(10, full_exp).
|
// 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;
|
const int exp_threshold = 21;
|
||||||
if (int_size <= full_exp && full_exp <= exp_threshold) {
|
if (int_size <= full_exp && full_exp <= exp_threshold) {
|
||||||
// 1234e7 -> 12340000000[.0+]
|
// 1234e7 -> 12340000000[.0+]
|
||||||
std::uninitialized_fill_n(buffer + int_size, full_exp - int_size, '0');
|
handler.append(full_exp - int_size, '0');
|
||||||
char *p = buffer + full_exp;
|
|
||||||
int num_zeros = static_cast<int>(params.num_digits) - full_exp;
|
int num_zeros = static_cast<int>(params.num_digits) - full_exp;
|
||||||
if (num_zeros > 0 && params.trailing_zeros) {
|
if (num_zeros > 0 && params.trailing_zeros) {
|
||||||
*p++ = '.';
|
handler.append('.');
|
||||||
std::uninitialized_fill_n(p, num_zeros, '0');
|
handler.append(num_zeros, '0');
|
||||||
p += num_zeros;
|
|
||||||
}
|
}
|
||||||
size = to_unsigned(p - buffer);
|
|
||||||
} else if (full_exp > 0) {
|
} else if (full_exp > 0) {
|
||||||
// 1234e-2 -> 12.34[0+]
|
// 1234e-2 -> 12.34[0+]
|
||||||
int fractional_size = -exp;
|
handler.insert(full_exp, '.');
|
||||||
std::memmove(buffer + full_exp + 1, buffer + full_exp,
|
|
||||||
to_unsigned(fractional_size));
|
|
||||||
buffer[full_exp] = '.';
|
|
||||||
++size;
|
|
||||||
if (!params.trailing_zeros) {
|
if (!params.trailing_zeros) {
|
||||||
// Remove trailing zeros.
|
// Remove trailing zeros.
|
||||||
while (buffer[size - 1] == '0') --size;
|
handler.remove_trailing('0');
|
||||||
} else if (params.num_digits >= size) {
|
} else if (params.num_digits > size) {
|
||||||
// Add trailing zeros.
|
// Add trailing zeros.
|
||||||
size_t num_zeros = params.num_digits - size + 1;
|
size_t num_zeros = params.num_digits - size;
|
||||||
std::uninitialized_fill_n(buffer + size, num_zeros, '0');
|
handler.append(num_zeros, '0');
|
||||||
size += to_unsigned(num_zeros);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 1234e-6 -> 0.001234
|
// 1234e-6 -> 0.001234
|
||||||
int offset = 2 - full_exp;
|
struct fill {
|
||||||
std::memmove(buffer + offset, buffer, size);
|
size_t n;
|
||||||
|
void operator()(char *buffer) const {
|
||||||
buffer[0] = '0';
|
buffer[0] = '0';
|
||||||
buffer[1] = '.';
|
buffer[1] = '.';
|
||||||
std::uninitialized_fill_n(buffer + 2, -full_exp, '0');
|
std::uninitialized_fill_n(buffer + 2, n, '0');
|
||||||
size = to_unsigned(int_size + offset);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
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>
|
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) {
|
grisu2_format(Double value, buffer &buf, core_format_specs specs) {
|
||||||
FMT_ASSERT(value >= 0, "value is negative");
|
FMT_ASSERT(value >= 0, "value is negative");
|
||||||
char *buffer = buf.data();
|
|
||||||
if (value == 0) {
|
if (value == 0) {
|
||||||
*buffer = '0';
|
gen_digits_params params = process_specs(specs, 1, buf);
|
||||||
size_t size = 1;
|
const size_t size = 1;
|
||||||
format_float(buffer, size, 0, gen_digits_params(specs, 1));
|
buf[0] = '0';
|
||||||
FMT_ASSERT(buf.capacity() >= size, "");
|
grisu2_prettify(params, size, 0, prettify_handler(buf, size));
|
||||||
buf.resize(size);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -694,7 +738,7 @@ FMT_FUNC typename std::enable_if<sizeof(Double) == sizeof(uint64_t), bool>::type
|
|||||||
// hi = floor(upper / one).
|
// hi = floor(upper / one).
|
||||||
uint32_t hi = static_cast<uint32_t>(upper.f >> -one.e);
|
uint32_t hi = static_cast<uint32_t>(upper.f >> -one.e);
|
||||||
int exp = static_cast<int>(count_digits(hi)); // kappa in Grisu.
|
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_value.normalize();
|
||||||
fp scaled_value = fp_value * cached_pow;
|
fp scaled_value = fp_value * cached_pow;
|
||||||
lower = lower * cached_pow; // \tilde{M}^- in Grisu.
|
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.
|
// lo = supper % one.
|
||||||
uint64_t lo = upper.f & (one.f - 1);
|
uint64_t lo = upper.f & (one.f - 1);
|
||||||
size_t size = 0;
|
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)) {
|
params.num_digits)) {
|
||||||
buf.clear();
|
buf.clear();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
format_float(buffer, size, cached_exp + exp, params);
|
grisu2_prettify(params, size, cached_exp + exp, prettify_handler(buf, size));
|
||||||
FMT_ASSERT(buf.capacity() >= size, "");
|
|
||||||
buf.resize(size);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Double>
|
template <typename Double>
|
||||||
void sprintf_format(
|
void sprintf_format(Double value, internal::buffer &buffer,
|
||||||
Double value, internal::buffer &buffer, core_format_specs spec) {
|
core_format_specs spec) {
|
||||||
// Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
|
// Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
|
||||||
FMT_ASSERT(buffer.capacity() != 0, "empty buffer");
|
FMT_ASSERT(buffer.capacity() != 0, "empty buffer");
|
||||||
|
|
||||||
|
@@ -1415,6 +1415,10 @@ TEST(FormatterTest, FormatDouble) {
|
|||||||
EXPECT_EQ(buffer, format("{:A}", -42.0));
|
EXPECT_EQ(buffer, format("{:A}", -42.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(FormatterTest, FormatDoubleBigPrecision) {
|
||||||
|
EXPECT_EQ(format("0.{:0<1000}", ""), format("{:.1000f}", 0.0));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(FormatterTest, FormatNaN) {
|
TEST(FormatterTest, FormatNaN) {
|
||||||
double nan = std::numeric_limits<double>::quiet_NaN();
|
double nan = std::numeric_limits<double>::quiet_NaN();
|
||||||
EXPECT_EQ("nan", format("{}", nan));
|
EXPECT_EQ("nan", format("{}", nan));
|
||||||
|
Reference in New Issue
Block a user