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); +}