| // This file is part of Eigen, a lightweight C++ template library | |
| // for linear algebra. | |
| // | |
| // Copyright (C) 2018-2025 Rasmus Munk Larsen <rmlarsen@gmail.com> | |
| // | |
| // This Source Code Form is subject to the terms of the Mozilla | |
| // Public License v. 2.0. If a copy of the MPL was not distributed | |
| // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| // SPDX-License-Identifier: MPL-2.0 | |
| #ifndef EIGEN_ARCH_GENERIC_PACKET_MATH_DOUBLE_WORD_H | |
| #define EIGEN_ARCH_GENERIC_PACKET_MATH_DOUBLE_WORD_H | |
| // IWYU pragma: private | |
| #include "../../InternalHeaderCheck.h" | |
| namespace Eigen { | |
| namespace internal { | |
| // This function splits x into the nearest integer n and fractional part r, | |
| // such that x = n + r holds exactly. | |
| template <typename Packet> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void absolute_split(const Packet& x, Packet& n, Packet& r) { | |
| n = pround(x); | |
| r = psub(x, n); | |
| } | |
| // This function computes the sum {s_hi, s_lo}, such that x + y = s_hi + s_lo | |
| // holds exactly, and s_hi = fl(x+y), if |x| >= |y|. | |
| template <typename Packet> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void fast_twosum(const Packet& x, const Packet& y, Packet& s_hi, Packet& s_lo) { | |
| s_hi = padd(x, y); | |
| const Packet t = psub(s_hi, x); | |
| s_lo = psub(y, t); | |
| } | |
| #ifdef EIGEN_VECTORIZE_FMA | |
| // Given x, y, and xy = fl(x*y), return the residual such that x*y = xy + residual exactly. | |
| template <typename Packet, std::enable_if_t<!is_scalar<Packet>::value, int> = 0> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Packet twoprod_low(const Packet& x, const Packet& y, const Packet& xy) { | |
| return pmsub(x, y, xy); | |
| } | |
| template <typename Scalar, std::enable_if_t<is_scalar<Scalar>::value, int> = 0> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar twoprod_low(const Scalar& x, const Scalar& y, const Scalar& xy) { | |
| // Error-free products require FMA even when EIGEN_SCALAR_MADD_USE_FMA disables fusion in scalar madd. | |
| return numext::fma(x, y, Scalar(-xy)); | |
| } | |
| // This function implements the extended precision product of | |
| // a pair of floating point numbers. Given {x, y}, it computes the pair | |
| // {p_hi, p_lo} such that x * y = p_hi + p_lo holds exactly and | |
| // p_hi = fl(x * y). | |
| template <typename Packet> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void twoprod(const Packet& x, const Packet& y, Packet& p_hi, Packet& p_lo) { | |
| p_hi = pmul(x, y); | |
| p_lo = twoprod_low(x, y, p_hi); | |
| } | |
| #else | |
| // Dekker's product requires separately rounded operations. GCC's C++ default -ffp-contract=fast may fuse | |
| // fl(x*y) into its consumers, so use fma for scalar types that <cmath> advertises as fast. | |
| // This does not prevent contraction in the remaining scalar or packet fallback paths. | |
| template <typename Scalar> | |
| struct has_fast_fma : std::false_type {}; | |
| #ifdef FP_FAST_FMAF | |
| template <> | |
| struct has_fast_fma<float> : std::true_type {}; | |
| #endif | |
| #ifdef FP_FAST_FMA | |
| template <> | |
| struct has_fast_fma<double> : std::true_type {}; | |
| #endif | |
| #ifdef FP_FAST_FMAL | |
| template <> | |
| struct has_fast_fma<long double> : std::true_type {}; | |
| #endif | |
| template <typename Scalar, std::enable_if_t<has_fast_fma<Scalar>::value, int> = 0> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Scalar twoprod_low(const Scalar& x, const Scalar& y, const Scalar& xy) { | |
| return numext::fma(x, y, Scalar(-xy)); | |
| } | |
| template <typename Scalar, std::enable_if_t<has_fast_fma<Scalar>::value, int> = 0> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void twoprod(const Scalar& x, const Scalar& y, Scalar& p_hi, Scalar& p_lo) { | |
| p_hi = x * y; | |
| p_lo = twoprod_low(x, y, p_hi); | |
| } | |
| // This function implements the Veltkamp splitting. Given a floating point | |
| // number x it returns the pair {x_hi, x_lo} such that x_hi + x_lo = x holds | |
| // exactly and that half of the significand of x fits in x_hi. | |
| // This is Algorithm 3 from Jean-Michel Muller, "Elementary Functions", | |
| // 3rd edition, Birkh\"auser, 2016. | |
| template <typename Packet> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void veltkamp_splitting(const Packet& x, Packet& x_hi, Packet& x_lo) { | |
| using Scalar = typename unpacket_traits<Packet>::type; | |
| constexpr int shift = (NumTraits<Scalar>::digits() + 1) / 2; | |
| const Scalar shift_scale = Scalar(uint64_t(1) << shift); // Scalar constructor not necessarily constexpr. | |
| const Packet gamma = pmul(pset1<Packet>(shift_scale + Scalar(1)), x); | |
| Packet rho = psub(x, gamma); | |
| x_hi = padd(rho, gamma); | |
| x_lo = psub(x, x_hi); | |
| } | |
| // This function implements Dekker's algorithm for products x * y. | |
| // Given floating point numbers {x, y} computes the pair | |
| // {p_hi, p_lo} such that x * y = p_hi + p_lo holds exactly and | |
| // p_hi = fl(x * y). | |
| template <typename Packet, std::enable_if_t<!has_fast_fma<Packet>::value, int> = 0> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void twoprod(const Packet& x, const Packet& y, Packet& p_hi, Packet& p_lo) { | |
| Packet x_hi, x_lo, y_hi, y_lo; | |
| veltkamp_splitting(x, x_hi, x_lo); | |
| veltkamp_splitting(y, y_hi, y_lo); | |
| p_hi = pmul(x, y); | |
| p_lo = pmadd(x_hi, y_hi, pnegate(p_hi)); | |
| p_lo = pmadd(x_hi, y_lo, p_lo); | |
| p_lo = pmadd(x_lo, y_hi, p_lo); | |
| p_lo = pmadd(x_lo, y_lo, p_lo); | |
| } | |
| // A version of twoprod that takes x, y, and fl(x*y) as input and returns the p_lo such that | |
| // x * y = xy + p_lo holds exactly. | |
| template <typename Packet, std::enable_if_t<!has_fast_fma<Packet>::value, int> = 0> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE Packet twoprod_low(const Packet& x, const Packet& y, const Packet& xy) { | |
| Packet x_hi, x_lo, y_hi, y_lo; | |
| veltkamp_splitting(x, x_hi, x_lo); | |
| veltkamp_splitting(y, y_hi, y_lo); | |
| Packet p_lo = pmadd(x_hi, y_hi, pnegate(xy)); | |
| p_lo = pmadd(x_hi, y_lo, p_lo); | |
| p_lo = pmadd(x_lo, y_hi, p_lo); | |
| p_lo = pmadd(x_lo, y_lo, p_lo); | |
| return p_lo; | |
| } | |
| #endif // EIGEN_VECTORIZE_FMA | |
| // This function implements the addition of two double word numbers | |
| // represented by {x_hi, x_lo} and {y_hi, y_lo}, with operands of either sign | |
| // and magnitude: Knuth's two-sum of the high parts is error-free, the low | |
| // parts are added to its error term, and the result is renormalized. | |
| // This is Algorithm 5 (SloppyDWPlusDW) from Joldes, Muller, & Popescu (2017), | |
| // "Tight and rigorous error bounds for basic building blocks of double-word | |
| // arithmetic", https://hal.science/hal-01351529: the error is a few u^2 | |
| // relative to |x| + |y|, not to the sum, which may cancel. | |
| template <typename Packet> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void twosum(const Packet& x_hi, const Packet& x_lo, const Packet& y_hi, | |
| const Packet& y_lo, Packet& s_hi, Packet& s_lo) { | |
| Packet s = padd(x_hi, y_hi); | |
| Packet y_part = psub(s, x_hi); | |
| Packet err = padd(psub(x_hi, psub(s, y_part)), psub(y_hi, y_part)); | |
| fast_twosum(s, padd(err, padd(x_lo, y_lo)), s_hi, s_lo); | |
| } | |
| // The difference {x_hi, x_lo} - {y_hi, y_lo} of two double word numbers, | |
| // the same algorithm as twosum with the sign of y folded into the operations. | |
| template <typename Packet> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void twodiff(const Packet& x_hi, const Packet& x_lo, const Packet& y_hi, | |
| const Packet& y_lo, Packet& s_hi, Packet& s_lo) { | |
| Packet s = psub(x_hi, y_hi); | |
| Packet y_part = psub(x_hi, s); | |
| Packet err = psub(psub(x_hi, padd(s, y_part)), psub(y_hi, y_part)); | |
| fast_twosum(s, padd(err, psub(x_lo, y_lo)), s_hi, s_lo); | |
| } | |
| // This is a version of twosum for double word numbers, | |
| // which assumes that |x_hi| >= |y_hi|. | |
| template <typename Packet> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void fast_twosum(const Packet& x_hi, const Packet& x_lo, const Packet& y_hi, | |
| const Packet& y_lo, Packet& s_hi, Packet& s_lo) { | |
| Packet r_hi, r_lo; | |
| fast_twosum(x_hi, y_hi, r_hi, r_lo); | |
| const Packet s = padd(padd(y_lo, r_lo), x_lo); | |
| fast_twosum(r_hi, s, s_hi, s_lo); | |
| } | |
| // This is a version of twosum for adding a floating point number x to | |
| // double word number {y_hi, y_lo} number, with the assumption | |
| // that |x| >= |y_hi|. | |
| template <typename Packet> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void fast_twosum(const Packet& x, const Packet& y_hi, const Packet& y_lo, | |
| Packet& s_hi, Packet& s_lo) { | |
| Packet r_hi, r_lo; | |
| fast_twosum(x, y_hi, r_hi, r_lo); | |
| const Packet s = padd(y_lo, r_lo); | |
| fast_twosum(r_hi, s, s_hi, s_lo); | |
| } | |
| // This function implements the multiplication of a double word | |
| // number represented by {x_hi, x_lo} by a floating point number y. | |
| // It returns the result as a pair {p_hi, p_lo} such that | |
| // (x_hi + x_lo) * y = p_hi + p_lo holds with a relative error | |
| // of less than 2*2^{-2p}, where p is the number of significand bits | |
| // in the floating point type. | |
| // This is Algorithm 7 from Jean-Michel Muller, "Elementary Functions", | |
| // 3rd edition, Birkh\"auser, 2016. | |
| template <typename Packet> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void twoprod(const Packet& x_hi, const Packet& x_lo, const Packet& y, | |
| Packet& p_hi, Packet& p_lo) { | |
| Packet c_hi, c_lo1; | |
| twoprod(x_hi, y, c_hi, c_lo1); | |
| const Packet c_lo2 = pmul(x_lo, y); | |
| Packet t_hi, t_lo1; | |
| fast_twosum(c_hi, c_lo2, t_hi, t_lo1); | |
| const Packet t_lo2 = padd(t_lo1, c_lo1); | |
| fast_twosum(t_hi, t_lo2, p_hi, p_lo); | |
| } | |
| // This function implements the multiplication of two double word | |
| // numbers represented by {x_hi, x_lo} and {y_hi, y_lo}. | |
| // For normalized inputs (x_hi = RN(x_hi + x_lo), likewise for y), the result | |
| // {p_hi, p_lo} has relative error < 5*u^2 for p-bit significands, u = 2^{-p}, p >= 8. | |
| // This assumes round-to-nearest, no overflow/underflow, an exact two-product | |
| // residual, and separately rounded operations outside that residual. | |
| // Each DW*FP product has relative error <= (3/2 + 4*u)*u^2: Joldes, Muller, | |
| // and Popescu (2017), "Tight and rigorous error bounds for basic building | |
| // blocks of double-word arithmetic", Theorem 4.1, https://hal.science/hal-01351529. | |
| // The final sum adds absolute error <= u^2*((3+3*u+u^2)*abs(p_hi_hi) | |
| // + (4+3*u+u^2)*abs(p_lo_hi)); using abs(y_lo) <= u*abs(y_hi) gives the bound above. | |
| template <typename Packet> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void twoprod(const Packet& x_hi, const Packet& x_lo, const Packet& y_hi, | |
| const Packet& y_lo, Packet& p_hi, Packet& p_lo) { | |
| Packet p_hi_hi, p_hi_lo; | |
| twoprod(x_hi, x_lo, y_hi, p_hi_hi, p_hi_lo); | |
| Packet p_lo_hi, p_lo_lo; | |
| twoprod(x_hi, x_lo, y_lo, p_lo_hi, p_lo_lo); | |
| fast_twosum(p_hi_hi, p_hi_lo, p_lo_hi, p_lo_lo, p_hi, p_lo); | |
| } | |
| // This function implements the multiplication of two double word numbers | |
| // {x_hi, x_lo} and {y_hi, y_lo} at a lower cost than twoprod above: the | |
| // cross terms are accumulated onto the two-product residual and the x_lo*y_lo | |
| // term is dropped. For normalized inputs the relative error is < 7*u^2. | |
| // This is Algorithm 10 (DWTimesDW1) from Joldes, Muller, & Popescu (2017), | |
| // "Tight and rigorous error bounds for basic building blocks of double-word | |
| // arithmetic", https://hal.science/hal-01351529. | |
| template <typename Packet> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void fast_twoprod(const Packet& x_hi, const Packet& x_lo, const Packet& y_hi, | |
| const Packet& y_lo, Packet& p_hi, Packet& p_lo) { | |
| Packet c_hi, c_lo; | |
| twoprod(x_hi, y_hi, c_hi, c_lo); | |
| c_lo = pmadd(x_hi, y_lo, pmadd(x_lo, y_hi, c_lo)); | |
| fast_twosum(c_hi, c_lo, p_hi, p_lo); | |
| } | |
| // This function implements the division of double word {x_hi, x_lo} | |
| // by float y. This is Algorithm 15 from "Tight and rigorous error bounds | |
| // for basic building blocks of double-word arithmetic", Joldes, Muller, & Popescu, | |
| // 2017. https://hal.archives-ouvertes.fr/hal-01351529 | |
| template <typename Packet> | |
| EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE void doubleword_div_fp(const Packet& x_hi, const Packet& x_lo, const Packet& y, | |
| Packet& z_hi, Packet& z_lo) { | |
| const Packet t_hi = pdiv(x_hi, y); | |
| Packet pi_hi, pi_lo; | |
| twoprod(t_hi, y, pi_hi, pi_lo); | |
| const Packet delta_hi = psub(x_hi, pi_hi); | |
| const Packet delta_t = psub(delta_hi, pi_lo); | |
| const Packet delta = padd(delta_t, x_lo); | |
| const Packet t_lo = pdiv(delta, y); | |
| fast_twosum(t_hi, t_lo, z_hi, z_lo); | |
| } | |
| } // end namespace internal | |
| } // end namespace Eigen | |
| #endif // EIGEN_ARCH_GENERIC_PACKET_MATH_DOUBLE_WORD_H |