ronantakizawa/codereview-bench huggingface dataset:
Repository: SerenityOS/serenity
File: Kernel/Arch/riscv64/CSR.h (C/C++)

Code before:
    FlatPtr ret;
    asm volatile("csrr %0, %1"
                 : "=r"(ret)
                 : "i"(address));
    return ret;
}

template<Address address>
ALWAYS_INLINE void write(FlatPtr value)
{
    asm volatile("csrw %0, %1" ::"i"(address), "Kr"(value));
}

template<Address address>
ALWAYS_INLINE FlatPtr read_and_set_bits(FlatPtr bit_mask)
{
    FlatPtr ret;
    asm volatile("csrrs %0, %1, %2"
                 : "=r"(ret)
                 : "i"(address), "Kr"(bit_mask));
    return ret;
}
template<Address address, Enum E>
requires(IsSame<UnderlyingType<E>, u64>)
ALWAYS_INLINE void set_bits(E bit_mask)
{
    asm volatile("csrs %0, %1" ::"i"(address), "Kr"(bit_mask));
}
template<Address address, Enum E>
requires(IsSame<UnderlyingType<E>, u64>)
ALWAYS_INLINE void clear_bits(E bit_mask)
{
    asm volatile("csrc %0, %1" ::"i"(address), "Kr"(bit_mask));
}

// 4.1.11 Supervisor Address Translation and Protection (satp) Register
struct SATP {
    enum class Mode : u64 {
        Bare = 0,
        Sv39 = 8,
        Sv48 = 9,
        Sv57 = 10,
    };

    // Physical page number of root page table
    u64 PPN : 44;

    // Address space identifier
    u64 ASID : 16;

    // Current address-translation scheme

Review comment (suggestion):
We might want to use these functions without enums in the future, but I guess we can add an overload for that then.

Code after:
    FlatPtr ret;
    asm volatile("csrr %0, %1"
                 : "=r"(ret)
                 : "i"(address));
    return ret;
}

template<Address address>
ALWAYS_INLINE void write(FlatPtr value)
{
    asm volatile("csrw %0, %1" ::"i"(address), "Kr"(value));
}

template<Address address>
ALWAYS_INLINE FlatPtr read_and_set_bits(FlatPtr bit_mask)
{
    FlatPtr ret;
    asm volatile("csrrs %0, %1, %2"
                 : "=r"(ret)
                 : "i"(address), "Kr"(bit_mask));
    return ret;
}

template<Address address, Enum E>
requires(IsSame<UnderlyingType<E>, FlatPtr>)
ALWAYS_INLINE void set_bits(E bit_mask)
{
    asm volatile("csrs %0, %1" ::"i"(address), "Kr"(bit_mask));
}

template<Address address, Enum E>
requires(IsSame<UnderlyingType<E>, FlatPtr>)
ALWAYS_INLINE void clear_bits(E bit_mask)
{
    asm volatile("csrc %0, %1" ::"i"(address), "Kr"(bit_mask));
}

// 4.1.11 Supervisor Address Translation and Protection (satp) Register
struct SATP {
    enum class Mode : u64 {
        Bare = 0,
        Sv39 = 8,
        Sv48 = 9,
        Sv57 = 10,
    };

    // Physical page number of root page table
    u64 PPN : 44;

    // Address space identifier
    u64 ASID : 16;
Repository: SerenityOS/serenity
File: Kernel/Arch/riscv64/CSR.h (C/C++)

Code before:
ALWAYS_INLINE FlatPtr read()
{
    FlatPtr ret;
    asm volatile("csrr %0, %1"
                 : "=r"(ret)
                 : "i"(address));
    return ret;
}

template<Address address>
ALWAYS_INLINE void write(FlatPtr value)
{
    asm volatile("csrw %0, %1" ::"i"(address), "Kr"(value));
}

template<Address address>
ALWAYS_INLINE FlatPtr read_and_set_bits(FlatPtr bit_mask)
{
    FlatPtr ret;
    asm volatile("csrrs %0, %1, %2"
                 : "=r"(ret)
                 : "i"(address), "Kr"(bit_mask));
    return ret;
}
template<Address address, Enum E>
requires(IsSame<UnderlyingType<E>, u64>)
ALWAYS_INLINE void set_bits(E bit_mask)
{
    asm volatile("csrs %0, %1" ::"i"(address), "Kr"(bit_mask));
}
template<Address address, Enum E>
requires(IsSame<UnderlyingType<E>, u64>)
ALWAYS_INLINE void clear_bits(E bit_mask)
{
    asm volatile("csrc %0, %1" ::"i"(address), "Kr"(bit_mask));
}

// 4.1.11 Supervisor Address Translation and Protection (satp) Register
struct SATP {
    enum class Mode : u64 {
        Bare = 0,
        Sv39 = 8,
        Sv48 = 9,
        Sv57 = 10,
    };

    // Physical page number of root page table
    u64 PPN : 44;

    // Address space identifier
    u64 ASID : 16;

Review comment (suggestion):
We currently use `FlatPtr` as the CSR value type everywhere, not `u64`.

Code after:
ALWAYS_INLINE FlatPtr read()
{
    FlatPtr ret;
    asm volatile("csrr %0, %1"
                 : "=r"(ret)
                 : "i"(address));
    return ret;
}

template<Address address>
ALWAYS_INLINE void write(FlatPtr value)
{
    asm volatile("csrw %0, %1" ::"i"(address), "Kr"(value));
}

template<Address address>
ALWAYS_INLINE FlatPtr read_and_set_bits(FlatPtr bit_mask)
{
    FlatPtr ret;
    asm volatile("csrrs %0, %1, %2"
                 : "=r"(ret)
                 : "i"(address), "Kr"(bit_mask));
    return ret;
}

template<Address address, Enum E>
requires(IsSame<UnderlyingType<E>, FlatPtr>)
ALWAYS_INLINE void set_bits(E bit_mask)
{
    asm volatile("csrs %0, %1" ::"i"(address), "Kr"(bit_mask));
}

template<Address address, Enum E>
requires(IsSame<UnderlyingType<E>, FlatPtr>)
ALWAYS_INLINE void clear_bits(E bit_mask)
{
    asm volatile("csrc %0, %1" ::"i"(address), "Kr"(bit_mask));
}

// 4.1.11 Supervisor Address Translation and Protection (satp) Register
struct SATP {
    enum class Mode : u64 {
        Bare = 0,
        Sv39 = 8,
        Sv48 = 9,
        Sv57 = 10,
    };

    // Physical page number of root page table
    u64 PPN : 44;
Repository: SerenityOS/serenity
File: Userland/Libraries/LibGfx/ImageFormats/JPEGXLLoader.cpp (C++)

Code before:
        kLFFrame = 1,
        kReferenceOnly = 2,
        kSkipProgressive = 3,
    };

    enum class Flags {
        None = 0,
        kNoise = 1,
        kPatches = 1 << 1,
        kSplines = 1 << 4,
        kUseLfFrame = 1 << 5,
        kSkipAdaptiveLFSmoothing = 1 << 7,
    };

    FrameType frame_type { FrameType::kRegularFrame };
    Encoding encoding { Encoding::kVarDCT };
    Flags flags { Flags::None };

    bool do_YCbCr { false };

    Array<u8, 3> jpeg_upsampling {};
    u8 upsampling {};
    FixedArray<u8> ec_upsampling {};

    u8 group_size_shift { 1 };
    u16 group_dim { static_cast<u16>(128 << group_size_shift) };
    u8 x_qm_scale { 3 };
    u8 b_qm_scale { 2 };
    Passes passes {};

    u8 lf_level {};
    bool have_crop { false };
    i32 x0 {};
    i32 y0 {};
    u32 width {};
    u32 height {};

    BlendingInfo blending_info {};
    FixedArray<BlendingInfo> ec_blending_info {};

    u32 duration {};

    bool is_last { true };
    u8 save_as_reference {};
    bool save_before_ct {};

    String name {};
    RestorationFilter restoration_filter {};
    Extensions extensions {};
};


Review comment (suggestion):
Looks like this never set to something differentn from `128 << group_size_shift` – should this be a method that does `u16 group_dim() const { return static_cast<u16>(128 << group_size_shift); }`?

(also, `group_dim` seems a un-serenity-y name. No abbreviations, etc.)

Code after:
        kLFFrame = 1,
        kReferenceOnly = 2,
        kSkipProgressive = 3,
    };

    enum class Flags {
        None = 0,
        kNoise = 1,
        kPatches = 1 << 1,
        kSplines = 1 << 4,
        kUseLfFrame = 1 << 5,
        kSkipAdaptiveLFSmoothing = 1 << 7,
    };

    FrameType frame_type { FrameType::kRegularFrame };
    Encoding encoding { Encoding::kVarDCT };
    Flags flags { Flags::None };

    bool do_YCbCr { false };

    Array<u8, 3> jpeg_upsampling {};
    u8 upsampling {};
    FixedArray<u8> ec_upsampling {};

    u8 group_size_shift { 1 };
    u16 group_dim() const { return 128 << group_size_shift; }
    u8 x_qm_scale { 3 };
    u8 b_qm_scale { 2 };
    Passes passes {};

    u8 lf_level {};
    bool have_crop { false };
    i32 x0 {};
    i32 y0 {};
    u32 width {};
    u32 height {};

    BlendingInfo blending_info {};
    FixedArray<BlendingInfo> ec_blending_info {};

    u32 duration {};

    bool is_last { true };
    u8 save_as_reference {};
    bool save_before_ct {};

    String name {};
    RestorationFilter restoration_filter {};
    Extensions extensions {};
};
Repository: SerenityOS/serenity
File: Kernel/Arch/riscv64/Timer.cpp (C++)

Code before:
    auto timer = adopt_lock_ref(*new Timer);
    s_the = timer.ptr();
    return timer;
}

Timer& Timer::the()
{
    VERIFY(s_the);
    return *s_the;
}

u64 Timer::current_ticks()
{
    return RISCV64::CSR::read(RISCV64::CSR::Address::TIME);
}

void Timer::handle_interrupt()
{
    if (m_callback)
        m_callback();
    set_compare(current_ticks() + m_interrupt_interval);
}

u64 Timer::update_time(u64& seconds_since_boot, u32& ticks_this_second, bool query_only)
{
    // Should only be called by the time keeper interrupt handler!
    u64 current_value = current_ticks();
    u64 delta_ticks = m_main_counter_drift;
    if (current_value >= m_main_counter_last_read) {
        delta_ticks += current_value - m_main_counter_last_read;
    } else {
        // the counter wrapped around
        delta_ticks += (NumericLimits<u64>::max() - m_main_counter_last_read + 1) + current_value;
    }

    u64 ticks_since_last_second = (u64)ticks_this_second + delta_ticks;
    auto frequency = ticks_per_second();
    seconds_since_boot += ticks_since_last_second / frequency;
    ticks_this_second = ticks_since_last_second % frequency;

    if (!query_only) {
        m_main_counter_drift = 0;
        m_main_counter_last_read = current_value;
    }

    // Return the time passed (in ns) since last time update_time was called
    return (delta_ticks * 1000000000ull) / frequency;
}

void Timer::set_compare(u64 compare)
{

Review comment (suggestion):
@Hendiadyoin1 if this PR is merged before #25800, you should maybe also change this to use your new CSR definitions.

Code after:
    auto timer = adopt_lock_ref(*new Timer);
    s_the = timer.ptr();
    return timer;
}

Timer& Timer::the()
{
    VERIFY(s_the);
    return *s_the;
}

u64 Timer::current_ticks()
{
    return RISCV64::CSR::read(RISCV64::CSR::Address::TIME);
}

void Timer::handle_interrupt()
{
    if (m_callback)
        m_callback();
    set_compare(current_ticks() + m_interrupt_interval);
}

void Timer::disable()
{
    RISCV64::CSR::clear_bits(RISCV64::CSR::Address::SIE, 1 << (to_underlying(CSR::SCAUSE::SupervisorTimerInterrupt) & ~CSR::SCAUSE_INTERRUPT_MASK));
}

u64 Timer::update_time(u64& seconds_since_boot, u32& ticks_this_second, bool query_only)
{
    // Should only be called by the time keeper interrupt handler!
    u64 current_value = current_ticks();
    u64 delta_ticks = m_main_counter_drift;
    if (current_value >= m_main_counter_last_read) {
        delta_ticks += current_value - m_main_counter_last_read;
    } else {
        // the counter wrapped around
        delta_ticks += (NumericLimits<u64>::max() - m_main_counter_last_read + 1) + current_value;
    }

    u64 ticks_since_last_second = (u64)ticks_this_second + delta_ticks;
    auto frequency = ticks_per_second();
    seconds_since_boot += ticks_since_last_second / frequency;
    ticks_this_second = ticks_since_last_second % frequency;

    if (!query_only) {
        m_main_counter_drift = 0;
        m_main_counter_last_read = current_value;
    }

    // Return the time passed (in ns) since last time update_time was called
Repository: SerenityOS/serenity
File: Kernel/Arch/aarch64/Time/PL031.h (C/C++)

Code before:
/*
 * Copyright (c) 2025, the SerenityOS developers
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <AK/StdLibExtras.h>
#include <AK/Time.h>
#include <Kernel/Memory/TypedMapping.h>

// https://developer.arm.com/documentation/ddi0224/c/?lang=en

namespace Kernel {

class PL031 {
public:
    struct RTCRegisters {
        u32 RTCDR;
        u32 RTCMR;
        u32 RTCLR;
        u32 RTCCR;
        u32 RTCIMSC;
        u32 RTCRIS;
        u32 RTCMIS;
        u32 RTCICR;
        u32 reserved[1008];
        u32 RTCPeriphID0;
        u32 RTCPeriphID1;
        u32 RTCPeriphID2;
        u32 RTCPeriphID3;
        u32 RTCPCellID0;
        u32 RTCPCellID1;
        u32 RTCPCellID2;
        u32 RTCPCellID3;
    };
    static_assert(AssertSize<RTCRegisters, 0x1000>());

    PL031(Memory::TypedMapping<RTCRegisters volatile>);

    static RawPtr<PL031> get_instance();

    UnixDateTime boot_time() { return UnixDateTime::from_seconds_since_epoch(m_boot_time); }

Review comment (suggestion):
We usually use more descriptive names (typically similar to the register description/long name from the datasheet) for registers instead of the short abbreviated ones.

You can take some inspiration from the datasheet register description and change the struct to maybe something like this (maybe also take a look at the GIC driver):
```c++
struct RTCRegisters {
    u32 data;  // RTCDR
    u32 match; // RTCMR
    [...]
};
```
(adding a comment with the "offical" register name is nice so you can find it more easily in the datasheet.)

Granted, `data` isn't very descriptive either, but at least it's better than `RTCDR`.

Code after:
/*
 * Copyright (c) 2025, the SerenityOS developers
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <AK/StdLibExtras.h>
#include <AK/Time.h>
#include <Kernel/Memory/TypedMapping.h>

// https://developer.arm.com/documentation/ddi0224/c/?lang=en

namespace Kernel {

class PL031 {
public:
    struct RTCRegisters {
        u32 data;                        // RTCDR
        u32 match;                       // RTCMR
        u32 load;                        // RTCLR
        u32 control;                     // RTCCR
        u32 interrupt_mask_set_or_clear; // RTCIMSC
        u32 raw_interrupt_status;        // RTCRIS
        u32 masked_interrupt_status;     // RTCMIS
        u32 interrupt_clear_register;    // RTCICR
        u32 reserved[1008];
        u32 peripheral_id_bits_7_0;   // RTCPeriphID0
        u32 peripheral_id_bits_15_8;  // RTCPeriphID1
        u32 peripheral_id_bits_23_16; // RTCPeriphID2
        u32 peripheral_id_bits_31_24; // RTCPeriphID3
        u32 primecell_id_bits_7_0;    // RTCPCellID0
        u32 primecell_id_bits_15_8;   // RTCPCellID1
        u32 primecell_id_bits_23_16;  // RTCPCellID2
        u32 primecell_id_bits_31_24;  // RTCPCellID3
    };
    static_assert(AssertSize<RTCRegisters, 0x1000>());

    PL031(Memory::TypedMapping<RTCRegisters volatile>);

    static RawPtr<PL031> the();

    UnixDateTime boot_time() { return UnixDateTime::from_seconds_since_epoch(m_boot_time); }
Repository: SerenityOS/serenity
File: Tests/LibGfx/TestImageDecoder.cpp (C++)

Code before:
    EXPECT(Gfx::JPEGXLImageDecoderPlugin::sniff(file->bytes()));
    auto plugin_decoder = TRY_OR_FAIL(Gfx::JPEGXLImageDecoderPlugin::create(file->bytes()));

    verify_checkerboard(*plugin_decoder);
}

TEST_CASE(test_jxl_icc)
{
    auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("jxl/icc.jxl"sv)));
    EXPECT(Gfx::JPEGXLImageDecoderPlugin::sniff(file->bytes()));
    auto plugin_decoder = TRY_OR_FAIL(Gfx::JPEGXLImageDecoderPlugin::create(file->bytes()));
    EXPECT(TRY_OR_FAIL(plugin_decoder->icc_data()).has_value());
    EXPECT_EQ(TRY_OR_FAIL(plugin_decoder->icc_data()).value().size(), 2644u);

    verify_checkerboard(*plugin_decoder);
}

TEST_CASE(test_dds)
{
    Array file_names = {
        TEST_INPUT("dds/catdog-alert-29x29.dds"sv),
        TEST_INPUT("dds/catdog-alert-32x32.dds"sv)
    };

    for (auto file_name : file_names) {
        auto file = TRY_OR_FAIL(Core::MappedFile::map(file_name));
        EXPECT(Gfx::DDSImageDecoderPlugin::sniff(file->bytes()));
        auto plugin_decoder = TRY_OR_FAIL(Gfx::DDSImageDecoderPlugin::create(file->bytes()));
        TRY_OR_FAIL(expect_single_frame(*plugin_decoder));
    }
}


Review comment (bug):
nit: `Gfx::Color(0x0b, 0x11, 0x12)` is shorter and doesn't drag string conversions into this.

Also, very nit: could compare all pixel to a decoded `png/buggie.png` (or is this a lossy image?)

Code after:
    EXPECT(Gfx::JPEGXLImageDecoderPlugin::sniff(file->bytes()));
    auto plugin_decoder = TRY_OR_FAIL(Gfx::JPEGXLImageDecoderPlugin::create(file->bytes()));

    verify_checkerboard(*plugin_decoder);
}

TEST_CASE(test_jxl_icc)
{
    auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("jxl/icc.jxl"sv)));
    EXPECT(Gfx::JPEGXLImageDecoderPlugin::sniff(file->bytes()));
    auto plugin_decoder = TRY_OR_FAIL(Gfx::JPEGXLImageDecoderPlugin::create(file->bytes()));
    EXPECT(TRY_OR_FAIL(plugin_decoder->icc_data()).has_value());
    EXPECT_EQ(TRY_OR_FAIL(plugin_decoder->icc_data()).value().size(), 2644u);

    verify_checkerboard(*plugin_decoder);
}

TEST_CASE(test_jxl_palette_and_groups)
{
    auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("jxl/palette_and_groups.jxl"sv)));
    EXPECT(Gfx::JPEGXLImageDecoderPlugin::sniff(file->bytes()));
    auto plugin_decoder = TRY_OR_FAIL(Gfx::JPEGXLImageDecoderPlugin::create(file->bytes()));

    TRY_OR_FAIL(expect_single_frame_of_size(*plugin_decoder, { 64, 138 }));
    auto frame = TRY_OR_FAIL(plugin_decoder->frame(0));
    EXPECT_EQ(frame.image->get_pixel(20, 130), Gfx::Color::from_string("#0b1112"sv));
}

TEST_CASE(test_dds)
{
    Array file_names = {
        TEST_INPUT("dds/catdog-alert-29x29.dds"sv),
        TEST_INPUT("dds/catdog-alert-32x32.dds"sv)
    };

    for (auto file_name : file_names) {
        auto file = TRY_OR_FAIL(Core::MappedFile::map(file_name));
        EXPECT(Gfx::DDSImageDecoderPlugin::sniff(file->bytes()));
        auto plugin_decoder = TRY_OR_FAIL(Gfx::DDSImageDecoderPlugin::create(file->bytes()));
        TRY_OR_FAIL(expect_single_frame(*plugin_decoder));
    }
}
Repository: SerenityOS/serenity
File: Tests/AK/TestFloatingPointStringConversions.cpp (C++)

Code before:
/*
 * Copyright (c) 2023, Tomás Simões <tomasprsimoes@tecnico.ulisboa.pt>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/FloatingPointStringConversions.h>
#include <AK/StringView.h>
#include <AK/UFixedBigInt.h>
#include <LibTest/TestCase.h>

using namespace AK;

TEST_CASE(basic_integer)
{
    auto const* input = "123";
    auto result = parse_first_floating_point<double>(input, input + 3);
    EXPECT_EQ(result.error, FloatingPointError::None);
    EXPECT_APPROXIMATE(result.value, 123.0);
    EXPECT_EQ(result.end_ptr, input + 3);
}

TEST_CASE(decimal_number)
{
    auto const* input = "123.45";
    auto result = parse_first_floating_point<double>(input, input + 6);
    EXPECT_EQ(result.error, FloatingPointError::None);
    EXPECT_APPROXIMATE(result.value, 123.45);
    EXPECT_EQ(result.end_ptr, input + 6);
}

TEST_CASE(exponent_notation)
{
    auto const* input = "1.5e3";
    auto result = parse_first_floating_point<double>(input, input + 5);
    EXPECT_EQ(result.error, FloatingPointError::None);
    EXPECT_APPROXIMATE(result.value, 1500.0);
    EXPECT_EQ(result.end_ptr, input + 5);
}

TEST_CASE(negative_number)

Review comment (suggestion):
Instead of duplicating code 20 million times, let's define an array of tests in the form:
```cpp
struct TestCase {
  StringView test;
  double expected_result;
  AK::FloatingPointError expected_error;
};

constexpr auto test_cases = to_array<TestCase>({
  { "123"sv, 123, AK::FloatingPointError::None },
  ...
});
```
and then iterate over it in a single test case. See how it's done, e. g., [here](https://github.com/SerenityOS/serenity/blob/master/Tests/LibHTTP/TestHttp11Connection.cpp#L23).

Code after:
/*
 * Copyright (c) 2025, Tomás Simões <tomasprsimoes@tecnico.ulisboa.pt>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/FloatingPointStringConversions.h>
#include <AK/UFixedBigInt.h>
#include <LibTest/TestCase.h>

using namespace AK;

enum class ParserType {
    Regular,
    Hexfloat,
    Complete
};

struct FloatingPointTestCase {
    StringView input;
    double expected_value;
    FloatingPointError expected_error;
    int expected_end_offset;
    ParserType parser_type;
};

static void floating_point_parsing_helper(FloatingPointTestCase const& test_case)
{
    auto const* input = test_case.input.characters_without_null_termination();
    size_t const input_length = test_case.input.length();

    switch (test_case.parser_type) {
    case ParserType::Regular: {
        auto result = parse_first_floating_point<double>(input, input + input_length);
        EXPECT_EQ(result.error, test_case.expected_error);
        // If the error is NoOrInvalidInput, parse_first_floating_point might return NaN/null
        if (test_case.expected_error != FloatingPointError::NoOrInvalidInput) {
            EXPECT_EQ(result.value, test_case.expected_value);
            EXPECT_EQ(result.end_ptr, input + test_case.expected_end_offset);
        }
        break;
Repository: SerenityOS/serenity
File: Tests/AK/TestFloatingPointStringConversions.cpp (C++)

Code before:
/*
 * Copyright (c) 2023, Tomás Simões <tomasprsimoes@tecnico.ulisboa.pt>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/FloatingPointStringConversions.h>
#include <AK/StringView.h>
#include <AK/UFixedBigInt.h>
#include <LibTest/TestCase.h>

using namespace AK;

TEST_CASE(basic_integer)
{
    auto const* input = "123";
    auto result = parse_first_floating_point<double>(input, input + 3);
    EXPECT_EQ(result.error, FloatingPointError::None);
    EXPECT_APPROXIMATE(result.value, 123.0);
    EXPECT_EQ(result.end_ptr, input + 3);
}

TEST_CASE(decimal_number)
{
    auto const* input = "123.45";
    auto result = parse_first_floating_point<double>(input, input + 6);
    EXPECT_EQ(result.error, FloatingPointError::None);
    EXPECT_APPROXIMATE(result.value, 123.45);
    EXPECT_EQ(result.end_ptr, input + 6);
}

TEST_CASE(exponent_notation)
{
    auto const* input = "1.5e3";
    auto result = parse_first_floating_point<double>(input, input + 5);
    EXPECT_EQ(result.error, FloatingPointError::None);
    EXPECT_APPROXIMATE(result.value, 1500.0);
    EXPECT_EQ(result.end_ptr, input + 5);
}

TEST_CASE(negative_number)
{
    auto const* input = "-67.89";
    auto result = parse_first_floating_point<double>(input, input + 6);

Review comment (suggestion):
The contract of `parse_*_floating_point` is that its result is precisely the double value as parsed by C++ compiler. So, use `EXPECT_EQ` instead of `EXPECT_APPROXIMATE`.

Code after:
/*
 * Copyright (c) 2025, Tomás Simões <tomasprsimoes@tecnico.ulisboa.pt>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/FloatingPointStringConversions.h>
#include <AK/UFixedBigInt.h>
#include <LibTest/TestCase.h>

using namespace AK;

enum class ParserType {
    Regular,
    Hexfloat,
    Complete
};

struct FloatingPointTestCase {
    StringView input;
    double expected_value;
    FloatingPointError expected_error;
    int expected_end_offset;
    ParserType parser_type;
};

static void floating_point_parsing_helper(FloatingPointTestCase const& test_case)
{
    auto const* input = test_case.input.characters_without_null_termination();
    size_t const input_length = test_case.input.length();

    switch (test_case.parser_type) {
    case ParserType::Regular: {
        auto result = parse_first_floating_point<double>(input, input + input_length);
        EXPECT_EQ(result.error, test_case.expected_error);
        // If the error is NoOrInvalidInput, parse_first_floating_point might return NaN/null
        if (test_case.expected_error != FloatingPointError::NoOrInvalidInput) {
            EXPECT_EQ(result.value, test_case.expected_value);
            EXPECT_EQ(result.end_ptr, input + test_case.expected_end_offset);
        }
        break;
    }
    case ParserType::Hexfloat: {
        auto result = parse_first_hexfloat_until_zero_character<double>(input);