Pārlūkot izejas kodu

AK: Add formatters for floating point numbers.

asynts 4 gadi atpakaļ
vecāks
revīzija
32957745fb
3 mainītis faili ar 132 papildinājumiem un 1 dzēšanām
  1. 73 0
      AK/Format.cpp
  2. 36 1
      AK/Format.h
  3. 23 0
      AK/Tests/TestFormat.cpp

+ 73 - 0
AK/Format.cpp

@@ -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)

+ 36 - 1
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);
 
 

+ 23 - 0
AK/Tests/TestFormat.cpp

@@ -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)