From 5a8781393aa5c45c790b87d8d0f2f4731aacd796 Mon Sep 17 00:00:00 2001 From: Martin Janiczek Date: Sat, 30 Dec 2023 16:27:41 +0100 Subject: [PATCH] AK: Cover TestComplex with more tests Related: - video detailing the process of writing these tests: https://www.youtube.com/watch?v=enxglLlALvI - PR fixing bugs the above effort found: https://github.com/SerenityOS/serenity/pull/22025 --- AK/Complex.h | 8 ++ Tests/AK/TestComplex.cpp | 191 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+) diff --git a/AK/Complex.h b/AK/Complex.h index e9963d31f6..d488a175e6 100644 --- a/AK/Complex.h +++ b/AK/Complex.h @@ -277,6 +277,14 @@ static constexpr Complex cexp(Complex const& a) } } +template +struct AK::Formatter> : Formatter { + ErrorOr format(FormatBuilder& builder, AK::Complex c) + { + return Formatter::format(builder, TRY(String::formatted("{}{:+}i", c.real(), c.imag()))); + } +}; + #if USING_AK_GLOBALLY using AK::approx_eq; using AK::cexp; diff --git a/Tests/AK/TestComplex.cpp b/Tests/AK/TestComplex.cpp index 4757430024..d74f83ad4a 100644 --- a/Tests/AK/TestComplex.cpp +++ b/Tests/AK/TestComplex.cpp @@ -8,6 +8,33 @@ #include +using namespace Test::Randomized; + +namespace { + +Complex gen_complex() +{ + auto r = Gen::number_f64(); + auto i = Gen::number_f64(); + return Complex(r, i); +} + +Complex gen_complex(f64 min, f64 max) +{ + auto r = Gen::number_f64(min, max); + auto i = Gen::number_f64(min, max); + return Complex(r, i); +} + +} + +template +void expect_approximate_complex(Complex a, Complex b) +{ + EXPECT_APPROXIMATE(a.real(), b.real()); + EXPECT_APPROXIMATE(a.imag(), b.imag()); +} + TEST_CASE(Complex) { auto a = Complex { 1.f, 1.f }; @@ -68,3 +95,167 @@ TEST_CASE(real_operators_regression) EXPECT_EQ(c2.imag(), -0.5); } } + +TEST_CASE(constructor_0_is_origin) +{ + auto c = Complex(); + EXPECT_EQ(c.real(), 0L); + EXPECT_EQ(c.imag(), 0L); +} + +RANDOMIZED_TEST_CASE(constructor_1) +{ + GEN(r, Gen::number_f64()); + auto c = Complex(r); + EXPECT_EQ(c.real(), r); + EXPECT_EQ(c.imag(), 0L); +} + +RANDOMIZED_TEST_CASE(constructor_2) +{ + GEN(r, Gen::number_f64()); + GEN(i, Gen::number_f64()); + auto c = Complex(r, i); + EXPECT_EQ(c.real(), r); + EXPECT_EQ(c.imag(), i); +} + +RANDOMIZED_TEST_CASE(magnitude_squared) +{ + GEN(c, gen_complex()); + auto magnitude_squared = c.magnitude_squared(); + auto magnitude = c.magnitude(); + EXPECT_APPROXIMATE(magnitude_squared, magnitude * magnitude); +} + +RANDOMIZED_TEST_CASE(from_polar_magnitude) +{ + // Magnitude only makes sense non-negative, but the library allows it to be negative. + GEN(m, Gen::number_f64(-1000, 1000)); + GEN(p, Gen::number_f64(-1000, 1000)); + auto c = Complex::from_polar(m, p); + EXPECT_APPROXIMATE(c.magnitude(), abs(m)); +} + +RANDOMIZED_TEST_CASE(from_polar_phase) +{ + // To have a meaningful phase, magnitude needs to be >0. + GEN(m, Gen::number_f64(1, 1000)); + GEN(p, Gen::number_f64(-1000, 1000)); + + auto c = Complex::from_polar(m, p); + + // Returned phase is in the (-pi,pi] interval. + // We need to mod from our randomly generated [-1000,1000] interval] + // down to [0,2pi) or (-2pi,0] depending on our sign. + // Then we can adjust and get into the -pi..pi range by adding/subtracting + // one last 2pi. + auto wanted_p = fmod(p, 2 * M_PI); + if (wanted_p > M_PI) + wanted_p -= 2 * M_PI; + else if (wanted_p < -M_PI) + wanted_p += 2 * M_PI; + + EXPECT_APPROXIMATE(c.phase(), wanted_p); +} + +RANDOMIZED_TEST_CASE(imag_untouched_c_plus_r) +{ + GEN(c1, gen_complex()); + GEN(r2, Gen::number_f64()); + auto c2 = c1 + r2; + EXPECT_EQ(c2.imag(), c1.imag()); +} + +RANDOMIZED_TEST_CASE(imag_untouched_c_minus_r) +{ + GEN(c1, gen_complex()); + GEN(r2, Gen::number_f64()); + auto c2 = c1 - r2; + EXPECT_EQ(c2.imag(), c1.imag()); +} + +RANDOMIZED_TEST_CASE(assignment_same_as_binop_plus) +{ + GEN(c1, gen_complex()); + GEN(c2, gen_complex()); + auto out1 = c1 + c2; + auto out2 = c1; + out2 += c2; + EXPECT_EQ(out2, out1); +} + +RANDOMIZED_TEST_CASE(assignment_same_as_binop_minus) +{ + GEN(c1, gen_complex()); + GEN(c2, gen_complex()); + auto out1 = c1 - c2; + auto out2 = c1; + out2 -= c2; + EXPECT_EQ(out2, out1); +} + +RANDOMIZED_TEST_CASE(assignment_same_as_binop_mult) +{ + GEN(c1, gen_complex(-1000, 1000)); + GEN(c2, gen_complex(-1000, 1000)); + auto out1 = c1 * c2; + auto out2 = c1; + out2 *= c2; + EXPECT_EQ(out2, out1); +} + +RANDOMIZED_TEST_CASE(assignment_same_as_binop_div) +{ + GEN(c1, gen_complex(-1000, 1000)); + GEN(c2, gen_complex(-1000, 1000)); + auto out1 = c1 / c2; + auto out2 = c1; + out2 /= c2; + EXPECT_EQ(out2, out1); +} + +RANDOMIZED_TEST_CASE(commutativity_c_c) +{ + GEN(c1, gen_complex()); + GEN(c2, gen_complex()); + expect_approximate_complex(c1 + c2, c2 + c1); + expect_approximate_complex(c1 * c2, c2 * c1); +} + +RANDOMIZED_TEST_CASE(commutativity_c_r) +{ + GEN(c, gen_complex()); + GEN(r, Gen::number_f64()); + expect_approximate_complex(r + c, c + r); + expect_approximate_complex(r * c, c * r); +} + +RANDOMIZED_TEST_CASE(unary_plus_noop) +{ + GEN(c, gen_complex()); + EXPECT_EQ(+c, c); +} + +RANDOMIZED_TEST_CASE(unary_minus_inverse) +{ + GEN(c, gen_complex()); + expect_approximate_complex(-(-c), c); +} + +RANDOMIZED_TEST_CASE(wrapping_real) +{ + GEN(c, gen_complex(-1000, 1000)); + GEN(r, Gen::number_f64(-1000, 1000)); + auto cr = Complex(r); + + expect_approximate_complex(r + c, cr + c); + expect_approximate_complex(r - c, cr - c); + expect_approximate_complex(r * c, cr * c); + expect_approximate_complex(r / c, cr / c); + + expect_approximate_complex(c + r, c + cr); + expect_approximate_complex(c - r, c - cr); + expect_approximate_complex(c * r, c * cr); + expect_approximate_complex(c / r, c / cr); +}