diff --git a/include/fmt/format.h b/include/fmt/format.h --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -28,16 +28,18 @@ of this Software are embedded into a machine-executable object form of such source code, you may redistribute such embedded portions in such object form without including the above copyright and permission notices. */ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ +#include "double-conversion/double-to-string.h" + #ifndef _LIBCPP_REMOVE_TRANSITIVE_INCLUDES # define _LIBCPP_REMOVE_TRANSITIVE_INCLUDES # define FMT_REMOVE_TRANSITIVE_INCLUDES #endif #include "base.h" #ifndef FMT_MODULE @@ -3141,16 +3143,23 @@ constexpr auto fractional_part_rounding_ return U"\x9999999a\x828f5c29\x80418938\x80068db9\x8000a7c6\x800010c7" U"\x800001ae\x8000002b"[index]; } template FMT_CONSTEXPR20 auto format_float(Float value, int precision, const format_specs& specs, bool binary32, buffer& buf) -> int { + // GCC and old clang seem to hit this even though this function isn't called. + #ifdef __clang__ + #if __clang_major__ >= 17 + static_assert(false, + "This method is not to be used in Gecko, use format_float_gecko"); + #endif + #endif // float is passed as double to reduce the number of instantiations. static_assert(!std::is_same::value, ""); auto converted_value = convert_float(value); const bool fixed = specs.type() == presentation_type::fixed; if (value == 0) { if (precision <= 0 || !fixed) { buf.push_back('0'); @@ -3436,68 +3445,109 @@ FMT_CONSTEXPR20 auto format_float(Float --num_digits; ++exp; } buf.try_resize(num_digits); } return exp; } +// See https://bugzilla.mozilla.org/show_bug.cgi?id=1717448#c2 for the reason +// we're doing this. This copied from and should be kept in sync with +// PrintfTarget::cvt_f in Gecko's mozglue/misc/Printf.cpp. +template +auto format_float_gecko(Float value, int precision, format_specs specs, + buffer& buf) -> int { + FMT_ASSERT(detail::isfinite(value), + "Non-finite values are to be handled ahead of calling this"); + using double_conversion::DoubleToStringConverter; + using DTSC = DoubleToStringConverter; + // Printf.cpp seem to use UNIQUE_ZERO here, but then adds its own `-` further + // in the code. + char e = specs.upper() ? 'E' : 'e'; + DTSC converter(DTSC::NO_TRAILING_ZERO | + DTSC::EMIT_POSITIVE_EXPONENT_SIGN, + "inf", "nan", e, 0, 0, 4, 0, 2); + buf.try_resize(64); + double_conversion::StringBuilder builder(buf.data(), buf.size()); + // Negative precision defaults to 6. + if (precision == -1) { precision = 6; } + bool success; + if (specs.type() == presentation_type::exp) { + success = converter.ToExponential(value, precision, &builder); + } else if (specs.type() == presentation_type::fixed) { + success = converter.ToFixed(value, precision, &builder); + } else if (specs.type() == presentation_type::general || + specs.type() == presentation_type::none) { + // "If an explicit precision is zero, it shall be taken as 1." + success = converter.ToPrecision(value, precision ? precision : 1, &builder); + } else { + FMT_ASSERT(false, "Unhandled"); + } + FMT_ASSERT(success, ""); + buf.try_resize(builder.position()); + return builder.position(); +} + template FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs, locale_ref loc) -> OutputIt { // Use signbit because value < 0 is false for NaN. sign s = detail::signbit(value) ? sign::minus : specs.sign(); if (!detail::isfinite(value)) return write_nonfinite(out, detail::isnan(value), specs, s); if (specs.align() == align::numeric && s != sign::none) { *out++ = detail::getsign(s); s = sign::none; if (specs.width != 0) --specs.width; } int precision = specs.precision; - if (precision < 0) { - if (specs.type() != presentation_type::none) { - precision = 6; - } else if (is_fast_float::value && !is_constant_evaluated()) { - // Use Dragonbox for the shortest format. - using floaty = conditional_t= sizeof(double), double, float>; - auto dec = dragonbox::to_decimal(static_cast(value)); - return write_float(out, dec, specs, s, loc); - } - } - memory_buffer buffer; if (specs.type() == presentation_type::hexfloat) { if (s != sign::none) buffer.push_back(detail::getsign(s)); format_hexfloat(convert_float(value), specs, buffer); return write_bytes(out, {buffer.data(), buffer.size()}, specs); } if (specs.type() == presentation_type::exp) { - if (precision == max_value()) - report_error("number is too big"); - else - ++precision; if (specs.precision != 0) specs.set_alt(); } else if (specs.type() == presentation_type::fixed) { if (specs.precision != 0) specs.set_alt(); } else if (precision == 0) { precision = 1; } - int exp = format_float(convert_float(value), precision, specs, - std::is_same(), buffer); specs.precision = precision; - auto f = big_decimal_fp{buffer.data(), static_cast(buffer.size()), exp}; - return write_float(out, f, specs, s, loc); + value = abs(value); + format_float_gecko(convert_float(value), precision, specs, buffer); + size_t size = buffer.size(); + if (s != sign::none) { + // Space for the sign, this influences the padding calculations below. + size++; + // If padding with zeros, the sign goes here, e.g. -0004.3 + if (*specs.fill() == '0') { + *out++ = detail::getsign(s); + } + } + return write_padded( + out, specs, size, size, [s, &buffer, specs](reserve_iterator it) { + // If padding with something other than zeros, the sign goes here, e.g. + // ' -4.3' (quotes added for clarity). + if (s != sign::none) { + if (*specs.fill() != '0') { + *it++ = detail::getsign(s); + } + } + const char* data = buffer.data(); + return copy(data, data + buffer.size(), it); + }); } template ::value)> FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs, locale_ref loc = {}) -> OutputIt { return specs.localized() && write_loc(out, value, specs, loc) ? out @@ -3513,18 +3563,17 @@ FMT_CONSTEXPR20 auto write(OutputIt out, constexpr auto specs = format_specs(); using floaty = conditional_t= sizeof(double), double, float>; using floaty_uint = typename dragonbox::float_info::carrier_uint; floaty_uint mask = exponent_mask(); if ((bit_cast(value) & mask) == mask) return write_nonfinite(out, std::isnan(value), specs, s); - auto dec = dragonbox::to_decimal(static_cast(value)); - return write_float(out, dec, specs, s, {}); + return write_float(out, value, specs, {}); } template ::value && !is_fast_float::value)> inline auto write(OutputIt out, T value) -> OutputIt { return write(out, value, format_specs()); }