[libc-commits] [libc] [libc][math] Implement a fast pass for atan2f128 with 1ULP error using DyadicFloat<128>. (PR #133150)
via libc-commits
libc-commits at lists.llvm.org
Wed Mar 26 15:13:40 PDT 2025
https://github.com/lntue updated https://github.com/llvm/llvm-project/pull/133150
>From 3d757645b842c4e08f75851d4cbd4c7ff6438b9c Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue.h at gmail.com>
Date: Wed, 26 Mar 2025 20:10:59 +0000
Subject: [PATCH 1/3] [libc][math] Implement a fast pass for atan2f128 with
1ULP error using DyadicFloat<128>.
---
libc/config/linux/aarch64/entrypoints.txt | 1 +
libc/config/linux/riscv/entrypoints.txt | 1 +
libc/config/linux/x86_64/entrypoints.txt | 1 +
libc/docs/headers/math/index.rst | 2 +-
libc/include/math.yaml | 8 +
libc/src/__support/FPUtil/dyadic_float.h | 2 +-
libc/src/math/CMakeLists.txt | 1 +
libc/src/math/atan2f128.h | 21 ++
libc/src/math/generic/CMakeLists.txt | 20 ++
libc/src/math/generic/atan2f128.cpp | 201 ++++++++++++++++++++
libc/src/math/generic/atan_utils.h | 108 ++++++++++-
libc/test/src/math/CMakeLists.txt | 12 ++
libc/test/src/math/atan2f128_test.cpp | 100 ++++++++++
libc/test/src/math/smoke/CMakeLists.txt | 10 +
libc/test/src/math/smoke/atan2f128_test.cpp | 28 +++
15 files changed, 512 insertions(+), 4 deletions(-)
create mode 100644 libc/src/math/atan2f128.h
create mode 100644 libc/src/math/generic/atan2f128.cpp
create mode 100644 libc/test/src/math/atan2f128_test.cpp
create mode 100644 libc/test/src/math/smoke/atan2f128_test.cpp
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index bca0b13feb1a0..3020f924bcf1d 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -758,6 +758,7 @@ endif()
if(LIBC_TYPES_HAS_FLOAT128)
list(APPEND TARGET_LIBM_ENTRYPOINTS
# math.h C23 _Float128 entrypoints
+ libc.src.math.atan2f128
libc.src.math.canonicalizef128
libc.src.math.ceilf128
libc.src.math.copysignf128
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 9cf05ef6d5a61..479b97abfcf6b 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -639,6 +639,7 @@ endif()
if(LIBC_TYPES_HAS_FLOAT128)
list(APPEND TARGET_LIBM_ENTRYPOINTS
# math.h C23 _Float128 entrypoints
+ libc.src.math.atan2f128
libc.src.math.canonicalizef128
libc.src.math.ceilf128
libc.src.math.copysignf128
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index ac281e8d39066..9713a5420be95 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -773,6 +773,7 @@ endif()
if(LIBC_TYPES_HAS_FLOAT128)
list(APPEND TARGET_LIBM_ENTRYPOINTS
# math.h C23 _Float128 entrypoints
+ libc.src.math.atan2f128
libc.src.math.canonicalizef128
libc.src.math.ceilf128
libc.src.math.copysignf128
diff --git a/libc/docs/headers/math/index.rst b/libc/docs/headers/math/index.rst
index 16903a40c03fa..bef946f4b63d0 100644
--- a/libc/docs/headers/math/index.rst
+++ b/libc/docs/headers/math/index.rst
@@ -263,7 +263,7 @@ Higher Math Functions
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
| atan | |check| | 1 ULP | | | | 7.12.4.3 | F.10.1.3 |
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
-| atan2 | |check| | 1 ULP | | | | 7.12.4.4 | F.10.1.4 |
+| atan2 | |check| | 1 ULP | | | 1 ULP | 7.12.4.4 | F.10.1.4 |
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
| atan2pi | | | | | | 7.12.4.11 | F.10.1.11 |
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
diff --git a/libc/include/math.yaml b/libc/include/math.yaml
index bd29eb0213c4f..5c1b3b594f3a9 100644
--- a/libc/include/math.yaml
+++ b/libc/include/math.yaml
@@ -86,6 +86,14 @@ functions:
arguments:
- type: long double
- type: long double
+ - name: atan2f128
+ standards:
+ - stdc
+ return_type: float128
+ arguments:
+ - type: float128
+ - type: float128
+ guard: LIBC_TYPES_HAS_FLOAT128
- name: atanf
standards:
- stdc
diff --git a/libc/src/__support/FPUtil/dyadic_float.h b/libc/src/__support/FPUtil/dyadic_float.h
index 65d55e16d95ec..673354888d589 100644
--- a/libc/src/__support/FPUtil/dyadic_float.h
+++ b/libc/src/__support/FPUtil/dyadic_float.h
@@ -104,7 +104,7 @@ template <size_t Bits> struct DyadicFloat {
normalize();
}
- LIBC_INLINE constexpr DyadicFloat(Sign s, int e, MantissaType m)
+ LIBC_INLINE constexpr DyadicFloat(Sign s, int e, const MantissaType &m)
: sign(s), exponent(e), mantissa(m) {
normalize();
}
diff --git a/libc/src/math/CMakeLists.txt b/libc/src/math/CMakeLists.txt
index 92c80a1053c9e..dcc35f1146d06 100644
--- a/libc/src/math/CMakeLists.txt
+++ b/libc/src/math/CMakeLists.txt
@@ -61,6 +61,7 @@ add_math_entrypoint_object(atanf)
add_math_entrypoint_object(atan2)
add_math_entrypoint_object(atan2f)
add_math_entrypoint_object(atan2l)
+add_math_entrypoint_object(atan2f128)
add_math_entrypoint_object(atanh)
add_math_entrypoint_object(atanhf)
diff --git a/libc/src/math/atan2f128.h b/libc/src/math/atan2f128.h
new file mode 100644
index 0000000000000..26f7ec624940c
--- /dev/null
+++ b/libc/src/math/atan2f128.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for atan2f128 ---------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_MATH_ATAN2F128_H
+#define LLVM_LIBC_SRC_MATH_ATAN2F128_H
+
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/properties/types.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+float128 atan2f128(float128 x, float128 y);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_MATH_ATAN2F128_H
diff --git a/libc/src/math/generic/CMakeLists.txt b/libc/src/math/generic/CMakeLists.txt
index 77c6244c5e5da..50b137c76407e 100644
--- a/libc/src/math/generic/CMakeLists.txt
+++ b/libc/src/math/generic/CMakeLists.txt
@@ -4080,8 +4080,11 @@ add_header_library(
HDRS
atan_utils.h
DEPENDS
+ libc.src.__support.integer_literals
libc.src.__support.FPUtil.double_double
+ libc.src.__support.FPUtil.dyadic_float
libc.src.__support.FPUtil.multiply_add
+ libc.src.__support.FPUtil.polyeval
libc.src.__support.macros.optimization
)
@@ -4163,6 +4166,23 @@ add_entrypoint_object(
.atan2
)
+add_entrypoint_object(
+ atan2f128
+ SRCS
+ atan2f128.cpp
+ HDRS
+ ../atan2f128.h
+ DEPENDS
+ .atan_utils
+ libc.src.__support.integer_literals
+ libc.src.__support.FPUtil.dyadic_float
+ libc.src.__support.FPUtil.fp_bits
+ libc.src.__support.FPUtil.multiply_add
+ libc.src.__support.FPUtil.nearest_integer
+ libc.src.__support.macros.optimization
+ libc.src.__support.macros.properties.types
+)
+
add_entrypoint_object(
scalbln
SRCS
diff --git a/libc/src/math/generic/atan2f128.cpp b/libc/src/math/generic/atan2f128.cpp
new file mode 100644
index 0000000000000..808f41982cffc
--- /dev/null
+++ b/libc/src/math/generic/atan2f128.cpp
@@ -0,0 +1,201 @@
+//===-- Quad-precision atan2 function -------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/math/atan2f128.h"
+#include "atan_utils.h"
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/FPUtil/dyadic_float.h"
+#include "src/__support/FPUtil/multiply_add.h"
+#include "src/__support/FPUtil/nearest_integer.h"
+#include "src/__support/integer_literals.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/optimization.h" // LIBC_UNLIKELY
+#include "src/__support/macros/properties/types.h"
+#include "src/__support/uint128.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+namespace {
+
+using Float128 = fputil::DyadicFloat<128>;
+
+static constexpr Float128 ZERO = {Sign::POS, 0, 0_u128};
+static constexpr Float128 MZERO = {Sign::NEG, 0, 0_u128};
+static constexpr Float128 PI = {Sign::POS, -126,
+ 0xc90fdaa2'2168c234'c4c6628b'80dc1cd1_u128};
+static constexpr Float128 MPI = {Sign::NEG, -126,
+ 0xc90fdaa2'2168c234'c4c6628b'80dc1cd1_u128};
+static constexpr Float128 PI_OVER_2 = {
+ Sign::POS, -127, 0xc90fdaa2'2168c234'c4c6628b'80dc1cd1_u128};
+static constexpr Float128 MPI_OVER_2 = {
+ Sign::NEG, -127, 0xc90fdaa2'2168c234'c4c6628b'80dc1cd1_u128};
+static constexpr Float128 PI_OVER_4 = {
+ Sign::POS, -128, 0xc90fdaa2'2168c234'c4c6628b'80dc1cd1_u128};
+static constexpr Float128 THREE_PI_OVER_4 = {
+ Sign::POS, -128, 0x96cbe3f9'990e91a7'9394c9e8'a0a5159d_u128};
+
+// Adjustment for constant term:
+// CONST_ADJ[x_sign][y_sign][recip]
+static constexpr Float128 CONST_ADJ[2][2][2] = {
+ {{ZERO, MPI_OVER_2}, {MZERO, MPI_OVER_2}},
+ {{MPI, PI_OVER_2}, {MPI, PI_OVER_2}}};
+
+} // anonymous namespace
+
+// There are several range reduction steps we can take for atan2(y, x) as
+// follow:
+
+// * Range reduction 1: signness
+// atan2(y, x) will return a number between -PI and PI representing the angle
+// forming by the 0x axis and the vector (x, y) on the 0xy-plane.
+// In particular, we have that:
+// atan2(y, x) = atan( y/x ) if x >= 0 and y >= 0 (I-quadrant)
+// = pi + atan( y/x ) if x < 0 and y >= 0 (II-quadrant)
+// = -pi + atan( y/x ) if x < 0 and y < 0 (III-quadrant)
+// = atan( y/x ) if x >= 0 and y < 0 (IV-quadrant)
+// Since atan function is odd, we can use the formula:
+// atan(-u) = -atan(u)
+// to adjust the above conditions a bit further:
+// atan2(y, x) = atan( |y|/|x| ) if x >= 0 and y >= 0 (I-quadrant)
+// = pi - atan( |y|/|x| ) if x < 0 and y >= 0 (II-quadrant)
+// = -pi + atan( |y|/|x| ) if x < 0 and y < 0 (III-quadrant)
+// = -atan( |y|/|x| ) if x >= 0 and y < 0 (IV-quadrant)
+// Which can be simplified to:
+// atan2(y, x) = sign(y) * atan( |y|/|x| ) if x >= 0
+// = sign(y) * (pi - atan( |y|/|x| )) if x < 0
+
+// * Range reduction 2: reciprocal
+// Now that the argument inside atan is positive, we can use the formula:
+// atan(1/x) = pi/2 - atan(x)
+// to make the argument inside atan <= 1 as follow:
+// atan2(y, x) = sign(y) * atan( |y|/|x|) if 0 <= |y| <= x
+// = sign(y) * (pi/2 - atan( |x|/|y| ) if 0 <= x < |y|
+// = sign(y) * (pi - atan( |y|/|x| )) if 0 <= |y| <= -x
+// = sign(y) * (pi/2 + atan( |x|/|y| )) if 0 <= -x < |y|
+
+// * Range reduction 3: look up table.
+// After the previous two range reduction steps, we reduce the problem to
+// compute atan(u) with 0 <= u <= 1, or to be precise:
+// atan( n / d ) where n = min(|x|, |y|) and d = max(|x|, |y|).
+// An accurate polynomial approximation for the whole [0, 1] input range will
+// require a very large degree. To make it more efficient, we reduce the input
+// range further by finding an integer idx such that:
+// | n/d - idx/64 | <= 1/128.
+// In particular,
+// idx := round(2^6 * n/d)
+// Then for the fast pass, we find a polynomial approximation for:
+// atan( n/d ) ~ atan( idx/64 ) + (n/d - idx/64) * Q(n/d - idx/64)
+// For the accurate pass, we use the addition formula:
+// atan( n/d ) - atan( idx/64 ) = atan( (n/d - idx/64)/(1 + (n*idx)/(64*d)) )
+// = atan( (n - d*(idx/64))/(d + n*(idx/64)) )
+// And for the fast pass, we use degree-9 Taylor polynomial to compute the RHS:
+// atan(u) ~ P(u) = u - u^3/3 + u^5/5 - u^7/7 + u^9/9
+// with absolute errors bounded by:
+// |atan(u) - P(u)| < |u|^11 / 11 < 2^-80
+// and relative errors bounded by:
+// |(atan(u) - P(u)) / P(u)| < u^10 / 11 < 2^-73.
+
+LLVM_LIBC_FUNCTION(float128, atan2f128, (float128 y, float128 x)) {
+ using FPBits = fputil::FPBits<float128>;
+ using Float128 = fputil::DyadicFloat<128>;
+
+ FPBits x_bits(x), y_bits(y);
+ bool x_sign = x_bits.sign().is_neg();
+ bool y_sign = y_bits.sign().is_neg();
+ x_bits = x_bits.abs();
+ y_bits = y_bits.abs();
+ UInt128 x_abs = x_bits.uintval();
+ UInt128 y_abs = y_bits.uintval();
+ bool recip = x_abs < y_abs;
+ UInt128 min_abs = recip ? x_abs : y_abs;
+ UInt128 max_abs = !recip ? x_abs : y_abs;
+ unsigned min_exp = static_cast<unsigned>(min_abs >> FPBits::FRACTION_LEN);
+ unsigned max_exp = static_cast<unsigned>(max_abs >> FPBits::FRACTION_LEN);
+
+ Float128 num(FPBits(min_abs).get_val());
+ Float128 den(FPBits(max_abs).get_val());
+
+ // Check for exceptional cases, whether inputs are 0, inf, nan, or close to
+ // overflow, or close to underflow.
+ if (LIBC_UNLIKELY(max_exp >= 0x7fffU || min_exp == 0U)) {
+ if (x_bits.is_nan() || y_bits.is_nan())
+ return FPBits::quiet_nan().get_val();
+ unsigned x_except = x == 0 ? 0 : (FPBits(x_abs).is_inf() ? 2 : 1);
+ unsigned y_except = y == 0 ? 0 : (FPBits(y_abs).is_inf() ? 2 : 1);
+
+ // Exceptional cases:
+ // EXCEPT[y_except][x_except][x_is_neg]
+ // with x_except & y_except:
+ // 0: zero
+ // 1: finite, non-zero
+ // 2: infinity
+ constexpr Float128 EXCEPTS[3][3][2] = {
+ {{ZERO, PI}, {ZERO, PI}, {ZERO, PI}},
+ {{PI_OVER_2, PI_OVER_2}, {ZERO, ZERO}, {ZERO, PI}},
+ {{PI_OVER_2, PI_OVER_2},
+ {PI_OVER_2, PI_OVER_2},
+ {PI_OVER_4, THREE_PI_OVER_4}},
+ };
+
+ if ((x_except != 1) || (y_except != 1)) {
+ Float128 r = EXCEPTS[y_except][x_except][x_sign];
+ if (y_sign)
+ r.sign = (r.sign == Sign::POS) ? Sign::NEG : Sign::POS;
+ return static_cast<float128>(r);
+ }
+ }
+
+ bool final_sign = ((x_sign != y_sign) != recip);
+ Float128 const_term = CONST_ADJ[x_sign][y_sign][recip];
+ int exp_diff = den.exponent - num.exponent;
+ // We have the following bound for normalized n and d:
+ // 2^(-exp_diff - 1) < n/d < 2^(-exp_diff + 1).
+ if (LIBC_UNLIKELY(exp_diff > FPBits::FRACTION_LEN + 2)) {
+ if (final_sign)
+ const_term.sign = (const_term.sign == Sign::POS) ? Sign::NEG : Sign::POS;
+ return static_cast<float128>(const_term);
+ }
+
+ // Take 24 leading bits of num and den to convert to float for fast division.
+ // We also multiply the numerator by 64 using integer addition directly to the
+ // exponent field.
+ float num_f =
+ cpp::bit_cast<float>(static_cast<uint32_t>(num.mantissa >> 104) +
+ (6U << fputil::FPBits<float>::FRACTION_LEN));
+ float den_f = cpp::bit_cast<float>(
+ static_cast<uint32_t>(den.mantissa >> 104) +
+ (static_cast<uint32_t>(exp_diff) << fputil::FPBits<float>::FRACTION_LEN));
+
+ float k = fputil::nearest_integer(num_f / den_f);
+ unsigned idx = static_cast<unsigned>(k);
+
+ // k_f128 = idx / 64
+ Float128 k_f128(Sign::POS, -6, Float128::MantissaType(idx));
+
+ // Range reduction:
+ // atan(n/d) - atan(k) = atan((n/d - k/64) / (1 + (n/d) * (k/64)))
+ // = atan((n - d * k/64)) / (d + n * k/64))
+ // num_f128 = n - d * k/64
+ Float128 num_f128 = fputil::multiply_add(den, -k_f128, num);
+ // den_f128 = d + n * k/64
+ Float128 den_f128 = fputil::multiply_add(num, k_f128, den);
+
+ // q = (n - d * k) / (d + n * k)
+ Float128 q = fputil::quick_mul(num_f128, fputil::approx_reciprocal(den_f128));
+ // p ~ atan(q)
+ Float128 p = atan_eval(q);
+
+ Float128 r =
+ fputil::quick_add(const_term, fputil::quick_add(ATAN_I_F128[idx], p));
+ if (final_sign)
+ r.sign = (r.sign == Sign::POS) ? Sign::NEG : Sign::POS;
+
+ return static_cast<float128>(r);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/math/generic/atan_utils.h b/libc/src/math/generic/atan_utils.h
index 3331843900d4b..24c7271b7e4ec 100644
--- a/libc/src/math/generic/atan_utils.h
+++ b/libc/src/math/generic/atan_utils.h
@@ -9,8 +9,11 @@
#ifndef LLVM_LIBC_SRC_MATH_GENERIC_ATAN_UTILS_H
#define LLVM_LIBC_SRC_MATH_GENERIC_ATAN_UTILS_H
+#include "src/__support/FPUtil/PolyEval.h"
#include "src/__support/FPUtil/double_double.h"
+#include "src/__support/FPUtil/dyadic_float.h"
#include "src/__support/FPUtil/multiply_add.h"
+#include "src/__support/integer_literals.h"
#include "src/__support/macros/config.h"
namespace LIBC_NAMESPACE_DECL {
@@ -18,6 +21,7 @@ namespace LIBC_NAMESPACE_DECL {
namespace {
using DoubleDouble = fputil::DoubleDouble;
+using Float128 = fputil::DyadicFloat<128>;
// atan(i/64) with i = 0..64, generated by Sollya with:
// > for i from 0 to 64 do {
@@ -25,7 +29,7 @@ using DoubleDouble = fputil::DoubleDouble;
// b = round(atan(i/64) - a, D, RN);
// print("{", b, ",", a, "},");
// };
-constexpr fputil::DoubleDouble ATAN_I[65] = {
+constexpr DoubleDouble ATAN_I[65] = {
{0.0, 0.0},
{-0x1.220c39d4dff5p-61, 0x1.fff555bbb729bp-7},
{-0x1.5ec431444912cp-60, 0x1.ffd55bba97625p-6},
@@ -106,7 +110,7 @@ constexpr fputil::DoubleDouble ATAN_I[65] = {
// + x_lo * (1 - x_hi^2 + x_hi^4)
// Since p.lo is ~ x^3/3, the relative error from rounding is bounded by:
// |(atan(x) - P(x))/atan(x)| < ulp(x^2) <= 2^(-14-52) = 2^-66.
-DoubleDouble atan_eval(const DoubleDouble &x) {
+[[maybe_unused]] DoubleDouble atan_eval(const DoubleDouble &x) {
DoubleDouble p;
p.hi = x.hi;
double x_hi_sq = x.hi * x.hi;
@@ -130,6 +134,106 @@ DoubleDouble atan_eval(const DoubleDouble &x) {
return p;
}
+// Float128 versions.
+// atan(i/64) with i = 0..64, generated by Sollya with:
+// > for i from 1 to 64 do {
+// a = round(atan(i/64), 128, RN);
+// ll = ceil(log2(a));
+// b = 2^ll + a;
+// print("{Sign::POS, ", 2^(ll - 128), ",", b, "},");
+// };
+constexpr Float128 ATAN_I_F128[65] = {
+ {Sign::POS, 0, 0_u128},
+ {Sign::POS, -134, 0xfffaaadd'db94d5bb'e78c5640'15f76048_u128},
+ {Sign::POS, -133, 0xffeaaddd'4bb12542'779d776d'da8c6214_u128},
+ {Sign::POS, -132, 0xbfdc0c21'86d14fcf'220e10d6'1df56ec7_u128},
+ {Sign::POS, -132, 0xffaaddb9'67ef4e36'cb2792dc'0e2e0d51_u128},
+ {Sign::POS, -131, 0x9facf873'e2aceb58'99c50bbf'08e6cdf6_u128},
+ {Sign::POS, -131, 0xbf70c130'17887460'93567e78'4cf83676_u128},
+ {Sign::POS, -131, 0xdf1cf5f3'783e1bef'71e5340b'30e5d9ef_u128},
+ {Sign::POS, -131, 0xfeadd4d5'617b6e32'c897989f'3e888ef8_u128},
+ {Sign::POS, -130, 0x8f0fd7d8'21b93725'bd375929'83a0af9a_u128},
+ {Sign::POS, -130, 0x9eb77746'331362c3'47619d25'0360fe85_u128},
+ {Sign::POS, -130, 0xae4c08f1'f6134efa'b54d3fef'0c2de994_u128},
+ {Sign::POS, -130, 0xbdcbda5e'72d81134'7b0b4f88'1c9c7488_u128},
+ {Sign::POS, -130, 0xcd35474b'643130e7'b00f3da1'a46eeb3b_u128},
+ {Sign::POS, -130, 0xdc86ba94'93051022'f621a5c1'cb552f03_u128},
+ {Sign::POS, -130, 0xebbeaef9'02b9b38c'91a2a68b'2fbd78e8_u128},
+ {Sign::POS, -130, 0xfadbafc9'6406eb15'6dc79ef5'f7a217e6_u128},
+ {Sign::POS, -129, 0x84ee2cbe'c31b12c5'c8e72197'0cabd3a3_u128},
+ {Sign::POS, -129, 0x8c5fad18'5f8bc130'ca4748b1'bf88298d_u128},
+ {Sign::POS, -129, 0x93c1b902'bf7a2df1'06459240'6fe1447a_u128},
+ {Sign::POS, -129, 0x9b13b9b8'3f5e5e69'c5abb498'd27af328_u128},
+ {Sign::POS, -129, 0xa25521b6'15784d45'43787549'88b8d9e3_u128},
+ {Sign::POS, -129, 0xa9856cca'8e6a4eda'99b7f77b'f7d9e8c1_u128},
+ {Sign::POS, -129, 0xb0a42018'4e7f0cb1'b51d51dc'200a0fc3_u128},
+ {Sign::POS, -129, 0xb7b0ca0f'26f78473'8aa32122'dcfe4483_u128},
+ {Sign::POS, -129, 0xbeab025b'1d9fbad3'910b8564'93411026_u128},
+ {Sign::POS, -129, 0xc59269ca'50d92b6d'a1746e91'f50a28de_u128},
+ {Sign::POS, -129, 0xcc66aa2a'6b58c33c'd9311fa1'4ed9b7c4_u128},
+ {Sign::POS, -129, 0xd327761e'611fe5b6'427c95e9'001e7136_u128},
+ {Sign::POS, -129, 0xd9d488ed'32e3635c'30f6394a'0806345d_u128},
+ {Sign::POS, -129, 0xe06da64a'764f7c67'c631ed96'798cb804_u128},
+ {Sign::POS, -129, 0xe6f29a19'609a84ba'60b77ce1'ca6dc2c8_u128},
+ {Sign::POS, -129, 0xed63382b'0dda7b45'6fe445ec'bc3a8d03_u128},
+ {Sign::POS, -129, 0xf3bf5bf8'bad1a21c'a7b837e6'86adf3fa_u128},
+ {Sign::POS, -129, 0xfa06e85a'a0a0be5c'66d23c7d'5dc8ecc2_u128},
+ {Sign::POS, -128, 0x801ce39e'0d205c99'a6d6c6c5'4d938596_u128},
+ {Sign::POS, -128, 0x832bf4a6'd9867e2a'4b6a09cb'61a515c1_u128},
+ {Sign::POS, -128, 0x8630a2da'da1ed065'd3e84ed5'013ca37e_u128},
+ {Sign::POS, -128, 0x892aecdf'de9547b5'094478fc'472b4afc_u128},
+ {Sign::POS, -128, 0x8c1ad445'f3e09b8c'439d8018'60205921_u128},
+ {Sign::POS, -128, 0x8f005d5e'f7f59f9b'5c835e16'65c43748_u128},
+ {Sign::POS, -128, 0x91db8f16'64f350e2'10e4f9c1'126e0220_u128},
+ {Sign::POS, -128, 0x94ac72c9'847186f6'18c4f393'f78a32f9_u128},
+ {Sign::POS, -128, 0x97731420'365e538b'abd3fe19'f1aeb6b3_u128},
+ {Sign::POS, -128, 0x9a2f80e6'71bdda20'4226f8e2'204ff3bd_u128},
+ {Sign::POS, -128, 0x9ce1c8e6'a0b8cdb9'f799c4e8'174cf11c_u128},
+ {Sign::POS, -128, 0x9f89fdc4'f4b7a1ec'f8b49264'4f0701e0_u128},
+ {Sign::POS, -128, 0xa22832db'cadaae08'92fe9c08'637af0e6_u128},
+ {Sign::POS, -128, 0xa4bc7d19'34f70924'19a87f2a'457dac9f_u128},
+ {Sign::POS, -128, 0xa746f2dd'b7602294'67b7d66f'2d74e019_u128},
+ {Sign::POS, -128, 0xa9c7abdc'4830f5c8'916a84b5'be7933f6_u128},
+ {Sign::POS, -128, 0xac3ec0fb'997dd6a1'a36273a5'6afa8ef4_u128},
+ {Sign::POS, -128, 0xaeac4c38'b4d8c080'14725e2f'3e52070a_u128},
+ {Sign::POS, -128, 0xb110688a'ebdc6f6a'43d65788'b9f6a7b5_u128},
+ {Sign::POS, -128, 0xb36b31c9'1f043691'59014174'4462f93a_u128},
+ {Sign::POS, -128, 0xb5bcc490'59ecc4af'f8f3cee7'5e3907d5_u128},
+ {Sign::POS, -128, 0xb8053e2b'c2319e73'cb2da552'10a4443d_u128},
+ {Sign::POS, -128, 0xba44bc7d'd470782f'654c2cb1'0942e386_u128},
+ {Sign::POS, -128, 0xbc7b5dea'e98af280'd4113006'e80fb290_u128},
+ {Sign::POS, -128, 0xbea94144'fd049aac'1043c5e7'55282e7d_u128},
+ {Sign::POS, -128, 0xc0ce85b8'ac526640'89dd62c4'6e92fa25_u128},
+ {Sign::POS, -128, 0xc2eb4abb'661628b5'b373fe45'c61bb9fb_u128},
+ {Sign::POS, -128, 0xc4ffaffa'bf8fbd54'8cb43d10'bc9e0221_u128},
+ {Sign::POS, -128, 0xc70bd54c'e602ee13'e7d54fbd'09f2be38_u128},
+ {Sign::POS, -128, 0xc90fdaa2'2168c234'c4c6628b'80dc1cd1_u128},
+};
+
+// Degree-13 minimax polynomial generated by Sollya with:
+// > P = fpminimax(atan(x), [|1, 3, 5, 7, 9, 11, 13|], [|1, 128...|],
+// [0, 2^-7]);
+// > dirtyinfnorm(atan(x) - P, [0, 2^-7]);
+// 0x1.26016ad97f323875760f869684c0898d7b7bb8bep-122
+constexpr Float128 ATAN_POLY_F128[] = {
+ {Sign::NEG, -129, 0xaaaaaaaa'aaaaaaaa'aaaaaaa6'003c5d1d_u128},
+ {Sign::POS, -130, 0xcccccccc'cccccccc'cca00232'8776b063_u128},
+ {Sign::NEG, -130, 0x92492492'49249201'27f5268a'cb24aec0_u128},
+ {Sign::POS, -131, 0xe38e38e3'8dce3d96'626a1643'f8eb68f3_u128},
+ {Sign::NEG, -131, 0xba2e8b7a'ea4ad00f'005a35c7'6ef609b1_u128},
+ {Sign::POS, -131, 0x9d82765e'd22a7d92'ac09c405'c0a69214_u128},
+};
+
+// Approximate atan for |x| <= 2^-7.
+[[maybe_unused]] Float128 atan_eval(const Float128 &x) {
+ Float128 x_sq = fputil::quick_mul(x, x);
+ Float128 x3 = fputil::quick_mul(x, x_sq);
+ Float128 p = fputil::polyeval(x_sq, ATAN_POLY_F128[0], ATAN_POLY_F128[1],
+ ATAN_POLY_F128[2], ATAN_POLY_F128[3],
+ ATAN_POLY_F128[4], ATAN_POLY_F128[5]);
+ return fputil::multiply_add(x3, p, x);
+}
+
} // anonymous namespace
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/math/CMakeLists.txt b/libc/test/src/math/CMakeLists.txt
index 6ca4163f07949..5825454a0fe2c 100644
--- a/libc/test/src/math/CMakeLists.txt
+++ b/libc/test/src/math/CMakeLists.txt
@@ -2391,6 +2391,18 @@ add_fp_unittest(
libc.src.__support.FPUtil.fp_bits
)
+add_fp_unittest(
+ atan2f128_test
+ NEED_MPFR
+ SUITE
+ libc-math-unittests
+ SRCS
+ atan2f128_test.cpp
+ DEPENDS
+ libc.src.math.atan2f128
+ libc.src.__support.FPUtil.fp_bits
+)
+
add_fp_unittest(
f16add_test
NEED_MPFR
diff --git a/libc/test/src/math/atan2f128_test.cpp b/libc/test/src/math/atan2f128_test.cpp
new file mode 100644
index 0000000000000..16f4d5d17960b
--- /dev/null
+++ b/libc/test/src/math/atan2f128_test.cpp
@@ -0,0 +1,100 @@
+//===-- Unittests for atan2 -----------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/math/atan2f128.h"
+#include "test/UnitTest/FPMatcher.h"
+#include "test/UnitTest/Test.h"
+#include "utils/MPFRWrapper/MPFRUtils.h"
+
+using LlvmLibcAtan2f128Test = LIBC_NAMESPACE::testing::FPTest<float128>;
+using LIBC_NAMESPACE::testing::tlog;
+
+namespace mpfr = LIBC_NAMESPACE::testing::mpfr;
+
+TEST_F(LlvmLibcAtan2f128Test, InQuadRange) {
+ constexpr StorageType X_COUNT = 123;
+ constexpr StorageType X_START = FPBits(0.25q).uintval();
+ constexpr StorageType X_STOP = FPBits(4.0q).uintval();
+ constexpr StorageType X_STEP = (X_STOP - X_START) / X_COUNT;
+
+ constexpr StorageType Y_COUNT = 137;
+ constexpr StorageType Y_START = FPBits(0.25q).uintval();
+ constexpr StorageType Y_STOP = FPBits(4.0q).uintval();
+ constexpr StorageType Y_STEP = (Y_STOP - Y_START) / Y_COUNT;
+
+ auto test = [&](mpfr::RoundingMode rounding_mode) {
+ mpfr::ForceRoundingMode __r(rounding_mode);
+ if (!__r.success)
+ return;
+
+ uint64_t fails = 0;
+ uint64_t finite_count = 0;
+ uint64_t total_count = 0;
+ float128 failed_x = 0.0, failed_y = 0.0, failed_r = 0.0;
+ double tol = 0.5;
+
+ for (StorageType i = 0, v = X_START; i <= X_COUNT; ++i, v += X_STEP) {
+ float128 x = FPBits(v).get_val();
+ if (FPBits(x).is_inf_or_nan() || x < 0.0q)
+ continue;
+
+ for (StorageType j = 0, w = Y_START; j <= Y_COUNT; ++j, w += Y_STEP) {
+ float128 y = FPBits(w).get_val();
+ if (FPBits(y).is_inf_or_nan())
+ continue;
+
+ float128 result = LIBC_NAMESPACE::atan2f128(x, y);
+ ++total_count;
+ if (FPBits(result).is_inf_or_nan())
+ continue;
+
+ ++finite_count;
+ mpfr::BinaryInput<float128> inputs{x, y};
+
+ if (!TEST_MPFR_MATCH_ROUNDING_SILENTLY(mpfr::Operation::Atan2, inputs,
+ result, 2.0, rounding_mode)) {
+ ++fails;
+ while (!TEST_MPFR_MATCH_ROUNDING_SILENTLY(
+ mpfr::Operation::Atan2, inputs, result, tol, rounding_mode)) {
+ failed_x = x;
+ failed_y = y;
+ failed_r = result;
+
+ if (tol > 1000.0)
+ break;
+
+ tol *= 2.0;
+ }
+ }
+ }
+ }
+ if (fails || (finite_count < total_count)) {
+ tlog << " Atan2 failed: " << fails << "/" << finite_count << "/"
+ << total_count << " tests.\n"
+ << " Max ULPs is at most: " << static_cast<uint64_t>(tol) << ".\n";
+ }
+ if (fails) {
+ mpfr::BinaryInput<float128> inputs{failed_x, failed_y};
+ EXPECT_MPFR_MATCH(mpfr::Operation::Atan2, inputs, failed_r, 0.5,
+ rounding_mode);
+ }
+ };
+
+ tlog << " Test Rounding To Nearest...\n";
+ test(mpfr::RoundingMode::Nearest);
+
+ tlog << " Test Rounding Downward...\n";
+ test(mpfr::RoundingMode::Downward);
+
+ tlog << " Test Rounding Upward...\n";
+ test(mpfr::RoundingMode::Upward);
+
+ tlog << " Test Rounding Toward Zero...\n";
+ test(mpfr::RoundingMode::TowardZero);
+}
diff --git a/libc/test/src/math/smoke/CMakeLists.txt b/libc/test/src/math/smoke/CMakeLists.txt
index 660b68687d63c..5394e0e455100 100644
--- a/libc/test/src/math/smoke/CMakeLists.txt
+++ b/libc/test/src/math/smoke/CMakeLists.txt
@@ -4060,6 +4060,16 @@ add_fp_unittest(
libc.src.math.atan2
)
+add_fp_unittest(
+ atan2f128_test
+ SUITE
+ libc-math-smoke-tests
+ SRCS
+ atan2f128_test.cpp
+ DEPENDS
+ libc.src.math.atan2f128
+)
+
add_fp_unittest(
scalbln_test
SUITE
diff --git a/libc/test/src/math/smoke/atan2f128_test.cpp b/libc/test/src/math/smoke/atan2f128_test.cpp
new file mode 100644
index 0000000000000..51ef51715688a
--- /dev/null
+++ b/libc/test/src/math/smoke/atan2f128_test.cpp
@@ -0,0 +1,28 @@
+//===-- Unittests for atan2f128 -------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/integer_literals.h"
+#include "src/math/atan2f128.h"
+#include "test/UnitTest/FPMatcher.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcAtan2f128Test = LIBC_NAMESPACE::testing::FPTest<float128>;
+
+TEST_F(LlvmLibcAtan2f128Test, SpecialNumbers) {
+ EXPECT_FP_EQ_ALL_ROUNDING(aNaN, LIBC_NAMESPACE::atan2f128(aNaN, zero));
+ EXPECT_FP_EQ_ALL_ROUNDING(aNaN, LIBC_NAMESPACE::atan2f128(1.0, aNaN));
+ EXPECT_FP_EQ_ALL_ROUNDING(0.0, LIBC_NAMESPACE::atan2f128(zero, zero));
+ EXPECT_FP_EQ_ALL_ROUNDING(-0.0, LIBC_NAMESPACE::atan2f128(-0.0, zero));
+ EXPECT_FP_EQ_ALL_ROUNDING(0.0, LIBC_NAMESPACE::atan2f128(1.0, inf));
+ EXPECT_FP_EQ_ALL_ROUNDING(-0.0, LIBC_NAMESPACE::atan2f128(-1.0, inf));
+
+ float128 x = 0x1.ffffffffffffffffffffffffffe7p1q;
+ float128 y = 0x1.fffffffffffffffffffffffffff2p1q;
+ EXPECT_FP_EQ(0x1.921fb54442d18469898cc51701b3p-1q,
+ LIBC_NAMESPACE::atan2f128(x, y));
+}
>From 9b08822bb65598d1c9bfb2367a2e97d03371eeae Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue.h at gmail.com>
Date: Wed, 26 Mar 2025 20:20:15 +0000
Subject: [PATCH 2/3] Update comments.
---
libc/src/math/generic/atan2f128.cpp | 10 ++++++----
libc/test/src/math/atan2f128_test.cpp | 2 +-
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/libc/src/math/generic/atan2f128.cpp b/libc/src/math/generic/atan2f128.cpp
index 808f41982cffc..e3221cf02b691 100644
--- a/libc/src/math/generic/atan2f128.cpp
+++ b/libc/src/math/generic/atan2f128.cpp
@@ -93,12 +93,14 @@ static constexpr Float128 CONST_ADJ[2][2][2] = {
// For the accurate pass, we use the addition formula:
// atan( n/d ) - atan( idx/64 ) = atan( (n/d - idx/64)/(1 + (n*idx)/(64*d)) )
// = atan( (n - d*(idx/64))/(d + n*(idx/64)) )
-// And for the fast pass, we use degree-9 Taylor polynomial to compute the RHS:
-// atan(u) ~ P(u) = u - u^3/3 + u^5/5 - u^7/7 + u^9/9
+// And for the fast pass, we use degree-13 minimax polynomial to compute the
+// RHS:
+// atan(u) ~ P(u) = u - c_3 * u^3 + c_5 * u^5 - c_7 * u^7 + c_9 *u^9 -
+// - c_11 * u^11 + c_13 * u^13
// with absolute errors bounded by:
-// |atan(u) - P(u)| < |u|^11 / 11 < 2^-80
+// |atan(u) - P(u)| < 2^-121
// and relative errors bounded by:
-// |(atan(u) - P(u)) / P(u)| < u^10 / 11 < 2^-73.
+// |(atan(u) - P(u)) / P(u)| < 2^-114.
LLVM_LIBC_FUNCTION(float128, atan2f128, (float128 y, float128 x)) {
using FPBits = fputil::FPBits<float128>;
diff --git a/libc/test/src/math/atan2f128_test.cpp b/libc/test/src/math/atan2f128_test.cpp
index 16f4d5d17960b..5caa13a5d4779 100644
--- a/libc/test/src/math/atan2f128_test.cpp
+++ b/libc/test/src/math/atan2f128_test.cpp
@@ -1,4 +1,4 @@
-//===-- Unittests for atan2 -----------------------------------------------===//
+//===-- Unittests for atan2f128 -------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
>From b683588af10aa89ce09543fca7741f7d063c1756 Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue.h at gmail.com>
Date: Wed, 26 Mar 2025 22:13:14 +0000
Subject: [PATCH 3/3] Fix smoke test for full build.
---
libc/test/src/math/smoke/atan2f128_test.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/libc/test/src/math/smoke/atan2f128_test.cpp b/libc/test/src/math/smoke/atan2f128_test.cpp
index 51ef51715688a..108e771d760d4 100644
--- a/libc/test/src/math/smoke/atan2f128_test.cpp
+++ b/libc/test/src/math/smoke/atan2f128_test.cpp
@@ -23,6 +23,6 @@ TEST_F(LlvmLibcAtan2f128Test, SpecialNumbers) {
float128 x = 0x1.ffffffffffffffffffffffffffe7p1q;
float128 y = 0x1.fffffffffffffffffffffffffff2p1q;
- EXPECT_FP_EQ(0x1.921fb54442d18469898cc51701b3p-1q,
- LIBC_NAMESPACE::atan2f128(x, y));
+ float128 r = 0x1.921fb54442d18469898cc51701b3p-1q;
+ EXPECT_FP_EQ(r, LIBC_NAMESPACE::atan2f128(x, y));
}
More information about the libc-commits
mailing list