Repository: apytypes/apytypes (language: python)

Issue:
APyFixed/APyFixedArray (division)
See https://github.com/apytypes/apytypes/blob/2d7fa53a7562bf6e6c785668176d25858f1a1d0a/src/apyfloatarray_wrapper.cc#L231-L235


Discussion / hints:

Fix patch:
diff --git a/lib/apytypes/_apytypes.pyi b/lib/apytypes/_apytypes.pyi
index ce7b0a9a5..6bf9dc7ce 100644
--- a/lib/apytypes/_apytypes.pyi
+++ b/lib/apytypes/_apytypes.pyi
@@ -536,6 +536,8 @@ class APyFixedArray:
     # def __truediv__(self, arg0: float) -> APyFixedArray: ...
     @typing.overload
     def __truediv__(self, arg0: APyFixed) -> APyFixedArray: ...
+    @typing.overload
+    def __rtruediv__(self, arg0: APyFixed) -> APyFixedArray: ...
     def cast(
         self,
         int_bits: int | None = None,
diff --git a/src/apyfixedarray.cc b/src/apyfixedarray.cc
index e3b1744ae..b51d57c73 100644
--- a/src/apyfixedarray.cc
+++ b/src/apyfixedarray.cc
@@ -538,11 +538,15 @@ APyFixedArray APyFixedArray::operator/(const APyFixed& rhs) const
     }
 
     // General case: This always works but is slower than the special cases.
-    // Absolute value denominator
+
+    // Absolute value denominator. `mpn_tdiv_qr` requires the number of *significant*
+    // limbs in denominator
     ScratchVector<mp_limb_t> abs_den(rhs.vector_size());
     bool den_sign = limb_vector_abs(
         std::begin(rhs._data), std::end(rhs._data), std::begin(abs_den)
     );
+    std::size_t den_significant_limbs
+        = significant_limbs(std::begin(abs_den), std::end(abs_den));
 
     // Absolute value left-shifted numerator
     ScratchVector<mp_limb_t> abs_num(bits_to_limbs(res_bits));
@@ -556,6 +560,63 @@ APyFixedArray APyFixedArray::operator/(const APyFixed& rhs) const
         );
         limb_vector_lsl(abs_num.begin(), abs_num.end(), rhs.bits());
 
+        mpn_div_qr(
+            &result._data[i * result._itemsize], // Quotient
+            &abs_num[0],                         // Numerator
+            abs_num.size(),                      // Numerator limbs
+            &abs_den[0],                         // Denominator
+            den_significant_limbs                // Denominator significant limbs
+        );
+
+        // Negate result if negative
+        if (num_sign ^ den_sign) {
+            limb_vector_negate(
+                std::begin(result._data) + (i + 0) * result._itemsize,
+                std::begin(result._data) + (i + 1) * result._itemsize,
+                std::begin(result._data) + (i + 0) * result._itemsize
+            );
+        }
+    }
+    return result;
+}
+
+APyFixedArray APyFixedArray::rdiv(const APyFixed& rhs) const
+{
+    const int res_int_bits = rhs.int_bits() + frac_bits() + 1;
+    const int res_frac_bits = rhs.frac_bits() + int_bits();
+    const int res_bits = res_int_bits + res_frac_bits;
+    APyFixedArray result(_shape, res_bits, res_int_bits);
+
+    // Special case #1: The resulting number of bits fit in a single limb
+    if (unsigned(res_bits) <= _LIMB_SIZE_BITS) {
+        simd::vector_rdiv_const_signed(
+            std::begin(_data),        // src2 (denominator)
+            rhs._data[0] << bits(),   // src1 (constant numerator)
+            std::begin(result._data), // dst
+            _data.size()              // vector elements
+        );
+        return result;
+    }
+
+    // General case: This always works but is slower than the special cases.
+
+    // Absolute value denominator
+    ScratchVector<mp_limb_t> abs_den(_itemsize);
+
+    // Absolute value left-shifted numerator
+    ScratchVector<mp_limb_t> abs_num(bits_to_limbs(res_bits));
+    bool num_sign = limb_vector_abs(
+        std::begin(rhs._data), std::end(rhs._data), std::begin(abs_num)
+    );
+    limb_vector_lsl(std::begin(abs_num), std::end(abs_num), bits());
+
+    for (std::size_t i = 0; i < _nitems; i++) {
+        bool den_sign = limb_vector_abs(
+            std::begin(_data) + (i + 0) * _itemsize,
+            std::begin(_data) + (i + 1) * _itemsize,
+            std::begin(abs_den)
+        );
+
         // `mpn_tdiv_qr` requires the number of *significant* limbs in denominator
         std::size_t den_significant_limbs
             = significant_limbs(std::begin(abs_den), std::end(abs_den));
@@ -971,6 +1032,7 @@ void APyFixedArray::_checked_hadamard_product(
         // product's most significant limb is zero."
         auto op1_begin = _data.begin();
         auto op2_begin = rhs._data.begin();
+        std::size_t result_itemsize = bits_to_limbs(bits() + rhs.bits());
         for (std::size_t i = 0; i < _nitems; i++) {
             // Current working operands
             auto op1_end = op1_begin + _itemsize;
@@ -994,7 +1056,6 @@ void APyFixedArray::_checked_hadamard_product(
                 op2_scratch.size()  // src2 limb vector length
             );
 
-            int result_itemsize = bits_to_limbs(bits() + rhs.bits());
             if (result_sign) {
                 // Negate result
                 limb_vector_negate(
@@ -1010,7 +1071,6 @@ void APyFixedArray::_checked_hadamard_product(
                     res_out + (i + 0) * result_itemsize
                 );
             }
-
             op1_begin = op1_end;
             op2_begin = op2_end;
         }
diff --git a/src/apyfixedarray.h b/src/apyfixedarray.h
index 2988d2a60..90a5b5eaa 100644
--- a/src/apyfixedarray.h
+++ b/src/apyfixedarray.h
@@ -113,6 +113,7 @@ class APyFixedArray : public APyBuffer<mp_limb_t> {
     APyFixedArray& operator<<=(const int shift_val);
     APyFixedArray& operator>>=(const int shift_val);
     APyFixedArray rsub(const APyFixed& rhs) const;
+    APyFixedArray rdiv(const APyFixed& rhs) const;
 
     /*!
      * Matrix multiplication. If both arguments ar 2-D tensors, this method performs the
diff --git a/src/apyfixedarray_wrapper.cc b/src/apyfixedarray_wrapper.cc
index b1c146c5d..38719ad25 100644
--- a/src/apyfixedarray_wrapper.cc
+++ b/src/apyfixedarray_wrapper.cc
@@ -207,6 +207,12 @@ void bind_fixed_array(nb::module_& m)
             [](const APyFixedArray& a, const APyFixed& b) { return a / b; },
             nb::is_operator()
         )
+        .def(
+            "__rtruediv__",
+            [](const APyFixedArray& a, const APyFixed& b) { return a.rdiv(b); },
+            nb::is_operator()
+
+        )
         .def(-nb::self)
         .def(nb::self <<= int(), nb::rv_policy::none)
         .def(nb::self >>= int(), nb::rv_policy::none)
diff --git a/src/apytypes_simd.cc b/src/apytypes_simd.cc
index e3fc9f53b..a907bbe09 100644
--- a/src/apytypes_simd.cc
+++ b/src/apytypes_simd.cc
@@ -321,6 +321,28 @@ namespace HWY_NAMESPACE { // required: unique per target
         }
     }
 
+    HWY_ATTR void _hwy_vector_rdiv_const_signed(
+        mp_limb_signed_t* HWY_RESTRICT dst,
+        const mp_limb_signed_t* HWY_RESTRICT src1,
+        mp_limb_signed_t constant,
+        const std::size_t size
+    )
+    {
+        constexpr const hn::ScalableTag<mp_limb_signed_t> d;
+        const std::size_t size_simd = size - size % hn::Lanes(d);
+
+        const auto c1 = hn::Set(d, constant);
+        std::size_t i = 0;
+        for (; i < size_simd; i += hn::Lanes(d)) {
+            const auto v1 = hn::LoadU(d, src1 + i);
+            const auto res = hn::Div(c1, v1);
+            hn::StoreU(res, d, dst + i);
+        }
+        for (; i < size; i++) {
+            dst[i] = constant / src1[i];
+        }
+    }
+
     HWY_ATTR mp_limb_t _hwy_vector_multiply_accumulate(
         const mp_limb_t* HWY_RESTRICT src1,
         const mp_limb_t* HWY_RESTRICT src2,
@@ -384,6 +406,7 @@ HWY_EXPORT(_hwy_vector_sub);
 HWY_EXPORT(_hwy_vector_add_const);
 HWY_EXPORT(_hwy_vector_sub_const);
 HWY_EXPORT(_hwy_vector_rsub_const);
+HWY_EXPORT(_hwy_vector_rdiv_const_signed);
 HWY_EXPORT(_hwy_vector_multiply_accumulate);
 
 std::string get_simd_version_str()
@@ -573,6 +596,21 @@ void vector_rsub_const(
     );
 }
 
+void vector_rdiv_const_signed(
+    std::vector<mp_limb_t>::const_iterator src1_begin,
+    mp_limb_t constant,
+    std::vector<mp_limb_t>::iterator dst_begin,
+    std::size_t size
+)
+{
+    return HWY_DYNAMIC_DISPATCH(_hwy_vector_rdiv_const_signed)(
+        reinterpret_cast<mp_limb_signed_t*>(&*dst_begin),
+        reinterpret_cast<const mp_limb_signed_t*>(&*src1_begin),
+        constant,
+        size
+    );
+}
+
 mp_limb_t vector_multiply_accumulate(
     std::vector<mp_limb_t>::const_iterator src1_begin,
     std::vector<mp_limb_t>::const_iterator src2_begin,
diff --git a/src/apytypes_simd.h b/src/apytypes_simd.h
index b6e449b98..3d54ffc21 100644
--- a/src/apytypes_simd.h
+++ b/src/apytypes_simd.h
@@ -36,7 +36,7 @@ void vector_shift_add(
     std::size_t size
 );
 
-/*
+/*!
  * For each element in the iterator regions [ `*_begin`, `*_begin + size` ):
  * * Shift element in `src1_begin` left by `src1_shift_amount`
  * * Add shifted element to `constant` and store the result in `dst_begin`
@@ -64,7 +64,7 @@ void vector_shift_sub(
     std::size_t size
 );
 
-/*
+/*!
  * For each element in the iterator regions [ `*_begin`, `*_begin + size` ):
  * * Shift element in `src1_begin` left by `src1_shift_amount`
  * * Subtract `constant` from shifted element and store the result in `dst_begin`
@@ -77,7 +77,7 @@ void vector_shift_sub_const(
     std::size_t size
 );
 
-/*
+/*!
  * For each element in the iterator regions [ `*_begin`, `*_begin + size` ):
  * * Shift element in `src1_begin` left by `src1_shift_amount`
  * * Divide shifted element (numerator) by `src2_begin` (denominator) and store the
@@ -91,7 +91,7 @@ void vector_shift_div_signed(
     std::size_t size
 );
 
-/*
+/*!
  * For each element in the iterator regions [ `*_begin`, `*_begin + size` ):
  * * Shift element in `src1_begin` left by `src1_shift_amount`
  * * Divide shifted element (numerator) by `constant` (denominator) and store the
@@ -182,6 +182,18 @@ void vector_rsub_const(
     std::size_t size
 );
 
+/*!
+ * For each element in the iterator regions [ `*_begin`, `*_begin + size` ):
+ * * Divide `constant` (numerator) by the element in `src1_begin` (signed)
+ * * Store the result in `dst_begin`
+ */
+void vector_rdiv_const_signed(
+    std::vector<mp_limb_t>::const_iterator src1_begin,
+    mp_limb_t constant,
+    std::vector<mp_limb_t>::iterator dst_begin,
+    std::size_t size
+);
+
 /*!
  * Multiply (signed) and accumulate all elements from `src1_begin` with `src2_begin`
  * for `size` number of elements. Returns accumulated value.


Test patch:
diff --git a/lib/test/apyfixedarray/test_arithmetic.py b/lib/test/apyfixedarray/test_arithmetic.py
index 259f79376..ec03bb893 100644
--- a/lib/test/apyfixedarray/test_arithmetic.py
+++ b/lib/test/apyfixedarray/test_arithmetic.py
@@ -367,6 +367,9 @@ def test_array_div_scalar():
     assert (a / b).is_identical(
         APyFixedArray([129935, 129707, 1592, 1820, 2048, 1137], bits=17, int_bits=8)
     )
+    assert (b / a).is_identical(
+        APyFixedArray([131015, 131024, 41, 36, 32, 57], bits=17, int_bits=10)
+    )
 
     a = APyFixedArray.from_float([-5, -6, 7, 8, 9, 5], bits=100, int_bits=50)
     b = APyFixed.from_float(2.25, bits=123, int_bits=10)
@@ -385,6 +388,21 @@ def test_array_div_scalar():
         )
     )
 
+    assert (b / a).is_identical(
+        APyFixedArray(
+            [
+                26959946667150639789405609192628380168103879024761953610342253094503,
+                26959946667150639790282510175026921919026089924391723422135812620288,
+                3758147067422321789666618141270442050543826539081,
+                3288378683994531565958290873611636794225848221696,
+                2923003274661805836407369665432566039311865085952,
+                5261405894391250505533265397778618870761357154714,
+            ],
+            bits=224,
+            int_bits=61,
+        )
+    )
+
 
 def test_array_prod():
     a = APyFixedArray([-5, -6, 7, -1], bits=10, int_bits=5)