From f021baf25507e466def8b07ca48a7b54bf140131 Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 27 Dec 2021 18:23:04 -0700 Subject: [PATCH] AK: Add Formatter> without floating point Rather than casting the FixedPoint to double, format the FixedPoint directly. This avoids using floating point instruction, which in turn enables this to be used even in the kernel. --- AK/FixedPoint.h | 15 --------- AK/Format.cpp | 66 +++++++++++++++++++++++++++++++++++++ AK/Format.h | 49 +++++++++++++++++++++++++++ Tests/AK/TestFixedPoint.cpp | 9 +++++ 4 files changed, 124 insertions(+), 15 deletions(-) diff --git a/AK/FixedPoint.h b/AK/FixedPoint.h index a67aa81f0c..91d05af7df 100644 --- a/AK/FixedPoint.h +++ b/AK/FixedPoint.h @@ -315,21 +315,6 @@ private: Underlying m_value; }; -template -struct Formatter> : StandardFormatter { - Formatter() = default; - explicit Formatter(StandardFormatter formatter) - : StandardFormatter(formatter) - { - } - - ErrorOr format(FormatBuilder& builder, FixedPoint value) - { - Formatter formatter { *this }; - return formatter.format(builder, (double)value); - } -}; - } using AK::FixedPoint; diff --git a/AK/Format.cpp b/AK/Format.cpp index 635f0218db..c71b6ddf5b 100644 --- a/AK/Format.cpp +++ b/AK/Format.cpp @@ -356,6 +356,72 @@ ErrorOr FormatBuilder::put_i64( return {}; } +ErrorOr FormatBuilder::put_fixed_point( + i64 integer_value, + u64 fraction_value, + u64 fraction_one, + u8 base, + bool upper_case, + bool zero_pad, + Align align, + size_t min_width, + size_t precision, + char fill, + SignMode sign_mode) +{ + StringBuilder string_builder; + FormatBuilder format_builder { string_builder }; + + bool is_negative = integer_value < 0; + if (is_negative) + integer_value = -integer_value; + + TRY(format_builder.put_u64(static_cast(integer_value), base, false, upper_case, false, Align::Right, 0, ' ', sign_mode, is_negative)); + + if (precision > 0) { + // FIXME: This is a terrible approximation but doing it properly would be a lot of work. If someone is up for that, a good + // place to start would be the following video from CppCon 2019: + // https://youtu.be/4P_kbF0EbZM (Stephan T. Lavavej “Floating-Point : Making Your Code 10x Faster With C++17's Final Boss”) + +#ifdef KERNEL + // We don't have pow() in kernel land + u64 scale = 10; + for (size_t i = 0; i < precision - 1; i++) // TODO: not efficient + scale *= 10; +#else + u64 scale = pow(10.0, (double)precision); +#endif + + auto fraction = (scale * fraction_value) / fraction_one; // TODO: overflows + if (is_negative) + fraction = scale - fraction; + while (fraction != 0 && fraction % 10 == 0) + fraction /= 10; + + size_t visible_precision = 0; + { + auto fraction_tmp = fraction; + for (; visible_precision < precision; ++visible_precision) { + if (fraction_tmp == 0) + break; + fraction_tmp /= 10; + } + } + + if (zero_pad || visible_precision > 0) + TRY(string_builder.try_append('.')); + + if (visible_precision > 0) + TRY(format_builder.put_u64(fraction, base, false, upper_case, true, Align::Right, visible_precision)); + + if (zero_pad && (precision - visible_precision) > 0) + TRY(format_builder.put_u64(0, base, false, false, true, Align::Right, precision - visible_precision)); + } + + TRY(put_string(string_builder.string_view(), align, min_width, NumericLimits::max(), fill)); + return {}; +} + #ifndef KERNEL ErrorOr FormatBuilder::put_f64( double value, diff --git a/AK/Format.h b/AK/Format.h index 3d4f33bc6a..d5e127356f 100644 --- a/AK/Format.h +++ b/AK/Format.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -185,6 +186,19 @@ public: char fill = ' ', SignMode sign_mode = SignMode::OnlyIfNeeded); + ErrorOr put_fixed_point( + i64 integer_value, + u64 fraction_value, + u64 fraction_one, + u8 base = 10, + bool upper_case = false, + bool zero_pad = false, + Align align = Align::Right, + size_t min_width = 0, + size_t precision = 6, + char fill = ' ', + SignMode sign_mode = SignMode::OnlyIfNeeded); + #ifndef KERNEL ErrorOr put_f80( long double value, @@ -469,6 +483,41 @@ struct Formatter : StandardFormatter { }; #endif +template +struct Formatter> : StandardFormatter { + Formatter() = default; + explicit Formatter(StandardFormatter formatter) + : StandardFormatter(formatter) + { + } + + ErrorOr format(FormatBuilder& builder, FixedPoint value) + { + u8 base; + bool upper_case; + if (m_mode == Mode::Default || m_mode == Mode::Float) { + base = 10; + upper_case = false; + } else if (m_mode == Mode::Hexfloat) { + base = 16; + upper_case = false; + } else if (m_mode == Mode::HexfloatUppercase) { + base = 16; + upper_case = true; + } else { + VERIFY_NOT_REACHED(); + } + + m_width = m_width.value_or(0); + m_precision = m_precision.value_or(6); + + i64 integer = value.ltrunk(); + constexpr u64 one = static_cast(1) << precision; + u64 fraction_raw = value.raw() & (one - 1); + return builder.put_fixed_point(integer, fraction_raw, one, base, upper_case, m_zero_pad, m_align, m_width.value(), m_precision.value(), m_fill, m_sign_mode); + } +}; + template<> struct Formatter : Formatter { ErrorOr format(FormatBuilder& builder, std::nullptr_t) diff --git a/Tests/AK/TestFixedPoint.cpp b/Tests/AK/TestFixedPoint.cpp index 83d1271f67..422320d1e3 100644 --- a/Tests/AK/TestFixedPoint.cpp +++ b/Tests/AK/TestFixedPoint.cpp @@ -72,3 +72,12 @@ TEST_CASE(rounding) EXPECT_EQ(Type(-1.5).lceil(), -1); EXPECT_EQ(Type(-1.5).ltrunk(), -1); } + +TEST_CASE(formatter) +{ + EXPECT_EQ(String::formatted("{}", FixedPoint<16>(123.456)), "123.455993"sv); + EXPECT_EQ(String::formatted("{}", FixedPoint<16>(-123.456)), "-123.455994"sv); + EXPECT_EQ(String::formatted("{}", FixedPoint<4>(123.456)), "123.4375"sv); + EXPECT_EQ(String::formatted("{}", FixedPoint<4>(-123.456)), "-123.4375"sv); + EXPECT_EQ(String::formatted("{}", FixedPoint<16> {}), "0"sv); +}