mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
AK: Add Formatter<FixedPoint<...>> 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.
This commit is contained in:
parent
77b3230c80
commit
f021baf255
Notes:
sideshowbarker
2024-07-17 20:21:45 +09:00
Author: https://github.com/tomuta Commit: https://github.com/SerenityOS/serenity/commit/f021baf2550 Pull-request: https://github.com/SerenityOS/serenity/pull/11439 Reviewed-by: https://github.com/kleinesfilmroellchen Reviewed-by: https://github.com/linusg ✅
4 changed files with 124 additions and 15 deletions
|
@ -315,21 +315,6 @@ private:
|
|||
Underlying m_value;
|
||||
};
|
||||
|
||||
template<size_t precision, Integral Underlying>
|
||||
struct Formatter<FixedPoint<precision, Underlying>> : StandardFormatter {
|
||||
Formatter() = default;
|
||||
explicit Formatter(StandardFormatter formatter)
|
||||
: StandardFormatter(formatter)
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<void> format(FormatBuilder& builder, FixedPoint<precision, Underlying> value)
|
||||
{
|
||||
Formatter<double> formatter { *this };
|
||||
return formatter.format(builder, (double)value);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using AK::FixedPoint;
|
||||
|
|
|
@ -356,6 +356,72 @@ ErrorOr<void> FormatBuilder::put_i64(
|
|||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> 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<u64>(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 <charconv>: 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<size_t>::max(), fill));
|
||||
return {};
|
||||
}
|
||||
|
||||
#ifndef KERNEL
|
||||
ErrorOr<void> FormatBuilder::put_f64(
|
||||
double value,
|
||||
|
|
49
AK/Format.h
49
AK/Format.h
|
@ -12,6 +12,7 @@
|
|||
#include <AK/AnyOf.h>
|
||||
#include <AK/Array.h>
|
||||
#include <AK/Error.h>
|
||||
#include <AK/FixedPoint.h>
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/StringView.h>
|
||||
|
@ -185,6 +186,19 @@ public:
|
|||
char fill = ' ',
|
||||
SignMode sign_mode = SignMode::OnlyIfNeeded);
|
||||
|
||||
ErrorOr<void> 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<void> put_f80(
|
||||
long double value,
|
||||
|
@ -469,6 +483,41 @@ struct Formatter<long double> : StandardFormatter {
|
|||
};
|
||||
#endif
|
||||
|
||||
template<size_t precision, typename Underlying>
|
||||
struct Formatter<FixedPoint<precision, Underlying>> : StandardFormatter {
|
||||
Formatter() = default;
|
||||
explicit Formatter(StandardFormatter formatter)
|
||||
: StandardFormatter(formatter)
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<void> format(FormatBuilder& builder, FixedPoint<precision, Underlying> 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<Underlying>(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<std::nullptr_t> : Formatter<FlatPtr> {
|
||||
ErrorOr<void> format(FormatBuilder& builder, std::nullptr_t)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue