[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