diff --git a/quaddtype/numpy_quaddtype/src/ops.hpp b/quaddtype/numpy_quaddtype/src/ops.hpp index 754db054..f3a53120 100644 --- a/quaddtype/numpy_quaddtype/src/ops.hpp +++ b/quaddtype/numpy_quaddtype/src/ops.hpp @@ -243,6 +243,15 @@ quad_atanh(const Sleef_quad *op) return Sleef_atanhq1_u10(*op); } +static inline Sleef_quad +quad_degrees(const Sleef_quad *op) +{ + // degrees = radians * 180 / π + static const Sleef_quad one_eighty = sleef_q(+0x1680000000000LL, 0x0000000000000000ULL, 7); // 180.0 in quad + Sleef_quad ratio = Sleef_divq1_u05(one_eighty, SLEEF_M_PIq); + return Sleef_mulq1_u05(*op, ratio); +} + static inline Sleef_quad quad_radians(const Sleef_quad *op) { @@ -455,6 +464,16 @@ ld_atanh(const long double *op) return atanhl(*op); } +static inline long double +ld_degrees(const long double *op) +{ + // degrees = radians * 180 / π + #ifndef M_PI + #define M_PI 3.14159265358979323846 + #endif + return (*op) * (180.0L / static_cast(M_PI)); +} + static inline long double ld_radians(const long double *op) { diff --git a/quaddtype/numpy_quaddtype/src/umath/unary_ops.cpp b/quaddtype/numpy_quaddtype/src/umath/unary_ops.cpp index 944033fc..b0694ddf 100644 --- a/quaddtype/numpy_quaddtype/src/umath/unary_ops.cpp +++ b/quaddtype/numpy_quaddtype/src/umath/unary_ops.cpp @@ -250,6 +250,12 @@ init_quad_unary_ops(PyObject *numpy) if (create_quad_unary_ufunc(numpy, "arctanh") < 0) { return -1; } + if (create_quad_unary_ufunc(numpy, "degrees") < 0) { + return -1; + } + if (create_quad_unary_ufunc(numpy, "rad2deg") < 0) { + return -1; + } if (create_quad_unary_ufunc(numpy, "radians") < 0) { return -1; } diff --git a/quaddtype/release_tracker.md b/quaddtype/release_tracker.md index b2e2250c..ee9ef67e 100644 --- a/quaddtype/release_tracker.md +++ b/quaddtype/release_tracker.md @@ -54,16 +54,10 @@ | arcsinh | ✅ | ✅ | | arccosh | ✅ | ✅ | | arctanh | ✅ | ✅ | -| degrees | | | -| radians | ✅ | ✅ | -| deg2rad | ✅ | ✅ | -| rad2deg | | | -| bitwise_and | | | -| bitwise_or | | | -| bitwise_xor | | | -| invert | | | -| left_shift | | | -| right_shift | | | +| degrees | ✅ | ✅ | +| radians | ✅ | ✅ | +| deg2rad | ✅ | ✅ | +| rad2deg | ✅ | ✅ | | greater | ✅ | ✅ | | greater_equal | ✅ | ✅ | | less | ✅ | ✅ | diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index 40063cdf..12f00b81 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -1906,6 +1906,46 @@ def test_hypot(x1, x2, expected): np.testing.assert_allclose(float(result), expected, rtol=1e-13) +@pytest.mark.parametrize("op", [np.degrees, np.rad2deg]) +@pytest.mark.parametrize("radians,expected_degrees", [ + # Basic conversions + (0.0, 0.0), + (np.pi / 6, 30.0), + (np.pi / 4, 45.0), + (np.pi / 3, 60.0), + (np.pi / 2, 90.0), + (np.pi, 180.0), + (3 * np.pi / 2, 270.0), + (2 * np.pi, 360.0), + # Negative values + (-np.pi / 2, -90.0), + (-np.pi, -180.0), + # Special values + (np.inf, np.inf), + (-np.inf, -np.inf), + (np.nan, np.nan), + # Edge cases + (-0.0, -0.0), +]) +def test_degrees_rad2deg(op, radians, expected_degrees): + """Test degrees and rad2deg ufuncs convert radians to degrees""" + q_rad = QuadPrecision(radians) + result = op(q_rad) + + assert isinstance(result, QuadPrecision) + + if np.isnan(expected_degrees): + assert np.isnan(float(result)) + elif np.isinf(expected_degrees): + assert np.isinf(float(result)) + if expected_degrees > 0: + assert float(result) > 0 + else: + assert float(result) < 0 + else: + np.testing.assert_allclose(float(result), expected_degrees, rtol=1e-13) + + @pytest.mark.parametrize("op", [np.radians, np.deg2rad]) @pytest.mark.parametrize("degrees,expected_radians", [ # Basic conversions @@ -1932,9 +1972,9 @@ def test_radians(op, degrees, expected_radians): """Test radians and deg2rad ufuncs convert degrees to radians""" q_deg = QuadPrecision(degrees) result = op(q_deg) - + assert isinstance(result, QuadPrecision) - + if np.isnan(expected_radians): assert np.isnan(float(result)) elif np.isinf(expected_radians):