mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 10:08:12 +00:00
AK: Add formatters for floating point numbers.
This commit is contained in:
parent
3b3edbc4d2
commit
32957745fb
3 changed files with 132 additions and 1 deletions
|
@ -384,6 +384,44 @@ void FormatBuilder::put_i64(
|
||||||
put_u64(static_cast<size_t>(value), base, prefix, upper_case, zero_pad, align, min_width, fill, sign_mode, is_negative);
|
put_u64(static_cast<size_t>(value), base, prefix, upper_case, zero_pad, align, min_width, fill, sign_mode, is_negative);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef KERNEL
|
||||||
|
void FormatBuilder::put_f64(
|
||||||
|
double value,
|
||||||
|
u8 base,
|
||||||
|
bool upper_case,
|
||||||
|
Align align,
|
||||||
|
size_t min_width,
|
||||||
|
size_t precision,
|
||||||
|
char fill,
|
||||||
|
SignMode sign_mode)
|
||||||
|
{
|
||||||
|
StringBuilder string_builder;
|
||||||
|
FormatBuilder format_builder { string_builder };
|
||||||
|
|
||||||
|
format_builder.put_i64(static_cast<i64>(value), base, false, upper_case, false, Align::Right, 0, ' ', sign_mode);
|
||||||
|
string_builder.append('.');
|
||||||
|
|
||||||
|
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”)
|
||||||
|
value -= static_cast<i64>(value);
|
||||||
|
|
||||||
|
if (value < 0)
|
||||||
|
value = -value;
|
||||||
|
|
||||||
|
for (u32 i = 0; i < precision; ++i)
|
||||||
|
value *= 10;
|
||||||
|
|
||||||
|
format_builder.put_u64(static_cast<u64>(value), base, false, upper_case, true, Align::Right, precision);
|
||||||
|
|
||||||
|
// FIXME: Cut off trailing zeroes by default?
|
||||||
|
}
|
||||||
|
|
||||||
|
put_string(string_builder.string_view(), align, min_width, NumericLimits<size_t>::max(), fill);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void vformat(StringBuilder& builder, StringView fmtstr, TypeErasedFormatParams params)
|
void vformat(StringBuilder& builder, StringView fmtstr, TypeErasedFormatParams params)
|
||||||
{
|
{
|
||||||
FormatBuilder fmtbuilder { builder };
|
FormatBuilder fmtbuilder { builder };
|
||||||
|
@ -463,6 +501,12 @@ void StandardFormatter::parse(TypeErasedFormatParams& params, FormatParser& pars
|
||||||
m_mode = Mode::String;
|
m_mode = Mode::String;
|
||||||
else if (parser.consume_specific('p'))
|
else if (parser.consume_specific('p'))
|
||||||
m_mode = Mode::Pointer;
|
m_mode = Mode::Pointer;
|
||||||
|
else if (parser.consume_specific('f'))
|
||||||
|
m_mode = Mode::Float;
|
||||||
|
else if (parser.consume_specific('a'))
|
||||||
|
m_mode = Mode::Hexfloat;
|
||||||
|
else if (parser.consume_specific('A'))
|
||||||
|
m_mode = Mode::HexfloatUppercase;
|
||||||
|
|
||||||
if (!parser.is_eof())
|
if (!parser.is_eof())
|
||||||
dbgln("{} did not consume '{}'", __PRETTY_FUNCTION__, parser.remaining());
|
dbgln("{} did not consume '{}'", __PRETTY_FUNCTION__, parser.remaining());
|
||||||
|
@ -570,6 +614,35 @@ void Formatter<bool>::format(TypeErasedFormatParams& params, FormatBuilder& buil
|
||||||
return formatter.format(params, builder, value ? "true" : "false");
|
return formatter.format(params, builder, value ? "true" : "false");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#ifndef KERNEL
|
||||||
|
void Formatter<double>::format(TypeErasedFormatParams& params, FormatBuilder& builder, double 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 {
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto width = params.decode(m_width);
|
||||||
|
const auto precision = params.decode(m_precision, 6);
|
||||||
|
|
||||||
|
builder.put_f64(value, base, upper_case, m_align, width, precision, m_fill, m_sign_mode);
|
||||||
|
}
|
||||||
|
void Formatter<float>::format(TypeErasedFormatParams& params, FormatBuilder& builder, float value)
|
||||||
|
{
|
||||||
|
Formatter<double> formatter { *this };
|
||||||
|
formatter.format(params, builder, value);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef KERNEL
|
#ifndef KERNEL
|
||||||
void vout(FILE* file, StringView fmtstr, TypeErasedFormatParams params, bool newline)
|
void vout(FILE* file, StringView fmtstr, TypeErasedFormatParams params, bool newline)
|
||||||
|
|
37
AK/Format.h
37
AK/Format.h
|
@ -157,7 +157,22 @@ public:
|
||||||
char fill = ' ',
|
char fill = ' ',
|
||||||
SignMode sign_mode = SignMode::OnlyIfNeeded);
|
SignMode sign_mode = SignMode::OnlyIfNeeded);
|
||||||
|
|
||||||
const StringBuilder& builder() const { return m_builder; }
|
#ifndef KERNEL
|
||||||
|
void put_f64(
|
||||||
|
double value,
|
||||||
|
u8 base = 10,
|
||||||
|
bool upper_case = false,
|
||||||
|
Align align = Align::Right,
|
||||||
|
size_t min_width = 0,
|
||||||
|
size_t precision = 6,
|
||||||
|
char fill = ' ',
|
||||||
|
SignMode sign_mode = SignMode::OnlyIfNeeded);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const StringBuilder& builder() const
|
||||||
|
{
|
||||||
|
return m_builder;
|
||||||
|
}
|
||||||
StringBuilder& builder() { return m_builder; }
|
StringBuilder& builder() { return m_builder; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -218,6 +233,9 @@ struct StandardFormatter {
|
||||||
Character,
|
Character,
|
||||||
String,
|
String,
|
||||||
Pointer,
|
Pointer,
|
||||||
|
Float,
|
||||||
|
Hexfloat,
|
||||||
|
HexfloatUppercase,
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr size_t value_not_set = NumericLimits<size_t>::max();
|
static constexpr size_t value_not_set = NumericLimits<size_t>::max();
|
||||||
|
@ -303,6 +321,23 @@ struct Formatter<bool> : StandardFormatter {
|
||||||
void format(TypeErasedFormatParams&, FormatBuilder&, bool value);
|
void format(TypeErasedFormatParams&, FormatBuilder&, bool value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifndef KERNEL
|
||||||
|
template<>
|
||||||
|
struct Formatter<float> : StandardFormatter {
|
||||||
|
void format(TypeErasedFormatParams&, FormatBuilder&, float value);
|
||||||
|
};
|
||||||
|
template<>
|
||||||
|
struct Formatter<double> : StandardFormatter {
|
||||||
|
Formatter() { }
|
||||||
|
explicit Formatter(StandardFormatter formatter)
|
||||||
|
: StandardFormatter(formatter)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void format(TypeErasedFormatParams&, FormatBuilder&, double value);
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
void vformat(StringBuilder& builder, StringView fmtstr, TypeErasedFormatParams);
|
void vformat(StringBuilder& builder, StringView fmtstr, TypeErasedFormatParams);
|
||||||
void vformat(const LogStream& stream, StringView fmtstr, TypeErasedFormatParams);
|
void vformat(const LogStream& stream, StringView fmtstr, TypeErasedFormatParams);
|
||||||
|
|
||||||
|
|
|
@ -235,4 +235,27 @@ TEST_CASE(file_descriptor)
|
||||||
fclose(file);
|
fclose(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(floating_point_numbers)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(String::formatted("{}", 1.12), "1.120000");
|
||||||
|
EXPECT_EQ(String::formatted("{}", 1.), "1.000000");
|
||||||
|
EXPECT_EQ(String::formatted("{:.3}", 1.12), "1.120");
|
||||||
|
EXPECT_EQ(String::formatted("{:.1}", 1.12), "1.1");
|
||||||
|
EXPECT_EQ(String::formatted("{}", -1.12), "-1.120000");
|
||||||
|
|
||||||
|
// FIXME: There is always the question what we mean with the width field. Do we mean significant digits?
|
||||||
|
// Do we mean the whole width? This is what was the simplest to implement:
|
||||||
|
EXPECT_EQ(String::formatted("{:x>5.1}", 1.12), "xx1.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(no_precision_no_trailing_number)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(String::formatted("{:.0}", 0.1), "0.");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(yay_this_implementation_sucks)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(String::formatted("{:.0}", .99999999999), "0.");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_MAIN(Format)
|
TEST_MAIN(Format)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue