mirror of
https://github.com/fmtlib/fmt.git
synced 2026-10-29 02:17:28 +08:00
fix: preserve high-precision float digits and constexpr growth
This commit is contained in:
parent
2c78e73198
commit
d649d10f13
@ -53,7 +53,9 @@
|
||||
|
||||
# include <cmath> // std::signbit
|
||||
# include <limits> // std::numeric_limits
|
||||
# if defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI)
|
||||
# if FMT_USE_CONSTEVAL
|
||||
# include <memory> // std::construct_at
|
||||
# elif defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI)
|
||||
// Workaround for pre gcc 5 libstdc++.
|
||||
# include <memory> // std::allocator_traits
|
||||
# endif
|
||||
@ -888,12 +890,26 @@ FMT_API auto allocate(size_t size) -> void*;
|
||||
template <typename T> struct allocator : private std::decay<void> {
|
||||
using value_type = T;
|
||||
|
||||
auto allocate(size_t n) -> T* {
|
||||
FMT_CONSTEXPR20 auto allocate(size_t n) -> T* {
|
||||
FMT_ASSERT(n <= max_value<size_t>() / sizeof(T), "");
|
||||
#if FMT_USE_CONSTEVAL
|
||||
// Use the builtin directly to avoid C++ runtime dependencies at -O0.
|
||||
if (__builtin_is_constant_evaluated())
|
||||
return std::allocator<T>().allocate(n);
|
||||
#endif
|
||||
return static_cast<T*>(detail::allocate(n * sizeof(T)));
|
||||
}
|
||||
|
||||
void deallocate(T* p, size_t) { free(p); }
|
||||
FMT_CONSTEXPR20 void deallocate(T* p, size_t n) {
|
||||
#if FMT_USE_CONSTEVAL
|
||||
if (__builtin_is_constant_evaluated()) {
|
||||
std::allocator<T>().deallocate(p, n);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
ignore_unused(n);
|
||||
free(p);
|
||||
}
|
||||
|
||||
constexpr friend auto operator==(allocator, allocator) noexcept -> bool {
|
||||
return true; // All instances of this allocator are equivalent.
|
||||
@ -960,10 +976,17 @@ class basic_memory_buffer : public detail::buffer<T> {
|
||||
new_capacity = max_of(size, max_size);
|
||||
T* old_data = buf.data();
|
||||
T* new_data = self.alloc_.allocate(new_capacity);
|
||||
// Suppress a bogus -Wstringop-overflow in gcc 13.1 (#3481).
|
||||
detail::assume(buf.size() <= new_capacity);
|
||||
// The following code doesn't throw, so the raw pointer above doesn't leak.
|
||||
memcpy(new_data, old_data, buf.size() * sizeof(T));
|
||||
if (detail::is_constant_evaluated()) {
|
||||
#if FMT_USE_CONSTEVAL
|
||||
for (size_t i = 0; i < new_capacity; ++i) std::construct_at(new_data + i);
|
||||
#endif
|
||||
for (size_t i = 0; i < buf.size(); ++i) new_data[i] = old_data[i];
|
||||
} else {
|
||||
// Suppress a bogus -Wstringop-overflow in gcc 13.1 (#3481).
|
||||
detail::assume(buf.size() <= new_capacity);
|
||||
memcpy(new_data, old_data, buf.size() * sizeof(T));
|
||||
}
|
||||
self.set(new_data, new_capacity);
|
||||
// deallocate must not throw according to the standard, but even if it does,
|
||||
// the buffer already uses the new storage and will deallocate it in
|
||||
@ -3669,10 +3692,17 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision,
|
||||
: f.assign(converted_value);
|
||||
if (is_predecessor_closer) dragon_flags |= dragon::predecessor_closer;
|
||||
if (fixed) dragon_flags |= dragon::fixed;
|
||||
// Limit precision to the maximum possible number of significant digits in
|
||||
// an IEEE754 double because we don't need to generate zeros.
|
||||
const int max_double_digits = 767;
|
||||
if (precision > max_double_digits) precision = max_double_digits;
|
||||
// Keep the fast double path's significant-digit limit.
|
||||
int max_precision = 767;
|
||||
if ((dragon_flags & dragon::fixup) != 0) {
|
||||
// Fixed precision still counts fractional places before fixup. A value
|
||||
// f * 2^e has at most max(-e, 0) fractional decimal places, and its
|
||||
// number of significant decimal digits is bounded by the bit length of f
|
||||
// + |e|.
|
||||
max_precision = fixed ? (f.e < 0 ? -f.e : 0)
|
||||
: count_digits<1>(f.f) + (f.e < 0 ? -f.e : f.e);
|
||||
}
|
||||
if (precision > max_precision) precision = max_precision;
|
||||
format_dragon(f, dragon_flags, precision, buf, exp);
|
||||
}
|
||||
if (!fixed && !specs.alt()) {
|
||||
|
||||
@ -250,6 +250,18 @@ TEST(compile_test, static_format) {
|
||||
EXPECT_STREQ(result.c_str(), "42");
|
||||
EXPECT_EQ(result.str(), "42");
|
||||
}
|
||||
|
||||
TEST(compile_test, high_precision_constexpr_float) {
|
||||
constexpr auto tiny = []() {
|
||||
auto value = 1.0;
|
||||
for (int i = 0; i < 900; ++i) value /= 2;
|
||||
return value;
|
||||
}();
|
||||
constexpr auto result = FMT_STATIC_FORMAT("{:.1000f}", tiny);
|
||||
EXPECT_EQ(1002u, result.str().size());
|
||||
EXPECT_EQ("43174959877427608893241125591038",
|
||||
fmt::string_view(result.c_str() + 768, 32));
|
||||
}
|
||||
# endif
|
||||
|
||||
TEST(compile_test, text_and_arg) {
|
||||
|
||||
@ -307,6 +307,30 @@ TEST(memory_buffer_test, ctor) {
|
||||
EXPECT_EQ(123u, buffer.capacity());
|
||||
}
|
||||
|
||||
#if FMT_USE_CONSTEVAL && (!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940)
|
||||
template <typename Allocator> constexpr auto constexpr_buffer_growth() -> bool {
|
||||
basic_memory_buffer<char, 2, Allocator> buffer;
|
||||
buffer.push_back('a');
|
||||
buffer.push_back('b');
|
||||
buffer.push_back('c');
|
||||
buffer.push_back('d');
|
||||
buffer.push_back('e');
|
||||
buffer.push_back('f');
|
||||
return buffer.size() == 6 && buffer[0] == 'a' && buffer[1] == 'b' &&
|
||||
buffer[2] == 'c' && buffer[3] == 'd' && buffer[4] == 'e' &&
|
||||
buffer[5] == 'f';
|
||||
}
|
||||
|
||||
TEST(memory_buffer_test, constexpr_grow) {
|
||||
constexpr auto default_allocator =
|
||||
constexpr_buffer_growth<fmt::detail::allocator<char>>();
|
||||
constexpr auto standard_allocator =
|
||||
constexpr_buffer_growth<std::allocator<char>>();
|
||||
EXPECT_TRUE(default_allocator);
|
||||
EXPECT_TRUE(standard_allocator);
|
||||
}
|
||||
#endif
|
||||
|
||||
using std_allocator = allocator_ref<std::allocator<char>>;
|
||||
|
||||
TEST(memory_buffer_test, move_ctor_inline_buffer) {
|
||||
@ -1264,10 +1288,8 @@ TEST(format_test, high_precision_trailing_zeros) {
|
||||
fmt::format("{:.{}f}", -0.5f, 768));
|
||||
EXPECT_EQ(std::string("-0.0625") + std::string(765, '0'),
|
||||
fmt::format("{:#.{}g}", -0.0625, 768));
|
||||
EXPECT_EQ(std::string(" ") + fixed,
|
||||
fmt::format("{:>772.768f}", 0.0625));
|
||||
EXPECT_EQ(std::string(" ") + general,
|
||||
fmt::format("{:>#772.768g}", 0.0625));
|
||||
EXPECT_EQ(std::string(" ") + fixed, fmt::format("{:>772.768f}", 0.0625));
|
||||
EXPECT_EQ(std::string(" ") + general, fmt::format("{:>#772.768g}", 0.0625));
|
||||
}
|
||||
|
||||
TEST(format_test, high_precision_bounded_output) {
|
||||
@ -1284,6 +1306,29 @@ TEST(format_test, high_precision_bounded_output) {
|
||||
EXPECT_EQ("0.062500x", fmt::string_view(buffer, 9));
|
||||
}
|
||||
|
||||
TEST(format_test, high_precision_long_double) {
|
||||
if (std::numeric_limits<long double>::max_exponent <= 3000 ||
|
||||
fmt::detail::is_double_double<long double>::value)
|
||||
return;
|
||||
auto tiny = std::ldexp(1.0L, -3000);
|
||||
auto large = std::ldexp(1.0L, 3000);
|
||||
char buffer[3004];
|
||||
safe_sprintf(buffer, "%.1000Lf", tiny);
|
||||
EXPECT_EQ(buffer, fmt::format("{:.1000f}", tiny));
|
||||
safe_sprintf(buffer, "%.1000Lf", -tiny);
|
||||
EXPECT_EQ(buffer, fmt::format("{:.1000f}", -tiny));
|
||||
safe_sprintf(buffer, "%.3000Lf", tiny);
|
||||
EXPECT_EQ(buffer, fmt::format("{:.3000f}", tiny));
|
||||
safe_sprintf(buffer, "%#.1000Lg", large);
|
||||
EXPECT_EQ(buffer, fmt::format("{:#.1000g}", large));
|
||||
safe_sprintf(buffer, "%.0Lf", large);
|
||||
EXPECT_EQ(buffer, fmt::format("{:.0f}", large));
|
||||
EXPECT_EQ("0." + std::string(1000, '0'), fmt::format("{:.1000f}", 0.0L));
|
||||
EXPECT_EQ("-0." + std::string(1000, '0'), fmt::format("{:.1000f}", -0.0L));
|
||||
EXPECT_EQ("0.5" + std::string(999, '0'), fmt::format("{:.1000f}", 0.5L));
|
||||
EXPECT_EQ("-0.5" + std::string(999, '0'), fmt::format("{:.1000f}", -0.5L));
|
||||
}
|
||||
|
||||
TEST(format_test, large_precision) {
|
||||
// Iterator used to abort the actual output.
|
||||
struct throwing_iterator {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user