gcem icon indicating copy to clipboard operation
gcem copied to clipboard

gcem::atan2 deviates from std::atan2

Open EmergReanimator opened this issue 5 months ago • 2 comments

https://en.cppreference.com/w/cpp/numeric/math/atan2

// Define a small epsilon for floating-point comparisons
const double EPSILON = 1e-5;

#if (CONFIG_USE_GCEM)
namespace math
{
    float atan2f(float y, float x) { return gcem::atan2(x,y); }
}
#else
namespace math
{
    float atan2f(float y, float x) { return std::atan2(x,y); }
}
#endif


/*
 *     On success, these functions return the principal value of the arc tangent of y/x in radians; the return value is in the range [-pi, pi].
 *
 *     If y is +0 (-0) and x is less than 0, +pi (-pi) is returned.
 *
 *     If y is +0 (-0) and x is greater than 0, +0 (-0) is returned.
 *
 *     If y is less than 0 and x is +0 or -0, -pi/2 is returned.
 *
 *     If y is greater than 0 and x is +0 or -0, pi/2 is returned.
 *
 *     If either x or y is NaN, a NaN is returned.
 *
 *     If y is +0 (-0) and x is -0, +pi (-pi) is returned.
 *
 *     If y is +0 (-0) and x is +0, +0 (-0) is returned.
 *
 *     If y is a finite value greater (less) than 0, and x is negative infinity, +pi (-pi) is returned.
 *
 *     If y is a finite value greater (less) than 0, and x is positive infinity, +0 (-0) is returned.
 *
 *     If y is positive infinity (negative infinity), and x is finite, pi/2 (-pi/2) is returned.
 *
 *     If y is positive infinity (negative infinity) and x is negative infinity, +3*pi/4 (-3*pi/4) is returned.
 *
 *     If y is positive infinity (negative infinity) and x is positive infinity, +pi/4 (-pi/4) is returned.
 *
 */

// Test case for general positive values in the first quadrant
TEST(TestMath, PositiveValuesQuadrant1) {
    EXPECT_NEAR(math::atan2f(1.0, 1.0), M_PI / 4.0, EPSILON);
    EXPECT_NEAR(math::atan2f(0.5, 0.5), M_PI / 4.0, EPSILON);
    EXPECT_NEAR(math::atan2f(2.0, 1.0), std::atan(2.0), EPSILON); // y > x
    EXPECT_NEAR(math::atan2f(1.0, 2.0), std::atan(0.5), EPSILON); // x > y
}

// Test case for general values in the second quadrant (y > 0, x < 0)
TEST(TestMath, Quadrant2) {
    EXPECT_NEAR(math::atan2f(1.0, -1.0), 3.0 * M_PI / 4.0, EPSILON);
    EXPECT_NEAR(math::atan2f(0.5, -0.5), 3.0 * M_PI / 4.0, EPSILON);
    EXPECT_NEAR(math::atan2f(2.0, -1.0), M_PI - std::atan(2.0), EPSILON);
    EXPECT_NEAR(math::atan2f(1.0, -2.0), M_PI - std::atan(0.5), EPSILON);
}

// Test case for general values in the third quadrant (y < 0, x < 0)
TEST(TestMath, Quadrant3) {
    EXPECT_NEAR(math::atan2f(-1.0, -1.0), -3.0 * M_PI / 4.0, EPSILON);
    EXPECT_NEAR(math::atan2f(-0.5, -0.5), -3.0 * M_PI / 4.0, EPSILON);
    EXPECT_NEAR(math::atan2f(-2.0, -1.0), -M_PI + std::atan(2.0), EPSILON);
    EXPECT_NEAR(math::atan2f(-1.0, -2.0), -M_PI + std::atan(0.5), EPSILON);
}

// Test case for general values in the fourth quadrant (y < 0, x > 0)
TEST(TestMath, Quadrant4) {
    EXPECT_NEAR(math::atan2f(-1.0, 1.0), -M_PI / 4.0, EPSILON);
    EXPECT_NEAR(math::atan2f(-0.5, 0.5), -M_PI / 4.0, EPSILON);
    EXPECT_NEAR(math::atan2f(-2.0, 1.0), -std::atan(2.0), EPSILON);
    EXPECT_NEAR(math::atan2f(-1.0, 2.0), -std::atan(0.5), EPSILON);
}

// Test cases for points on the positive x-axis (y = 0, x > 0)
TEST(TestMath, PositiveXAxis) {
    EXPECT_NEAR(math::atan2f(0.0, 1.0), 0.0, EPSILON);
    EXPECT_NEAR(math::atan2f(0.0, 100.0), 0.0, EPSILON);
    // atan2(+0, x) where x > 0 is +0
    EXPECT_NEAR(math::atan2f(0.0, 5.0), 0.0, EPSILON);
}

// Test cases for points on the negative x-axis (y = 0, x < 0)
TEST(TestMath, NegativeXAxis) {
    EXPECT_NEAR(math::atan2f(0.0, -1.0), M_PI, EPSILON);
    EXPECT_NEAR(math::atan2f(0.0, -100.0), M_PI, EPSILON);
    // atan2(+0, x) where x < 0 is +PI
    EXPECT_NEAR(math::atan2f(0.0, -5.0), M_PI, EPSILON);
}

// Test cases for points on the positive y-axis (y > 0, x = 0)
TEST(TestMath, PositiveYAxis) {
    EXPECT_NEAR(math::atan2f(1.0, 0.0), M_PI / 2.0, EPSILON);
    EXPECT_NEAR(math::atan2f(100.0, 0.0), M_PI / 2.0, EPSILON);
    // atan2(y, +0) where y > 0 is +PI/2
    EXPECT_NEAR(math::atan2f(5.0, 0.0), M_PI / 2.0, EPSILON);
}

// Test cases for points on the negative y-axis (y < 0, x = 0)
TEST(TestMath, NegativeYAxis) {
    EXPECT_NEAR(math::atan2f(-1.0, 0.0), -M_PI / 2.0, EPSILON);
    EXPECT_NEAR(math::atan2f(-100.0, 0.0), -M_PI / 2.0, EPSILON);
    // atan2(y, +0) where y < 0 is -PI/2
    EXPECT_NEAR(math::atan2f(-5.0, 0.0), -M_PI / 2.0, EPSILON);
}

// Test cases for origin (0,0) - special case, typically 0 or NaN depending on implementation/standard
TEST(TestMath, Origin) {
    // C++ standard specifies atan2(0,0) is 0.
    // However, some implementations might return NaN if x and y are both -0.0
    // For standard C++, this should be 0.
    EXPECT_NEAR(math::atan2f(0.0, 0.0), 0.0, EPSILON);
}


// Test cases involving infinities
TEST(TestMath, Infinities) {
    const double infinity = std::numeric_limits<double>::infinity();
    const double neg_infinity = -std::numeric_limits<double>::infinity();

    // atan2(+-0, -infinity) is +-PI
    EXPECT_NEAR(math::atan2f(0.0, neg_infinity), M_PI, EPSILON);
    EXPECT_NEAR(math::atan2f(-0.0, neg_infinity), -M_PI, EPSILON);

    // atan2(+-0, +infinity) is +-0
    EXPECT_NEAR(math::atan2f(0.0, infinity), 0.0, EPSILON);
    EXPECT_NEAR(math::atan2f(-0.0, infinity), -0.0, EPSILON); // Note: -0.0 might be treated as 0.0 by EXPECT_NEAR

    // atan2(finite, +infinity) is +-0
    EXPECT_NEAR(math::atan2f(1.0, infinity), 0.0, EPSILON);
    EXPECT_NEAR(math::atan2f(-1.0, infinity), -0.0, EPSILON);

    // atan2(finite, -infinity) is +-PI
    EXPECT_NEAR(math::atan2f(1.0, neg_infinity), M_PI, EPSILON);
    EXPECT_NEAR(math::atan2f(-1.0, neg_infinity), -M_PI, EPSILON);

    // atan2(+-infinity, finite) is +-PI/2
    EXPECT_NEAR(math::atan2f(infinity, 1.0), M_PI / 2.0, EPSILON);
    EXPECT_NEAR(math::atan2f(neg_infinity, 1.0), -M_PI / 2.0, EPSILON);

    // atan2(+-infinity, +infinity) is +-PI/4
    EXPECT_NEAR(math::atan2f(infinity, infinity), M_PI / 4.0, EPSILON);
    EXPECT_NEAR(math::atan2f(neg_infinity, infinity), -M_PI / 4.0, EPSILON);

    // atan2(+-infinity, -infinity) is +-3PI/4
    EXPECT_NEAR(math::atan2f(infinity, neg_infinity), 3.0 * M_PI / 4.0, EPSILON);
    EXPECT_NEAR(math::atan2f(neg_infinity, neg_infinity), -3.0 * M_PI / 4.0, EPSILON);
}

// Test cases involving NaNs (Not-a-Number)
TEST(TestMath, NaNs) {
    const double nan = std::numeric_limits<double>::quiet_NaN();

    // If y is NaN, result is NaN (unless x is +-Inf)
    ASSERT_TRUE(std::isnan(math::atan2f(nan, 1.0)));
    ASSERT_TRUE(std::isnan(math::atan2f(nan, -1.0)));
    ASSERT_TRUE(std::isnan(math::atan2f(nan, 0.0)));
    ASSERT_TRUE(std::isnan(math::atan2f(nan, nan)));

    // If x is NaN, result is NaN (unless y is +-Inf)
    ASSERT_TRUE(std::isnan(math::atan2f(1.0, nan)));
    ASSERT_TRUE(std::isnan(math::atan2f(-1.0, nan)));
    ASSERT_TRUE(std::isnan(math::atan2f(0.0, nan)));
}

// Test with subnormal numbers (numbers very close to zero)
TEST(TestMath, SubnormalNumbers) {
    double subnormal_val = std::numeric_limits<double>::denorm_min(); // Smallest positive denormalized value

    // Test with subnormal y and normal x
    EXPECT_NEAR(math::atan2f(subnormal_val, 1.0), 0.0, EPSILON);
    EXPECT_NEAR(math::atan2f(-subnormal_val, 1.0), -0.0, EPSILON);

    // Test with normal y and subnormal x
    EXPECT_NEAR(math::atan2f(1.0, subnormal_val), M_PI / 2.0, EPSILON);
    EXPECT_NEAR(math::atan2f(1.0, -subnormal_val), M_PI / 2.0, EPSILON); // Should be PI/2 as -0.0 is treated correctly
}

// Test with large magnitude numbers
TEST(TestMath, LargeNumbers) {
    double large_val = std::numeric_limits<double>::max() / 2.0; // A very large number, not infinity

    EXPECT_NEAR(math::atan2f(large_val, large_val), M_PI / 4.0, EPSILON);
    EXPECT_NEAR(math::atan2f(-large_val, large_val), -M_PI / 4.0, EPSILON);
    EXPECT_NEAR(math::atan2f(large_val, -large_val), 3.0 * M_PI / 4.0, EPSILON);
    EXPECT_NEAR(math::atan2f(-large_val, -large_val), -3.0 * M_PI / 4.0, EPSILON);

    EXPECT_NEAR(math::atan2f(1.0, large_val), 0.0, EPSILON);
    EXPECT_NEAR(math::atan2f(large_val, 1.0), M_PI / 2.0, EPSILON);
}

// Test for signed zeros
TEST(TestMath, SignedZeros) {
    // atan2(0, +x) for x > 0
    EXPECT_NEAR(math::atan2f(0.0, 1.0), 0.0, EPSILON);
    EXPECT_NEAR(math::atan2f(-0.0, 1.0), -0.0, EPSILON); // Should return -0.0

    // atan2(0, -x) for x > 0
    EXPECT_NEAR(math::atan2f(0.0, -1.0), M_PI, EPSILON);
    EXPECT_NEAR(math::atan2f(-0.0, -1.0), -M_PI, EPSILON);

    // atan2(+y, 0) for y > 0
    EXPECT_NEAR(math::atan2f(1.0, 0.0), M_PI / 2.0, EPSILON);
    EXPECT_NEAR(math::atan2f(1.0, -0.0), M_PI / 2.0, EPSILON);

    // atan2(-y, 0) for y > 0
    EXPECT_NEAR(math::atan2f(-1.0, 0.0), -M_PI / 2.0, EPSILON);
    EXPECT_NEAR(math::atan2f(-1.0, -0.0), -M_PI / 2.0, EPSILON);

    // atan2(+-0, +-0)
    EXPECT_NEAR(math::atan2f(0.0, 0.0), 0.0, EPSILON);
    EXPECT_NEAR(math::atan2f(-0.0, 0.0), -0.0, EPSILON);
    EXPECT_NEAR(math::atan2f(0.0, -0.0), M_PI, EPSILON);
    EXPECT_NEAR(math::atan2f(-0.0, -0.0), -M_PI, EPSILON);
}
``

EmergReanimator avatar Jul 24 '25 12:07 EmergReanimator