Refactor float spec parsing

This commit is contained in:
Victor Zverovich 2019-11-18 05:10:11 -08:00
parent ed117baa4f
commit 56a2e2075c
2 changed files with 55 additions and 96 deletions

View File

@ -70,6 +70,12 @@
# define FMT_HAS_BUILTIN(x) 0 # define FMT_HAS_BUILTIN(x) 0
#endif #endif
#if FMT_HAS_CPP_ATTRIBUTE(fallthrough) >= 201603 && __cplusplus >= 201703
# define FMT_FALLTHROUGH [[fallthrough]]
#else
# define FMT_FALLTHROUGH
#endif
#ifndef FMT_THROW #ifndef FMT_THROW
# if FMT_EXCEPTIONS # if FMT_EXCEPTIONS
# if FMT_MSC_VER # if FMT_MSC_VER
@ -1053,9 +1059,18 @@ namespace internal {
// A floating-point presentation format. // A floating-point presentation format.
enum class float_format { enum class float_format {
general, // General: shortest of exponential and fixed. shortest, // Shortest round-trip format.
exp, // Exponential: 1e42 general, // General: exponent notation or fixed point based on magnitude.
fixed // Fixed: 1.42 exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3.
fixed, // Fixed point with the default precision of 6, e.g. 0.0012.
hex
};
struct float_spec {
float_format format;
bool upper;
bool locale;
bool percent;
}; };
struct gen_digits_params { struct gen_digits_params {
@ -1244,36 +1259,48 @@ FMT_CONSTEXPR void handle_int_type_spec(char spec, Handler&& handler) {
} }
} }
template <typename Handler> template <typename ErrorHandler = error_handler>
FMT_CONSTEXPR void handle_float_type_spec(char spec, Handler&& handler) { FMT_CONSTEXPR float_spec parse_float_type_spec(char spec,
ErrorHandler&& eh = {}) {
auto result = float_spec();
switch (spec) { switch (spec) {
case 'G':
result.upper = true;
FMT_FALLTHROUGH;
case 0: case 0:
case 'g': case 'g':
case 'G': result.format = float_format::general;
handler.on_general();
break; break;
case 'e':
case 'E': case 'E':
handler.on_exp(); result.upper = true;
FMT_FALLTHROUGH;
case 'e':
result.format = float_format::exp;
break; break;
case 'f':
case 'F': case 'F':
handler.on_fixed(); result.upper = true;
FMT_FALLTHROUGH;
case 'f':
result.format = float_format::fixed;
break; break;
case '%': case '%':
handler.on_percent(); result.format = float_format::fixed;
result.percent = true;
break; break;
case 'a':
case 'A': case 'A':
handler.on_hex(); result.upper = true;
FMT_FALLTHROUGH;
case 'a':
result.format = float_format::hex;
break; break;
case 'n': case 'n':
handler.on_num(); result.locale = true;
break; break;
default: default:
handler.on_error(); eh.on_error("invalid type specifier");
break; break;
} }
return result;
} }
template <typename Char, typename Handler> template <typename Char, typename Handler>
@ -1321,68 +1348,6 @@ template <typename ErrorHandler> class int_type_checker : private ErrorHandler {
} }
}; };
template <typename ErrorHandler>
class float_type_checker : private ErrorHandler {
public:
FMT_CONSTEXPR explicit float_type_checker(ErrorHandler eh)
: ErrorHandler(eh) {}
FMT_CONSTEXPR void on_general() {}
FMT_CONSTEXPR void on_exp() {}
FMT_CONSTEXPR void on_fixed() {}
FMT_CONSTEXPR void on_percent() {}
FMT_CONSTEXPR void on_hex() {}
FMT_CONSTEXPR void on_num() {}
FMT_CONSTEXPR void on_error() {
ErrorHandler::on_error("invalid type specifier");
}
};
struct float_spec_handler {
char type;
bool upper;
float_format format;
bool as_percentage;
bool use_locale;
explicit float_spec_handler(char t)
: type(t),
upper(false),
format(float_format::general),
as_percentage(false),
use_locale(false) {}
void on_general() {
if (type == 'G') upper = true;
}
void on_exp() {
format = float_format::exp;
if (type == 'E') upper = true;
}
void on_fixed() {
format = float_format::fixed;
if (type == 'F') upper = true;
}
void on_percent() {
format = float_format::fixed;
as_percentage = true;
}
void on_hex() {
if (type == 'A') upper = true;
}
void on_num() { use_locale = true; }
FMT_NORETURN void on_error() {
FMT_THROW(format_error("invalid type specifier"));
}
};
template <typename ErrorHandler> template <typename ErrorHandler>
class char_specs_checker : public ErrorHandler { class char_specs_checker : public ErrorHandler {
private: private:
@ -2835,10 +2800,6 @@ template <typename Range>
template <typename T, bool USE_GRISU> template <typename T, bool USE_GRISU>
void internal::basic_writer<Range>::write_fp(T value, void internal::basic_writer<Range>::write_fp(T value,
const format_specs& specs) { const format_specs& specs) {
// Check type.
float_spec_handler handler(specs.type);
handle_float_type_spec(handler.type, handler);
auto sign = specs.sign; auto sign = specs.sign;
// Use signbit instead of value < 0 since the latter is always false for NaN. // Use signbit instead of value < 0 since the latter is always false for NaN.
if (std::signbit(value)) { if (std::signbit(value)) {
@ -2848,20 +2809,20 @@ void internal::basic_writer<Range>::write_fp(T value,
sign = sign::none; sign = sign::none;
} }
float_spec fspec = parse_float_type_spec(specs.type);
if (!std::isfinite(value)) { if (!std::isfinite(value)) {
const char* str = std::isinf(value) ? (handler.upper ? "INF" : "inf") const char* str = std::isinf(value) ? (fspec.upper ? "INF" : "inf")
: (handler.upper ? "NAN" : "nan"); : (fspec.upper ? "NAN" : "nan");
return write_padded(specs, return write_padded(specs, inf_or_nan_writer{sign, fspec.percent, str});
inf_or_nan_writer{sign, handler.as_percentage, str});
} }
if (handler.as_percentage) value *= 100; if (fspec.percent) value *= 100;
memory_buffer buffer; memory_buffer buffer;
int exp = 0; int exp = 0;
int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6; int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6;
unsigned options = 0; unsigned options = 0;
if (handler.format == float_format::fixed) options |= grisu_options::fixed; if (fspec.format == float_format::fixed) options |= grisu_options::fixed;
if (const_check(sizeof(value) == sizeof(float))) if (const_check(sizeof(value) == sizeof(float)))
options |= grisu_options::binary32; options |= grisu_options::binary32;
bool use_grisu = bool use_grisu =
@ -2872,7 +2833,7 @@ void internal::basic_writer<Range>::write_fp(T value,
char* decimal_point_pos = nullptr; char* decimal_point_pos = nullptr;
if (!use_grisu) decimal_point_pos = sprintf_format(value, buffer, specs); if (!use_grisu) decimal_point_pos = sprintf_format(value, buffer, specs);
if (handler.as_percentage) { if (fspec.percent) {
buffer.push_back('%'); buffer.push_back('%');
--exp; // Adjust decimal place position. --exp; // Adjust decimal place position.
} }
@ -2888,17 +2849,17 @@ void internal::basic_writer<Range>::write_fp(T value,
} else if (specs.align == align::none) { } else if (specs.align == align::none) {
as.align = align::right; as.align = align::right;
} }
char_type decimal_point = handler.use_locale char_type decimal_point = fspec.locale
? internal::decimal_point<char_type>(locale_) ? internal::decimal_point<char_type>(locale_)
: static_cast<char_type>('.'); : static_cast<char_type>('.');
if (use_grisu) { if (use_grisu) {
auto params = gen_digits_params(); auto params = gen_digits_params();
params.sign = sign; params.sign = sign;
params.format = handler.format; params.format = fspec.format;
params.num_digits = precision; params.num_digits = precision;
params.trailing_zeros = params.trailing_zeros =
(precision != 0 && (precision != 0 &&
(handler.format == float_format::fixed || !specs.type)) || (fspec.format == float_format::fixed || !specs.type)) ||
specs.alt; specs.alt;
int num_digits = static_cast<int>(buffer.size()); int num_digits = static_cast<int>(buffer.size());
write_padded(as, grisu_writer<char_type>(buffer.data(), num_digits, exp, write_padded(as, grisu_writer<char_type>(buffer.data(), num_digits, exp,
@ -3078,8 +3039,7 @@ struct formatter<T, Char,
case internal::float_type: case internal::float_type:
case internal::double_type: case internal::double_type:
case internal::long_double_type: case internal::long_double_type:
handle_float_type_spec(specs_.type, internal::parse_float_type_spec(specs_.type, eh);
internal::float_type_checker<decltype(eh)>(eh));
break; break;
case internal::cstring_type: case internal::cstring_type:
internal::handle_cstring_type_spec( internal::handle_cstring_type_spec(

View File

@ -666,8 +666,7 @@ struct formatter {
break; break;
case internal::double_type: case internal::double_type:
case internal::long_double_type: case internal::long_double_type:
handle_float_type_spec(type_spec, internal::parse_float_type_spec(type_spec, eh);
internal::float_type_checker<decltype(eh)>(eh));
break; break;
case internal::cstring_type: case internal::cstring_type:
internal::handle_cstring_type_spec( internal::handle_cstring_type_spec(