Repository: apytypes/apytypes (language: python)

Issue:
Support operations with arbitrary numbers by first converting to same format (for APyFloat)
One way I can consider for supporting operations with arbitrary numbers is to convert the number to the same format. Especially for `APyFloat` this makes sense. For `APyFixed`, it is not as straightforward, but one may consider the same happening for integer arguments.

Here, one can also consider supporting (NumPy-)arrays as arguments to `APyFloatArray` operations (possibly even `APyFloat`).


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c47f98c04..b4b1096bb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   - `APyFixedArray.broadcast_to()`
   - `APyFloatArray.broadcast_to()`
   - Automatic broadcasting on array arithmetic
+- Support arithmetic between APyTypes classes and Python's built-in floats and ints.
 
 ### Changed
 
diff --git a/examples/apyfixed.py b/examples/apyfixed.py
index dab1786db..e3e536e2c 100644
--- a/examples/apyfixed.py
+++ b/examples/apyfixed.py
@@ -42,6 +42,24 @@
 # Comparisons with integers and floats are supported
 (b >= -2.25, a == 1.75)
 
+# %%
+# Arithmetic with Python integers and floats is supported, although *not recommended*, by
+# automatically casting the number to the same format of the APyType-object
+b + 3.5
+
+# %%
+# Which is equivalent to
+b + APyFixed.from_float(3.5, int_bits=b.int_bits, frac_bits=b.frac_bits)
+
+# %%
+# However, while it is convenient to let APyTypes convert numbers automatically,
+# it is not recommended as it can yield unexpected results. Consider the expression
+1.25 + 1.25 + APyFixed.from_float(2, int_bits=4, frac_bits=1)
+
+# %%
+# which would yield a different result from
+APyFixed.from_float(2, int_bits=4, frac_bits=1) + 1.25 + 1.25
+
 # %%
 # To change the word length of a APyFixed, the method :func:`~APyFixed.cast` can be used
 (a + b).cast(4, 3)
diff --git a/examples/apyfloat.py b/examples/apyfloat.py
index 5435385a9..fc324cf09 100644
--- a/examples/apyfloat.py
+++ b/examples/apyfloat.py
@@ -38,6 +38,24 @@
 # Comparisons with integers and floats are supported
 (b >= 7, a < -1.2)
 
+# %%
+# Arithmetic with Python integers and floats is supported, although *not recommended*, by
+# automatically casting the number to the same format of the APyType-object
+b + 1.125
+
+# %%
+# Which is equivalent to
+b + APyFloat.from_float(1.125, exp_bits=b.exp_bits, man_bits=b.man_bits, bias=b.bias)
+
+# %%
+# However, while it is convenient to let APyTypes convert numbers automatically,
+# it is not recommended as it can yield unexpected results. Consider the expression
+2 + 2 + APyFloat.from_float(8, exp_bits=4, man_bits=1)
+
+# %%
+# which would yield a different result from
+APyFloat.from_float(8, exp_bits=4, man_bits=1) + 2 + 2
+
 # %%
 # To change the word length of a APyFloat, the method :func:`~APyFloat.cast` can be used
 (a + b).cast(4, 3)
diff --git a/lib/apytypes/_apytypes.pyi b/lib/apytypes/_apytypes.pyi
index 284b4276b..eb77cd05e 100644
--- a/lib/apytypes/_apytypes.pyi
+++ b/lib/apytypes/_apytypes.pyi
@@ -781,6 +781,12 @@ class APyFloat:
     @typing.overload
     def __mul__(self, arg0: float) -> APyFloat: ...
     @typing.overload
+    def __truediv__(self, arg0: APyFloat) -> APyFloat: ...
+    @typing.overload
+    def __truediv__(self, arg0: int) -> APyFloat: ...
+    @typing.overload
+    def __truediv__(self, arg0: float) -> APyFloat: ...
+    @typing.overload
     def __ne__(self, arg0: APyFloat) -> bool: ...
     @typing.overload
     def __ne__(self, arg0: float) -> bool: ...
diff --git a/src/apyfixed.cc b/src/apyfixed.cc
index 76635e79c..249985220 100644
--- a/src/apyfixed.cc
+++ b/src/apyfixed.cc
@@ -246,38 +246,24 @@ APyFixed APyFixed::operator/(const APyFixed& rhs) const
     return result;
 }
 
-APyFixed APyFixed::operator+(int rhs) const
+APyFixed APyFixed::operator+(const nb::int_& rhs) const
 {
-    // TODO
-    if (rhs == 0) {
-        return *this;
-    }
-    throw NotImplementedException("Not implemented: APyFixed.__add__(int)");
+    return *this + APyFixed::from_integer(rhs, _int_bits, frac_bits());
 }
 
-APyFixed APyFixed::operator-(int rhs) const
+APyFixed APyFixed::operator-(const nb::int_& rhs) const
 {
-    // TODO
-    if (rhs == 0) {
-        return *this;
-    }
-    throw NotImplementedException("Not implemented: APyFixed.__sub__(int)");
+    return *this - APyFixed::from_integer(rhs, _int_bits, frac_bits());
 }
 
-APyFixed APyFixed::operator*(int rhs) const
+APyFixed APyFixed::operator*(const nb::int_& rhs) const
 {
-    if (rhs == 1) {
-        return *this;
-    }
-    throw NotImplementedException("Not implemented: APyFixed.__mul__(int)");
+    return *this * APyFixed::from_integer(rhs, _int_bits, frac_bits());
 }
 
-APyFixed APyFixed::operator/(int rhs) const
+APyFixed APyFixed::operator/(const nb::int_& rhs) const
 {
-    if (rhs == 1) {
-        return *this;
-    }
-    throw NotImplementedException("Not implemented: APyFixed.__div__(int)");
+    return *this / APyFixed::from_integer(rhs, _int_bits, frac_bits());
 }
 
 APyFixed APyFixed::operator<<(const int shift_val) const
@@ -438,18 +424,6 @@ bool APyFixed::operator>(const double rhs) const
     return (*this > rhs_fp.to_fixed());
 }
 
-bool APyFixed::operator==(const float rhs) const { return *this == (double)rhs; }
-
-bool APyFixed::operator!=(const float rhs) const { return *this != (double)rhs; }
-
-bool APyFixed::operator<=(const float rhs) const { return *this <= (double)rhs; }
-
-bool APyFixed::operator<(const float rhs) const { return *this < (double)rhs; }
-
-bool APyFixed::operator>=(const float rhs) const { return *this >= (double)rhs; }
-
-bool APyFixed::operator>(const float rhs) const { return *this > (double)rhs; }
-
 APyFixed APyFixed::operator-() const
 {
     const int res_bits = _bits + 1;
@@ -961,6 +935,29 @@ bool APyFixed::positive_greater_than_equal_pow2(int n) const
  * *                           Static member functions                              * *
  * ********************************************************************************** */
 
+APyFixed APyFixed::from_number(
+    const nb::object& py_obj,
+    std::optional<int> int_bits,
+    std::optional<int> frac_bits,
+    std::optional<int> bits
+)
+{
+    if (nb::isinstance<nb::int_>(py_obj)) {
+        return APyFixed::from_integer(
+            nb::cast<nb::int_>(py_obj), int_bits, frac_bits, bits
+        );
+    } else if (nb::isinstance<nb::float_>(py_obj)) {
+        const auto d = static_cast<double>(nb::cast<nb::float_>(py_obj));
+        return APyFixed::from_double(d, int_bits, frac_bits, bits);
+    } else {
+        const nb::type_object type = nb::cast<nb::type_object>(py_obj.type());
+        const nb::str type_string = nb::str(type);
+        throw std::domain_error(
+            std::string("Non supported type: ") + type_string.c_str()
+        );
+    }
+}
+
 APyFixed APyFixed::from_double(
     double value,
     std::optional<int> int_bits,
@@ -973,6 +970,49 @@ APyFixed APyFixed::from_double(
     return result;
 }
 
+APyFixed APyFixed::from_integer(
+    const nb::int_& value,
+    std::optional<int> int_bits,
+    std::optional<int> frac_bits,
+    std::optional<int> bits
+)
+{
+    // Extract things bit widths
+    const int res_bits = bits_from_optional(bits, int_bits, frac_bits);
+    const int res_int_bits = int_bits.has_value() ? *int_bits : *bits - *frac_bits;
+
+    APyFixed result(res_bits, res_int_bits);
+    result._data = python_long_to_limb_vec(value, result._data.size());
+
+    // Adjust the number
+    if (result.frac_bits() > 0) {
+        limb_vector_lsl(result._data.begin(), result._data.end(), result.frac_bits());
+    } else { /* result.frac_bits() <= 0 */
+        limb_vector_asr(result._data.begin(), result._data.end(), -result.frac_bits());
+    }
+
+    // Two's-complements overflow bits outside of the range
+    result._overflow_twos_complement(
+        result._data.begin(), result._data.end(), res_bits, res_int_bits
+    );
+
+    return result;
+}
+
+APyFixed APyFixed::from_unspecified_integer(const nb::int_& value)
+{
+    std::vector<mp_limb_t> limbs = python_long_to_limb_vec(value);
+
+    // If the value is positive but the MSB is set, adding a zero limb
+    // will prevent it from representing a negative value.
+    if (!python_long_is_negative(value)
+        && limb_vector_is_negative(std::begin(limbs), std::end(limbs))) {
+        limbs.push_back(0);
+    }
+    const std::size_t res_bits = limbs.size() * _LIMB_SIZE_BITS;
+    return APyFixed(res_bits, res_bits, limbs.begin(), limbs.end());
+}
+
 APyFixed APyFixed::from_string(
     std::string string_value,
     std::optional<int> int_bits,
diff --git a/src/apyfixed.h b/src/apyfixed.h
index cf00ac192..781662a25 100644
--- a/src/apyfixed.h
+++ b/src/apyfixed.h
@@ -135,13 +135,13 @@ class APyFixed {
     APyFixed& operator>>=(const int shift_val);
 
     //! Addition of int
-    APyFixed operator+(int rhs) const;
+    APyFixed operator+(const nb::int_& rhs) const;
     //! Subtraction of int
-    APyFixed operator-(int rhs) const;
+    APyFixed operator-(const nb::int_& rhs) const;
     //! Multiplication of int
-    APyFixed operator*(int rhs) const;
+    APyFixed operator*(const nb::int_& rhs) const;
     //! Division of int
-    APyFixed operator/(int rhs) const;
+    APyFixed operator/(const nb::int_& rhs) const;
 
     /* ****************************************************************************** *
      *                          Binary comparison operators                            *
@@ -169,13 +169,6 @@ class APyFixed {
     bool operator>=(const double rhs) const;
     bool operator>(const double rhs) const;
 
-    bool operator==(const float rhs) const;
-    bool operator!=(const float rhs) const;
-    bool operator<=(const float rhs) const;
-    bool operator<(const float rhs) const;
-    bool operator>=(const float rhs) const;
-    bool operator>(const float rhs) const;
-
     /* ****************************************************************************** *
      *                        Other public member functions                           *
      * ****************************************************************************** */
@@ -277,6 +270,14 @@ class APyFixed {
      * ****************************************************************************** */
 
 public:
+    //! Create APyFixed from Python float or int.
+    static APyFixed from_number(
+        const nb::object& py_obj,
+        std::optional<int> int_bits = std::nullopt,
+        std::optional<int> frac_bits = std::nullopt,
+        std::optional<int> bits = std::nullopt
+    );
+
     //! Create APyFixed from double.
     static APyFixed from_double(
         double value,
@@ -285,6 +286,18 @@ class APyFixed {
         std::optional<int> bits = std::nullopt
     );
 
+    //! Create APyFixed from Python integer.
+    static APyFixed from_integer(
+        const nb::int_& value,
+        std::optional<int> int_bits = std::nullopt,
+        std::optional<int> frac_bits = std::nullopt,
+        std::optional<int> bits = std::nullopt
+    );
+
+    //! Create APyFixed from Python integer without specifying the format.
+    //! The format will be big enough to accommodate the Python integer.
+    static APyFixed from_unspecified_integer(const nb::int_& value);
+
     //! Create APyFixed from string.
     static APyFixed from_string(
         std::string string_value,
diff --git a/src/apyfixed_wrapper.cc b/src/apyfixed_wrapper.cc
index 84f5115fb..165d340ba 100644
--- a/src/apyfixed_wrapper.cc
+++ b/src/apyfixed_wrapper.cc
@@ -43,12 +43,12 @@ void bind_fixed(nb::module_& m)
         .def(nb::self > nb::int_())
         .def(nb::self >= nb::int_())
 
-        .def(nb::self == float())
-        .def(nb::self != float())
-        .def(nb::self < float())
-        .def(nb::self > float())
-        .def(nb::self <= float())
-        .def(nb::self >= float())
+        .def(nb::self == double())
+        .def(nb::self != double())
+        .def(nb::self < double())
+        .def(nb::self > double())
+        .def(nb::self <= double())
+        .def(nb::self >= double())
 
         .def(nb::self + nb::self)
         .def(nb::self - nb::self)
@@ -58,31 +58,111 @@ void bind_fixed(nb::module_& m)
         .def(nb::self <<= int(), nb::rv_policy::none)
         .def(nb::self >>= int(), nb::rv_policy::none)
 
-        // Addition with integers
-        .def(nb::self + int())
+        // Operations with integers
+        .def(nb::self + nb::int_())
         .def(
             "__radd__",
-            [](APyFixed& rhs, int lhs) { return rhs + lhs; },
+            [](const APyFixed& rhs, const nb::int_& lhs) { return rhs + lhs; },
             nb::is_operator()
         )
-        .def(nb::self - int())
+        .def(nb::self - nb::int_())
         .def(
             "__rsub__",
-            [](APyFixed& rhs, int lhs) {
-                if (lhs == 0) {
-                    return -rhs;
-                }
-                throw NotImplementedException("NotImplemented: __rsub__(int)");
+            [](const APyFixed& rhs, const nb::int_& lhs) {
+                return APyFixed::from_integer(lhs, rhs.int_bits(), rhs.frac_bits())
+                    - rhs;
             },
             nb::is_operator()
         )
-        .def(nb::self * int())
+        .def(nb::self * nb::int_())
         .def(
             "__rmul__",
-            [](APyFixed& rhs, int lhs) { return rhs * lhs; },
+            [](const APyFixed& rhs, const nb::int_& lhs) { return rhs * lhs; },
+            nb::is_operator()
+        )
+        .def(nb::self / nb::int_())
+        .def(
+            "__rtruediv__",
+            [](const APyFixed& rhs, const nb::int_& lhs) {
+                return APyFixed::from_integer(lhs, rhs.int_bits(), rhs.frac_bits())
+                    / rhs;
+            },
+            nb::is_operator()
+        )
+
+        // Operations with floats
+        .def(
+            "__add__",
+            [](const APyFixed& lhs, double rhs) {
+                return lhs
+                    + APyFixed::from_double(rhs, lhs.int_bits(), lhs.frac_bits());
+            },
+            nb::is_operator()
+        )
+        .def(
+            "__radd__",
+            [](const APyFixed& rhs, double lhs) {
+                return rhs
+                    + APyFixed::from_double(lhs, rhs.int_bits(), rhs.frac_bits());
+            },
+            nb::is_operator()
+        )
+        .def(
+            "__sub__",
+            [](const APyFixed& lhs, double rhs) {
+                return lhs
+                    - APyFixed::from_double(rhs, lhs.int_bits(), lhs.frac_bits());
+            },
+            nb::is_operator()
+        )
+        .def(
+            "__rsub__",
+            [](const APyFixed& rhs, double lhs) {
+                return APyFixed::from_double(lhs, rhs.int_bits(), rhs.frac_bits())
+                    - rhs;
+            },
+            nb::is_operator()
+        )
+        .def(
+            "__mul__",
+            [](const APyFixed& lhs, double rhs) {
+                return lhs
+                    * APyFixed::from_double(rhs, lhs.int_bits(), lhs.frac_bits());
+            },
+            nb::is_operator()
+        )
+        .def(
+            "__rmul__",
+            [](const APyFixed& rhs, double lhs) {
+                return rhs
+                    * APyFixed::from_double(lhs, rhs.int_bits(), rhs.frac_bits());
+            },
+            nb::is_operator()
+        )
+        .def(
+            "__radd__",
+            [](const APyFixed& rhs, double lhs) {
+                return rhs
+                    * APyFixed::from_double(lhs, rhs.int_bits(), rhs.frac_bits());
+            },
+            nb::is_operator()
+        )
+        .def(
+            "__truediv__",
+            [](const APyFixed& lhs, double rhs) {
+                return lhs
+                    / APyFixed::from_double(rhs, lhs.int_bits(), lhs.frac_bits());
+            },
+            nb::is_operator()
+        )
+        .def(
+            "__rtruediv__",
+            [](const APyFixed& rhs, double lhs) {
+                return APyFixed::from_double(lhs, rhs.int_bits(), rhs.frac_bits())
+                    / rhs;
+            },
             nb::is_operator()
         )
-        .def(nb::self / int())
 
         /*
          * Methods
@@ -296,13 +376,13 @@ void bind_fixed(nb::module_& m)
          */
         .def_static(
             "from_float",
-            &APyFixed::from_double,
+            &APyFixed::from_number,
             nb::arg("value"),
             nb::arg("int_bits") = nb::none(),
             nb::arg("frac_bits") = nb::none(),
             nb::arg("bits") = nb::none(),
             R"pbdoc(
-            Create an :class:`APyFixed` object from :class:`float`.
+            Create an :class:`APyFixed` object from an :class:`int` or :class:`float`.
 
             The initialized fixed-point value is the one closest to the input
             floating-point value, rounded away from zero on ties. Exactly two of the
@@ -310,7 +390,7 @@ void bind_fixed(nb::module_& m)
 
             Parameters
             ----------
-            value : float
+            value : int, float
                 Floating point value to initialize from
             int_bits : int, optional
                 Number of integer bits in the created fixed-point object
diff --git a/src/apyfixedarray_iterator.h b/src/apyfixedarray_iterator.h
index 5b1a966f5..5376471a3 100644
--- a/src/apyfixedarray_iterator.h
+++ b/src/apyfixedarray_iterator.h
@@ -1,11 +1,12 @@
+#ifndef _APYFIXED_ARRAY_ITERATOR_H
+#define _APYFIXED_ARRAY_ITERATOR_H
+
 #include "apyfixed.h"
 #include "apyfixedarray.h"
 
 #include <nanobind/nanobind.h>
 #include <nanobind/stl/variant.h>
 
-#ifndef _APYFIXED_ARRAY_ITERATOR_H
-#define _APYFIXED_ARRAY_ITERATOR_H
 struct APyFixedArrayIterator {
 public:
     APyFixedArrayIterator(const APyFixedArray& array, nanobind::object ref);
diff --git a/src/apyfixedarray_wrapper.cc b/src/apyfixedarray_wrapper.cc
index 56ad0deb9..0e7ecc1ad 100644
--- a/src/apyfixedarray_wrapper.cc
+++ b/src/apyfixedarray_wrapper.cc
@@ -33,46 +33,31 @@ void bind_fixed_array(nb::module_& m)
         .def(nb::self + nb::self)
         .def(
             "__add__",
-            [](const APyFixedArray& a, int b) {
-                if (b == 0) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot add with non-zero int");
-                };
+            [](const APyFixedArray& a, const nb::int_& b) {
+                return a + APyFixed::from_integer(b, a.int_bits(), a.frac_bits());
             },
             nb::is_operator()
         )
         .def(
             "__radd__",
-            [](const APyFixedArray& a, int b) {
-                if (b == 0) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot reverse add with non-zero int");
-                };
+            [](const APyFixedArray& a, const nb::int_& b) {
+                return a + APyFixed::from_integer(b, a.int_bits(), a.frac_bits());
             },
             nb::is_operator()
         )
         .def(
             "__add__",
-            [](const APyFixedArray& a, float b) {
-                if (b == 0.) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot add with non-zero float");
-                };
+            [](const APyFixedArray& lhs, double rhs) {
+                return lhs
+                    + APyFixed::from_double(rhs, lhs.int_bits(), lhs.frac_bits());
             },
             nb::is_operator()
         )
         .def(
             "__radd__",
-            [](const APyFixedArray& a, float b) {
-                if (b == 0.) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot reverse add with non-zero float"
-                    );
-                };
+            [](const APyFixedArray& rhs, double lhs) {
+                return rhs
+                    + APyFixed::from_double(lhs, rhs.int_bits(), rhs.frac_bits());
             },
             nb::is_operator()
         )
@@ -89,49 +74,32 @@ void bind_fixed_array(nb::module_& m)
         .def(nb::self - nb::self)
         .def(
             "__sub__",
-            [](const APyFixedArray& a, const int b) {
-                if (b == 0) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot subtract with non-zero int");
-                };
+            [](const APyFixedArray& a, const nb::int_& b) {
+                return a - APyFixed::from_integer(b, a.int_bits(), a.frac_bits());
             },
             nb::is_operator()
         )
         .def(
             "__rsub__",
-            [](const APyFixedArray& a, const int b) {
-                if (b == 0) {
-                    return -a;
-                } else {
-                    throw nanobind::type_error(
-                        "Cannot reverse subtract with non-zero int"
-                    );
-                };
+            [](const APyFixedArray& a, const nb::int_& b) {
+                return a.rsub(APyFixed::from_integer(b, a.int_bits(), a.frac_bits()));
             },
             nb::is_operator()
         )
         .def(
             "__sub__",
-            [](const APyFixedArray& a, const float b) {
-                if (b == 0.) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot subtract with non-zero float");
-                };
+            [](const APyFixedArray& lhs, double rhs) {
+                return lhs
+                    - APyFixed::from_double(rhs, lhs.int_bits(), lhs.frac_bits());
             },
             nb::is_operator()
         )
         .def(
             "__rsub__",
-            [](const APyFixedArray& a, float const b) {
-                if (b == 0.) {
-                    return -a;
-                } else {
-                    throw nanobind::type_error(
-                        "Cannot reverse subtract with non-zero float"
-                    );
-                };
+            [](const APyFixedArray& rhs, double lhs) {
+                return rhs.rsub(
+                    APyFixed::from_double(lhs, rhs.int_bits(), rhs.frac_bits())
+                );
             },
             nb::is_operator()
         )
@@ -158,45 +126,31 @@ void bind_fixed_array(nb::module_& m)
         )
         .def(
             "__mul__",
-            [](const APyFixedArray& a, const int b) {
-                if (b == 1) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot multiply with int");
-                };
+            [](const APyFixedArray& a, const nb::int_& b) {
+                return a * APyFixed::from_integer(b, a.int_bits(), a.frac_bits());
             },
             nb::is_operator()
         )
         .def(
             "__rmul__",
-            [](const APyFixedArray& a, const int b) {
-                if (b == 1) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot reverse multiply with int");
-                };
+            [](const APyFixedArray& a, const nb::int_& b) {
+                return a * APyFixed::from_integer(b, a.int_bits(), a.frac_bits());
             },
             nb::is_operator()
         )
         .def(
             "__mul__",
-            [](const APyFixedArray& a, const float b) {
-                if (b == 1.) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot multiply with float");
-                };
+            [](const APyFixedArray& lhs, double rhs) {
+                return lhs
+                    * APyFixed::from_double(rhs, lhs.int_bits(), lhs.frac_bits());
             },
             nb::is_operator()
         )
         .def(
             "__rmul__",
-            [](const APyFixedArray& a, const float b) {
-                if (b == 1.) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot multiply with float");
-                };
+            [](const APyFixedArray& rhs, double lhs) {
+                return rhs
+                    * APyFixed::from_double(lhs, rhs.int_bits(), rhs.frac_bits());
             },
             nb::is_operator()
         )
@@ -212,6 +166,37 @@ void bind_fixed_array(nb::module_& m)
             nb::is_operator()
 
         )
+        .def(
+            "__truediv__",
+            [](const APyFixedArray& a, const nb::int_& b) {
+                return a / APyFixed::from_integer(b, a.int_bits(), a.frac_bits());
+            },
+            nb::is_operator()
+        )
+        .def(
+            "__rtruediv__",
+            [](const APyFixedArray& a, const nb::int_& b) {
+                return a.rdiv(APyFixed::from_integer(b, a.int_bits(), a.frac_bits()));
+            },
+            nb::is_operator()
+        )
+        .def(
+            "__truediv__",
+            [](const APyFixedArray& lhs, double rhs) {
+                return lhs
+                    / APyFixed::from_double(rhs, lhs.int_bits(), lhs.frac_bits());
+            },
+            nb::is_operator()
+        )
+        .def(
+            "__rtruediv__",
+            [](const APyFixedArray& rhs, double lhs) {
+                return rhs.rdiv(
+                    APyFixed::from_double(lhs, rhs.int_bits(), rhs.frac_bits())
+                );
+            },
+            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/apyfloat.cc b/src/apyfloat.cc
index 7bec872ea..295ca31c5 100644
--- a/src/apyfloat.cc
+++ b/src/apyfloat.cc
@@ -111,13 +111,33 @@ APyFloat::APyFloat(
  * **********************************************************************************
  */
 
-APyFloat APyFloat::from_double(
-    double value, int exp_bits, int man_bits, std::optional<exp_t> bias
+APyFloat APyFloat::from_number(
+    const nb::object& py_obj, int exp_bits, int man_bits, std::optional<exp_t> bias
 )
 {
     check_exponent_format(exp_bits);
     check_mantissa_format(man_bits);
 
+    if (nb::isinstance<nb::int_>(py_obj)) {
+        return APyFloat::from_integer(
+            nb::cast<nb::int_>(py_obj), exp_bits, man_bits, bias
+        );
+    } else if (nb::isinstance<nb::float_>(py_obj)) {
+        const auto d = static_cast<double>(nb::cast<nb::float_>(py_obj));
+        return APyFloat::from_double(d, exp_bits, man_bits, bias);
+    } else {
+        const nb::type_object type = nb::cast<nb::type_object>(py_obj.type());
+        const nb::str type_string = nb::str(type);
+        throw std::domain_error(
+            std::string("Non supported type: ") + type_string.c_str()
+        );
+    }
+}
+
+APyFloat APyFloat::from_double(
+    double value, int exp_bits, int man_bits, std::optional<exp_t> bias
+)
+{
     APyFloat apytypes_double(
         sign_of_double(value), exp_of_double(value), man_of_double(value), 11, 52, 1023
     );
@@ -126,6 +146,59 @@ APyFloat APyFloat::from_double(
     );
 }
 
+APyFloat APyFloat::from_integer(
+    const nb::int_ value, int exp_bits, int man_bits, std::optional<exp_t> bias
+)
+{
+    APyFixed apyfixed = APyFixed::from_unspecified_integer(value);
+
+    const exp_t actual_bias = bias.value_or(APyFloat::ieee_bias(exp_bits));
+
+    if (apyfixed.is_zero()) {
+        return APyFloat(0, 0, 0, exp_bits, man_bits, actual_bias);
+    }
+
+    APyFloat res(exp_bits, man_bits, actual_bias);
+
+    // Get sign
+    if (apyfixed.is_negative()) {
+        res.sign = true;
+        apyfixed = apyfixed.abs();
+    } else {
+        res.sign = false;
+    }
+
+    // Calculate exponent
+    const exp_t target_exp
+        = apyfixed.bits() - apyfixed.leading_zeros() - 1; // The bit width minus 1
+    std::uint64_t tmp_exp = target_exp + res.bias;
+
+    // Calculate mantissa
+    apyfixed >>= target_exp;
+    apyfixed = apyfixed.cast(3, man_bits, QuantizationMode::RND_CONV);
+
+    // Check for overflow
+    if (apyfixed.positive_greater_than_equal_pow2(1)) {
+        ++tmp_exp;
+        apyfixed >>= 1;
+    }
+
+    // Check for overflow
+    if (tmp_exp >= res.max_exponent()) {
+        res.exp = res.max_exponent();
+        res.man = 0;
+    } else {
+        res.exp = tmp_exp;
+        // Remove leading one
+        if (apyfixed.positive_greater_than_equal_pow2(0)) {
+            apyfixed = apyfixed - fx_one;
+        }
+        res.man = apyfixed.get_lsbs();
+    }
+
+    return res;
+}
+
 APyFloat APyFloat::cast(
     std::optional<int> new_exp_bits,
     std::optional<int> new_man_bits,
@@ -458,13 +531,8 @@ APyFloat APyFloat::cast_from_double(
     }
 
     if (new_exp >= res.max_exponent()) {
-        if (do_infinity(get_float_quantization_mode(), res.sign)) {
-            new_exp = res.max_exponent();
-            new_man = 0;
-        } else {
-            new_exp = res.max_exponent() - 1;
-            new_man = res.man_mask();
-        }
+        new_exp = res.max_exponent() - 1;
+        new_man = res.man_mask();
     }
 
     res.man = new_man;
@@ -663,7 +731,7 @@ APyFixed APyFloat::to_fixed() const
     if (sign) {
         res = -res;
     }
-    exp_t exponent = true_exp();
+    const exp_t exponent = true_exp();
     if (exponent <= 0) {
         res >>= -exponent;
     } else {
@@ -842,8 +910,10 @@ APyFloat APyFloat::operator+(const APyFloat& rhs) const
     // Slower path
 
     // Two integer bits, sign bit and leading one
-    APyFixed apy_mx(2 + x.man_bits, 2, limb_vector_from_uint64_t({ mx }));
-    APyFixed apy_my(2 + y.man_bits, 2 - exp_delta, limb_vector_from_uint64_t({ my }));
+    const APyFixed apy_mx(2 + x.man_bits, 2, limb_vector_from_uint64_t({ mx }));
+    const APyFixed apy_my(
+        2 + y.man_bits, 2 - exp_delta, limb_vector_from_uint64_t({ my })
+    );
 
     // Perform addition/subtraction
     auto apy_res = (x.sign == y.sign) ? apy_mx + apy_my : apy_mx - apy_my;
@@ -950,7 +1020,7 @@ APyFloat& APyFloat::operator+=(const APyFloat& rhs)
     }
 
     // Compute if the signs are the same to be able to overwrite this.sign
-    bool same_sign = sign == rhs.sign;
+    const bool same_sign = sign == rhs.sign;
     const APyFloat* x = &*this;
     const APyFloat* y = &rhs;
     const auto quantization = get_float_quantization_mode();
@@ -964,11 +1034,11 @@ APyFloat& APyFloat::operator+=(const APyFloat& rhs)
     }
 
     // Compute smaller exponent so that one can overwrite exp later
-    exp_t smaller_exp = y->exp + y->is_subnormal();
+    const exp_t smaller_exp = y->exp + y->is_subnormal();
 
     // Conditionally add leading one's
-    man_t mx = x->true_man();
-    man_t my = y->true_man();
+    const man_t mx = x->true_man();
+    const man_t my = y->true_man();
 
     // Tentative exponent, write directly
     exp = x->exp + x->is_subnormal();
@@ -979,8 +1049,10 @@ APyFloat& APyFloat::operator+=(const APyFloat& rhs)
     // Slower path only as fast path is not currently used
 
     // Two integer bits, sign bit and leading one
-    APyFixed apy_mx(2 + man_bits, 2, limb_vector_from_uint64_t({ mx }));
-    APyFixed apy_my(2 + man_bits, 2 - exp_delta, limb_vector_from_uint64_t({ my }));
+    const APyFixed apy_mx(2 + man_bits, 2, limb_vector_from_uint64_t({ mx }));
+    const APyFixed apy_my(
+        2 + man_bits, 2 - exp_delta, limb_vector_from_uint64_t({ my })
+    );
 
     // Perform addition/subtraction
     auto apy_res = (same_sign) ? apy_mx + apy_my : apy_mx - apy_my;
@@ -1136,16 +1208,20 @@ APyFloat APyFloat::operator*(const APyFloat& y) const
         APyFloat norm_y = y.normalized();
 
         // Add leading one's
-        man_t mx = norm_x.true_man();
-        man_t my = norm_y.true_man();
+        const man_t mx = norm_x.true_man();
+        const man_t my = norm_y.true_man();
 
         // Tentative exponent
         std::int64_t new_exp = ((std::int64_t)norm_x.exp - (std::int64_t)norm_x.bias)
             + ((std::int64_t)norm_y.exp - (std::int64_t)norm_y.bias) + res.bias;
 
         // Two integer bits, sign bit and leading one
-        APyFixed apy_mx(2 + norm_x.man_bits, 2, limb_vector_from_uint64_t({ mx }));
-        APyFixed apy_my(2 + norm_y.man_bits, 2, limb_vector_from_uint64_t({ my }));
+        const APyFixed apy_mx(
+            2 + norm_x.man_bits, 2, limb_vector_from_uint64_t({ mx })
+        );
+        const APyFixed apy_my(
+            2 + norm_y.man_bits, 2, limb_vector_from_uint64_t({ my })
+        );
 
         auto apy_res = (apy_mx * apy_my);
 
@@ -1235,17 +1311,17 @@ APyFloat APyFloat::operator/(const APyFloat& y) const
         + (std::int64_t)res.bias;
 
     // Add leading one's
-    man_t mx = norm_x.true_man();
-    man_t my = norm_y.true_man();
+    const man_t mx = norm_x.true_man();
+    const man_t my = norm_y.true_man();
 
     // Guard bits for getting enough fractional bits out of fixed-point division
     const auto guard_bits = 64;
 
     // Two integer bits, sign bit and leading one
-    APyFixed apy_mx(
+    const APyFixed apy_mx(
         2 + guard_bits + norm_x.man_bits, 2, limb_vector_from_uint64_t({ mx })
     );
-    APyFixed apy_my(
+    const APyFixed apy_my(
         2 + guard_bits + norm_y.man_bits, 2, limb_vector_from_uint64_t({ my })
     );
     auto apy_man_res = apy_mx / apy_my;
@@ -1434,11 +1510,12 @@ APyFloat APyFloat::pown(const APyFloat& x, int n)
  * ******************************************************************************
  */
 
-APyFloat APyFloat::operator&(const APyFloat& rhs)
+APyFloat APyFloat::operator&(const APyFloat& rhs) const
 {
     if (same_type_as(rhs)) {
-        APyFloat f(sign & rhs.sign, exp & rhs.exp, man & rhs.man, exp_bits, man_bits);
-        return f;
+        return APyFloat(
+            sign & rhs.sign, exp & rhs.exp, man & rhs.man, exp_bits, man_bits
+        );
     }
 
     const auto max_exp_bits = std::max(exp_bits, rhs.exp_bits);
@@ -1447,21 +1524,21 @@ APyFloat APyFloat::operator&(const APyFloat& rhs)
     const auto lhs_big = cast_no_quant(max_exp_bits, max_man_bits, ieee_bias);
     const auto rhs_big = rhs.cast_no_quant(max_exp_bits, max_man_bits, ieee_bias);
 
-    APyFloat f(
+    return APyFloat(
         lhs_big.sign & rhs_big.sign,
         lhs_big.exp & rhs_big.exp,
         lhs_big.man & rhs_big.man,
         max_exp_bits,
         max_man_bits
     );
-    return f;
 }
 
-APyFloat APyFloat::operator|(const APyFloat& rhs)
+APyFloat APyFloat::operator|(const APyFloat& rhs) const
 {
     if (same_type_as(rhs)) {
-        APyFloat f(sign | rhs.sign, exp | rhs.exp, man | rhs.man, exp_bits, man_bits);
-        return f;
+        return APyFloat(
+            sign | rhs.sign, exp | rhs.exp, man | rhs.man, exp_bits, man_bits
+        );
     }
     const auto max_exp_bits = std::max(exp_bits, rhs.exp_bits);
     const auto max_man_bits = std::max(man_bits, rhs.man_bits);
@@ -1469,21 +1546,21 @@ APyFloat APyFloat::operator|(const APyFloat& rhs)
     const auto lhs_big = cast_no_quant(max_exp_bits, max_man_bits, ieee_bias);
     const auto rhs_big = rhs.cast_no_quant(max_exp_bits, max_man_bits, ieee_bias);
 
-    APyFloat f(
+    return APyFloat(
         lhs_big.sign | rhs_big.sign,
         lhs_big.exp | rhs_big.exp,
         lhs_big.man | rhs_big.man,
         max_exp_bits,
         max_man_bits
     );
-    return f;
 }
 
-APyFloat APyFloat::operator^(const APyFloat& rhs)
+APyFloat APyFloat::operator^(const APyFloat& rhs) const
 {
     if (same_type_as(rhs)) {
-        APyFloat f(sign ^ rhs.sign, exp ^ rhs.exp, man ^ rhs.man, exp_bits, man_bits);
-        return f;
+        return APyFloat(
+            sign ^ rhs.sign, exp ^ rhs.exp, man ^ rhs.man, exp_bits, man_bits
+        );
     }
     const auto max_exp_bits = std::max(exp_bits, rhs.exp_bits);
     const auto max_man_bits = std::max(man_bits, rhs.man_bits);
@@ -1491,26 +1568,24 @@ APyFloat APyFloat::operator^(const APyFloat& rhs)
     const auto lhs_big = cast_no_quant(max_exp_bits, max_man_bits, ieee_bias);
     const auto rhs_big = rhs.cast_no_quant(max_exp_bits, max_man_bits, ieee_bias);
 
-    APyFloat f(
+    return APyFloat(
         lhs_big.sign ^ rhs_big.sign,
         lhs_big.exp ^ rhs_big.exp,
         lhs_big.man ^ rhs_big.man,
         max_exp_bits,
         max_man_bits
     );
-    return f;
 }
 
-APyFloat APyFloat::operator~()
+APyFloat APyFloat::operator~() const
 {
-    APyFloat f(
+    return APyFloat(
         !sign,
         ~exp & ((1ULL << exp_bits) - 1),
         ~man & ((1ULL << man_bits) - 1),
         exp_bits,
         man_bits
     );
-    return f;
 }
 
 /* ******************************************************************************
@@ -1664,18 +1739,6 @@ bool APyFloat::operator>(const double rhs) const
     return (*this > rhs_fp);
 }
 
-bool APyFloat::operator==(const float rhs) const { return *this == (double)rhs; }
-
-bool APyFloat::operator!=(const float rhs) const { return *this != (double)rhs; }
-
-bool APyFloat::operator<=(const float rhs) const { return *this <= (double)rhs; }
-
-bool APyFloat::operator<(const float rhs) const { return *this < (double)rhs; }
-
-bool APyFloat::operator>=(const float rhs) const { return *this >= (double)rhs; }
-
-bool APyFloat::operator>(const float rhs) const { return *this > (double)rhs; }
-
 bool APyFloat::operator==(const APyFixed& rhs) const
 {
     if (is_max_exponent()) {
diff --git a/src/apyfloat.h b/src/apyfloat.h
index 6e98561e3..ba71bd7ed 100644
--- a/src/apyfloat.h
+++ b/src/apyfloat.h
@@ -74,6 +74,13 @@ class APyFloat {
         std::optional<exp_t> bias = std::nullopt
     );
 
+    //! Create APyFloat from double
+    static APyFloat from_number(
+        const nb::object& py_obj,
+        int exp_bits,
+        int man_bits,
+        std::optional<exp_t> bias = std::nullopt
+    );
     //! Create APyFloat from double
     static APyFloat from_double(
         double value,
@@ -81,6 +88,13 @@ class APyFloat {
         int man_bits,
         std::optional<exp_t> bias = std::nullopt
     );
+    //! Create APyFloat from Python integer
+    static APyFloat from_integer(
+        const nb::int_ value,
+        int exp_bits,
+        int man_bits,
+        std::optional<exp_t> bias = std::nullopt
+    );
     //! Cast to double
     double to_double() const;
     operator double() const;
@@ -156,6 +170,8 @@ class APyFloat {
     cast_no_quant(std::uint8_t exp_bits, std::uint8_t man_bits, exp_t bias) const;
 
     //! Simplified casting when the input is known to correspond to a double.
+    //! Values too large will become infinity, and not saturate depending on the
+    //! quantization mode.
     APyFloat
     cast_from_double(std::uint8_t exp_bits, std::uint8_t man_bits, exp_t bias) const;
 
@@ -200,10 +216,10 @@ class APyFloat {
      * ******************************************************************************
      */
 
-    APyFloat operator&(const APyFloat& rhs);
-    APyFloat operator|(const APyFloat& rhs);
-    APyFloat operator^(const APyFloat& rhs);
-    APyFloat operator~();
+    APyFloat operator&(const APyFloat& rhs) const;
+    APyFloat operator|(const APyFloat& rhs) const;
+    APyFloat operator^(const APyFloat& rhs) const;
+    APyFloat operator~() const;
 
     /* ******************************************************************************
      * * Binary comparison operators                                                *
@@ -222,12 +238,6 @@ class APyFloat {
     bool operator<(const double rhs) const;
     bool operator>=(const double rhs) const;
     bool operator>(const double rhs) const;
-    bool operator==(const float rhs) const;
-    bool operator!=(const float rhs) const;
-    bool operator<=(const float rhs) const;
-    bool operator<(const float rhs) const;
-    bool operator>=(const float rhs) const;
-    bool operator>(const float rhs) const;
     bool operator==(const APyFixed& rhs) const;
     bool operator!=(const APyFixed& rhs) const;
     bool operator<=(const APyFixed& rhs) const;
diff --git a/src/apyfloat_wrapper.cc b/src/apyfloat_wrapper.cc
index cc5571d8c..17519b634 100644
--- a/src/apyfloat_wrapper.cc
+++ b/src/apyfloat_wrapper.cc
@@ -50,19 +50,19 @@ void bind_float(nb::module_& m)
          */
         .def_static(
             "from_float",
-            &APyFloat::from_double,
+            &APyFloat::from_number,
             nb::arg("value"),
             nb::arg("exp_bits"),
             nb::arg("man_bits"),
             nb::arg("bias") = nb::none(),
             R"pbdoc(
-            Create an :class:`APyFloat` object from a :class:`float`.
+            Create an :class:`APyFloat` object from an :class:`int` or :class:`float`.
 
             The quantization mode used is :class:`QuantizationMode.TIES_EVEN`.
 
             Parameters
             ----------
-            value : float
+            value : int, float
                 Floating-point value to initialize from.
             exp_bits : int
                 Number of exponent bits.
@@ -198,135 +198,161 @@ void bind_float(nb::module_& m)
         .def(nb::self / nb::self)
         .def(
             "__add__",
-            [](const APyFloat& a, int b) {
-                if (b == 0) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot add with int");
-                };
+            [](const APyFloat& lhs, const nb::int_& rhs) {
+                return lhs
+                    + APyFloat::from_integer(
+                           rhs, lhs.get_exp_bits(), lhs.get_man_bits(), lhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__radd__",
-            [](const APyFloat& a, int b) {
-                if (b == 0) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot add with int");
-                };
+            [](const APyFloat& rhs, const nb::int_& lhs) {
+                return rhs
+                    + APyFloat::from_integer(
+                           lhs, rhs.get_exp_bits(), rhs.get_man_bits(), rhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
-
         .def(
             "__add__",
-            [](const APyFloat& a, float b) {
-                if (b == 0.) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot add with float");
-                };
+            [](const APyFloat& lhs, double rhs) {
+                return lhs
+                    + APyFloat::from_double(
+                           rhs, lhs.get_exp_bits(), lhs.get_man_bits(), lhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__radd__",
-            [](const APyFloat& a, float b) {
-                if (b == 0.) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot add with float");
-                };
+            [](const APyFloat& rhs, double lhs) {
+                return APyFloat::from_double(
+                           lhs, rhs.get_exp_bits(), rhs.get_man_bits(), rhs.get_bias()
+                       )
+                    + rhs;
             },
             nb::is_operator()
         )
         .def(
             "__sub__",
-            [](const APyFloat& a, int b) {
-                if (b == 0) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot subtract with int");
-                };
+            [](const APyFloat& lhs, const nb::int_& rhs) {
+                return lhs
+                    - APyFloat::from_integer(
+                           rhs, lhs.get_exp_bits(), lhs.get_man_bits(), lhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__rsub__",
-            [](const APyFloat& a, int b) {
-                if (b == 0) {
-                    return -a;
-                } else {
-                    throw nanobind::type_error("Cannot subtract with int");
-                };
+            [](const APyFloat& rhs, const nb::int_& lhs) {
+                return APyFloat::from_integer(
+                           lhs, rhs.get_exp_bits(), rhs.get_man_bits(), rhs.get_bias()
+                       )
+                    - rhs;
             },
             nb::is_operator()
         )
-
         .def(
             "__sub__",
-            [](const APyFloat& a, float b) {
-                if (b == 0.) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot subtract with float");
-                };
+            [](const APyFloat& lhs, double rhs) {
+                return lhs
+                    - APyFloat::from_double(
+                           rhs, lhs.get_exp_bits(), lhs.get_man_bits(), lhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__rsub__",
-            [](const APyFloat& a, float b) {
-                if (b == 0.) {
-                    return -a;
-                } else {
-                    throw nanobind::type_error("Cannot subtract with float");
-                };
+            [](const APyFloat& rhs, double lhs) {
+                return APyFloat::from_double(
+                           lhs, rhs.get_exp_bits(), rhs.get_man_bits(), rhs.get_bias()
+                       )
+                    - rhs;
             },
             nb::is_operator()
         )
         .def(
             "__mul__",
-            [](const APyFloat& a, int b) {
-                if (b == 1) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot multiply with int");
-                };
+            [](const APyFloat& lhs, const nb::int_& rhs) {
+                return lhs
+                    * APyFloat::from_integer(
+                           rhs, lhs.get_exp_bits(), lhs.get_man_bits(), lhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__rmul__",
-            [](const APyFloat& a, int b) {
-                if (b == 1) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot multiply with int");
-                };
+            [](const APyFloat& rhs, const nb::int_& lhs) {
+                return rhs
+                    * APyFloat::from_integer(
+                           lhs, rhs.get_exp_bits(), rhs.get_man_bits(), rhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__mul__",
-            [](const APyFloat& a, float b) {
-                if (b == 1.) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot multiply with float");
-                };
+            [](const APyFloat& lhs, double rhs) {
+                return lhs
+                    * APyFloat::from_double(
+                           rhs, lhs.get_exp_bits(), lhs.get_man_bits(), lhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__rmul__",
-            [](const APyFloat& a, float b) {
-                if (b == 1.) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot multiply with float");
-                };
+            [](const APyFloat& rhs, double lhs) {
+                return APyFloat::from_double(
+                           lhs, rhs.get_exp_bits(), rhs.get_man_bits(), rhs.get_bias()
+                       )
+                    * rhs;
+            },
+            nb::is_operator()
+        )
+        .def(
+            "__truediv__",
+            [](const APyFloat& lhs, const nb::int_& rhs) {
+                return lhs
+                    / APyFloat::from_integer(
+                           rhs, lhs.get_exp_bits(), lhs.get_man_bits(), lhs.get_bias()
+                    );
+            },
+            nb::is_operator()
+        )
+        .def(
+            "__rtruediv__",
+            [](const APyFloat& rhs, const nb::int_& lhs) {
+                return APyFloat::from_integer(
+                           lhs, rhs.get_exp_bits(), rhs.get_man_bits(), rhs.get_bias()
+                       )
+                    / rhs;
+            },
+            nb::is_operator()
+        )
+        .def(
+            "__truediv__",
+            [](const APyFloat& lhs, double rhs) {
+                return lhs
+                    / APyFloat::from_double(
+                           rhs, lhs.get_exp_bits(), lhs.get_man_bits(), lhs.get_bias()
+                    );
+            },
+            nb::is_operator()
+        )
+        .def(
+            "__rtruediv__",
+            [](const APyFloat& rhs, double lhs) {
+                return APyFloat::from_double(
+                           lhs, rhs.get_exp_bits(), rhs.get_man_bits(), rhs.get_bias()
+                       )
+                    / rhs;
             },
             nb::is_operator()
         )
@@ -347,42 +373,42 @@ void bind_float(nb::module_& m)
         .def(nb::self >= nb::self)
 
         // float, double
-        .def(nb::self == float())
-        .def(nb::self != float())
-        .def(nb::self < float())
-        .def(nb::self > float())
-        .def(nb::self <= float())
-        .def(nb::self >= float())
+        .def(nb::self == double())
+        .def(nb::self != double())
+        .def(nb::self < double())
+        .def(nb::self > double())
+        .def(nb::self <= double())
+        .def(nb::self >= double())
 
         // APyFixed
         .def(
             "__eq__",
-            [](const APyFloat& a, const APyFixed& b) { return a == b; },
+            [](const APyFloat& rhs, const APyFixed& lhs) { return rhs == lhs; },
             nb::is_operator()
         )
         .def(
             "__ne__",
-            [](const APyFloat& a, const APyFixed& b) { return a != b; },
+            [](const APyFloat& rhs, const APyFixed& lhs) { return rhs != lhs; },
             nb::is_operator()
         )
         .def(
             "__le__",
-            [](const APyFloat& a, const APyFixed& b) { return a <= b; },
+            [](const APyFloat& rhs, const APyFixed& lhs) { return rhs <= lhs; },
             nb::is_operator()
         )
         .def(
             "__lt__",
-            [](const APyFloat& a, const APyFixed& b) { return a < b; },
+            [](const APyFloat& rhs, const APyFixed& lhs) { return rhs < lhs; },
             nb::is_operator()
         )
         .def(
             "__ge__",
-            [](const APyFloat& a, const APyFixed& b) { return a >= b; },
+            [](const APyFloat& rhs, const APyFixed& lhs) { return rhs >= lhs; },
             nb::is_operator()
         )
         .def(
             "__gt__",
-            [](const APyFloat& a, const APyFixed& b) { return a > b; },
+            [](const APyFloat& rhs, const APyFixed& lhs) { return rhs > lhs; },
             nb::is_operator()
         )
 
@@ -390,16 +416,22 @@ void bind_float(nb::module_& m)
          * Logic operators
          */
         .def(
-            "__and__", [](APyFloat& a, APyFloat& b) { return a & b; }, nb::is_operator()
+            "__and__",
+            [](const APyFloat& rhs, const APyFloat& lhs) { return rhs & lhs; },
+            nb::is_operator()
         )
         .def(
-            "__or__", [](APyFloat& a, APyFloat& b) { return a | b; }, nb::is_operator()
+            "__or__",
+            [](const APyFloat& rhs, const APyFloat& lhs) { return rhs | lhs; },
+            nb::is_operator()
         )
         .def(
-            "__xor__", [](APyFloat& a, APyFloat& b) { return a ^ b; }, nb::is_operator()
+            "__xor__",
+            [](const APyFloat& rhs, const APyFloat& lhs) { return rhs ^ lhs; },
+            nb::is_operator()
         )
         .def(
-            "__invert__", [](APyFloat& a) { return ~a; }, nb::is_operator()
+            "__invert__", [](const APyFloat& rhs) { return ~rhs; }, nb::is_operator()
         )
 
         /*
@@ -475,7 +507,9 @@ void bind_float(nb::module_& m)
             :class:`bool`
             )pbdoc")
         .def_prop_ro(
-            "true_sign", [](APyFloat& a) { return a.get_sign() ? -1 : 1; }, R"pbdoc(
+            "true_sign",
+            [](const APyFloat& rhs) { return rhs.get_sign() ? -1 : 1; },
+            R"pbdoc(
             Sign value.
 
             See also
diff --git a/src/apyfloatarray.cc b/src/apyfloatarray.cc
index 651be7b21..9dd58d9c9 100644
--- a/src/apyfloatarray.cc
+++ b/src/apyfloatarray.cc
@@ -97,7 +97,7 @@ APyFloatArray::APyFloatArray(
 APyFloatArray APyFloatArray::operator+(const APyFloatArray& rhs) const
 {
     if (shape != rhs.shape) {
-        auto broadcast_shape = smallest_broadcastable_shape(shape, rhs.shape);
+        const auto broadcast_shape = smallest_broadcastable_shape(shape, rhs.shape);
         if (broadcast_shape.size() == 0) {
             throw std::length_error(fmt::format(
                 "APyFloatArray.__add__: shape mismatch, lhs.shape={}, rhs.shape={}",
@@ -1044,11 +1044,10 @@ std::variant<APyFloatArray, APyFloat> APyFloatArray::get_item(std::size_t idx) c
 
     if (get_ndim() == 1) {
         // One dimension, return APyFloat
-        APyFloat result(data[idx], exp_bits, man_bits, bias);
-        return result;
+        return APyFloat(data[idx], exp_bits, man_bits, bias);
     } else {
         // New shape contains all dimensions except the very first one
-        auto new_shape = std::vector<std::size_t>(shape.begin() + 1, shape.end());
+        const auto new_shape = std::vector<std::size_t>(shape.begin() + 1, shape.end());
 
         // Element stride is the new shape folded over multiplication
         std::size_t element_stride = std::accumulate(
@@ -1091,7 +1090,7 @@ bool APyFloatArray::is_identical(const APyFloatArray& other) const
 }
 
 APyFloatArray APyFloatArray::from_double(
-    const nb::sequence& double_seq,
+    const nb::sequence& number_seq,
     int exp_bits,
     int man_bits,
     std::optional<exp_t> bias
@@ -1100,34 +1099,37 @@ APyFloatArray APyFloatArray::from_double(
     check_exponent_format(exp_bits);
     check_mantissa_format(man_bits);
 
-    if (nb::isinstance<nb::ndarray<>>(double_seq)) {
+    if (nb::isinstance<nb::ndarray<>>(number_seq)) {
         // Sequence is NDArray. Initialize using `from_array`.
-        auto ndarray = nb::cast<nb::ndarray<nb::c_contig>>(double_seq);
+        auto ndarray = nb::cast<nb::ndarray<nb::c_contig>>(number_seq);
         return from_array(ndarray, exp_bits, man_bits, bias);
     }
 
     APyFloatArray result(
-        python_sequence_extract_shape(double_seq), exp_bits, man_bits, bias
+        python_sequence_extract_shape(number_seq), exp_bits, man_bits, bias
     );
 
-    auto py_obj = python_sequence_walk<nb::float_, nb::int_>(double_seq);
+    const auto py_obj = python_sequence_walk<nb::float_, nb::int_>(number_seq);
+    const auto actual_bias = bias.value_or(APyFloat::ieee_bias(exp_bits));
 
-    APyFloat apytypes_double(11, 52, 1023);
-    auto actual_bias = bias.value_or(APyFloat::ieee_bias(exp_bits));
     for (std::size_t i = 0; i < result.data.size(); i++) {
-        double d;
         if (nb::isinstance<nb::float_>(py_obj[i])) {
-            d = static_cast<double>(nb::cast<nb::float_>(py_obj[i]));
+            result.data[i] = APyFloat::from_double(
+                                 (double)nb::cast<nb::float_>(py_obj[i]),
+                                 exp_bits,
+                                 man_bits,
+                                 actual_bias
+            )
+                                 .get_data();
         } else if (nb::isinstance<nb::int_>(py_obj[i])) {
-            d = static_cast<int>(nb::cast<nb::int_>(py_obj[i]));
+            result.data[i]
+                = APyFloat::from_integer(
+                      nb::cast<nb::int_>(py_obj[i]), exp_bits, man_bits, actual_bias
+                )
+                      .get_data();
         } else {
             throw std::domain_error("Invalid Python objects in sequence");
         }
-        apytypes_double.set_data(
-            { sign_of_double(d), exp_t(exp_of_double(d)), man_of_double(d) }
-        );
-        APyFloat fp = apytypes_double.cast_from_double(exp_bits, man_bits, actual_bias);
-        result.data[i] = { fp.get_sign(), fp.get_exp(), fp.get_man() };
     }
     return result;
 }
@@ -1142,7 +1144,7 @@ APyFloatArray APyFloatArray::from_array(
     check_exponent_format(exp_bits);
     check_mantissa_format(man_bits);
 
-    std::size_t ndim = ndarray.ndim();
+    const std::size_t ndim = ndarray.ndim();
     assert(ndim > 0);
     std::vector<std::size_t> shape(ndim, 0);
     for (std::size_t i = 0; i < ndim; i++) {
@@ -1473,7 +1475,8 @@ APyFloat APyFloatArray::vector_sum(const QuantizationMode quantization) const
             // Conditionally add leading one's, also add room for guard bits
             // Note that exp can never be res_max_exponent here
             man_t mx = (x_is_zero_exponent ? 0 : res_leading_one) | (x.man << 3);
-            man_t msum = (sum_is_zero_exponent ? 0 : res_leading_one) | (sum_man << 3);
+            const man_t msum
+                = (sum_is_zero_exponent ? 0 : res_leading_one) | (sum_man << 3);
 
             // Compute sign and swap operands if need to make sure |x| >= |y|
             if (x.exp < sum_exp || (x.exp == sum_exp && x.man < sum_man)) {
@@ -1578,7 +1581,7 @@ APyFloatArray APyFloatArray::checked_2d_matmul(const APyFloatArray& rhs) const
         = calc_bias(max_exp_bits, exp_bits, bias, rhs.exp_bits, rhs.bias);
     const auto res_cols = rhs.shape.size() > 1 ? rhs.shape[1] : 1;
 
-    auto accumulator_mode = get_accumulator_mode_float();
+    const auto accumulator_mode = get_accumulator_mode_float();
 
     // Resulting `APyFloatArray`
     APyFloatArray result(res_shape, max_exp_bits, max_man_bits, res_bias);
diff --git a/src/apyfloatarray_iterator.h b/src/apyfloatarray_iterator.h
index 50d52cd30..247e138df 100644
--- a/src/apyfloatarray_iterator.h
+++ b/src/apyfloatarray_iterator.h
@@ -1,11 +1,12 @@
+#ifndef _APYFLOAT_ARRAY_ITERATOR_H
+#define _APYFLOAT_ARRAY_ITERATOR_H
+
 #include "apyfloat.h"
 #include "apyfloatarray.h"
 
 #include <nanobind/nanobind.h>
 #include <nanobind/stl/variant.h> // std::variant (with nanobind support)
 
-#ifndef _APYFLOAT_ARRAY_ITERATOR_H
-#define _APYFLOAT_ARRAY_ITERATOR_H
 struct APyFloatArrayIterator {
 public:
     APyFloatArrayIterator(const APyFloatArray& array, nanobind::object ref);
diff --git a/src/apyfloatarray_wrapper.cc b/src/apyfloatarray_wrapper.cc
index ccecfd493..46e1abdd2 100644
--- a/src/apyfloatarray_wrapper.cc
+++ b/src/apyfloatarray_wrapper.cc
@@ -54,200 +54,206 @@ void bind_float_array(nb::module_& m)
         .def(nb::self + nb::self)
         .def(
             "__add__",
-            [](const APyFloatArray& a, int b) {
-                if (b == 0) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot add with int");
-                };
+            [](const APyFloatArray& lhs, const nb::int_& rhs) {
+                return lhs
+                    + APyFloat::from_integer(
+                           rhs, lhs.get_exp_bits(), lhs.get_man_bits(), lhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__radd__",
-            [](const APyFloatArray& a, int b) {
-                if (b == 0) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot add with int");
-                };
+            [](const APyFloatArray& rhs, const nb::int_& lhs) {
+                return rhs
+                    + APyFloat::from_integer(
+                           lhs, rhs.get_exp_bits(), rhs.get_man_bits(), rhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__add__",
-            [](const APyFloatArray& a, float b) {
-                if (b == 0.) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot add with float");
-                };
+            [](const APyFloatArray& lhs, double rhs) {
+                return lhs
+                    + APyFloat::from_double(
+                           rhs, lhs.get_exp_bits(), lhs.get_man_bits(), lhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__radd__",
-            [](const APyFloatArray& a, float b) {
-                if (b == 0.) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot add with float");
-                };
+            [](const APyFloatArray& rhs, double lhs) {
+                return rhs
+                    + APyFloat::from_double(
+                           lhs, rhs.get_exp_bits(), rhs.get_man_bits(), rhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__add__",
-            [](const APyFloatArray& a, APyFloat& b) { return a + b; },
+            [](const APyFloatArray& lhs, const APyFloat& rhs) { return lhs + rhs; },
             nb::is_operator()
         )
         .def(
             "__radd__",
-            [](const APyFloatArray& a, APyFloat& b) { return a + b; },
+            [](const APyFloatArray& rhs, const APyFloat& lhs) { return rhs + lhs; },
             nb::is_operator()
         )
         .def(nb::self - nb::self)
         .def(-nb::self)
         .def(
             "__sub__",
-            [](const APyFloatArray& a, int b) {
-                if (b == 0) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot add with int");
-                };
+            [](const APyFloatArray& lhs, const nb::int_& rhs) {
+                return lhs
+                    - APyFloat::from_integer(
+                           rhs, lhs.get_exp_bits(), lhs.get_man_bits(), lhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__rsub__",
-            [](const APyFloatArray& a, int b) {
-                if (b == 0) {
-                    return -a;
-                } else {
-                    throw nanobind::type_error("Cannot sub with int");
-                };
+            [](const APyFloatArray& rhs, const nb::int_& lhs) {
+                return (-rhs)
+                    + APyFloat::from_integer(
+                           lhs, rhs.get_exp_bits(), rhs.get_man_bits(), rhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__sub__",
-            [](const APyFloatArray& a, float b) {
-                if (b == 0.) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot add with float");
-                };
+            [](const APyFloatArray& lhs, double rhs) {
+                return lhs
+                    - APyFloat::from_double(
+                           rhs, lhs.get_exp_bits(), lhs.get_man_bits(), lhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__rsub__",
-            [](const APyFloatArray& a, float b) {
-                if (b == 0.) {
-                    return -a;
-                } else {
-                    throw nanobind::type_error("Cannot sub with float");
-                };
+            [](const APyFloatArray& rhs, double lhs) {
+                return (-rhs)
+                    + APyFloat::from_double(
+                           lhs, rhs.get_exp_bits(), rhs.get_man_bits(), rhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__sub__",
-            [](const APyFloatArray& a, APyFloat& b) { return a - b; },
+            [](const APyFloatArray& lhs, const APyFloat& rhs) { return lhs - rhs; },
             nb::is_operator()
         )
         .def(
             "__rsub__",
-            [](const APyFloatArray& a, APyFloat& b) { return (-a) + b; },
+            [](const APyFloatArray& rhs, const APyFloat& lhs) { return (-rhs) + lhs; },
             nb::is_operator()
         )
         .def(nb::self * nb::self)
         .def(
             "__mul__",
-            [](const APyFloatArray& a, int b) {
-                if (b == 1) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot multiply with int");
-                };
+            [](const APyFloatArray& lhs, const nb::int_& rhs) {
+                return lhs
+                    * APyFloat::from_integer(
+                           rhs, lhs.get_exp_bits(), lhs.get_man_bits(), lhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__rmul__",
-            [](const APyFloatArray& a, int b) {
-                if (b == 1) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot multiply with int");
-                };
+            [](const APyFloatArray& rhs, const nb::int_& lhs) {
+                return rhs
+                    * APyFloat::from_integer(
+                           lhs, rhs.get_exp_bits(), rhs.get_man_bits(), rhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__mul__",
-            [](const APyFloatArray& a, float b) {
-                if (b == 1.) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot multiply with float");
-                };
+            [](const APyFloatArray& lhs, double rhs) {
+                return lhs
+                    * APyFloat::from_double(
+                           rhs, lhs.get_exp_bits(), lhs.get_man_bits(), lhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__rmul__",
-            [](const APyFloatArray& a, float b) {
-                if (b == 1.) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot multiply with float");
-                };
+            [](const APyFloatArray& rhs, double lhs) {
+                return rhs
+                    * APyFloat::from_double(
+                           lhs, rhs.get_exp_bits(), rhs.get_man_bits(), rhs.get_bias()
+                    );
             },
             nb::is_operator()
         )
         .def(
             "__mul__",
-            [](const APyFloatArray& a, APyFloat& b) { return a * b; },
+            [](const APyFloatArray& lhs, const APyFloat& rhs) { return lhs * rhs; },
             nb::is_operator()
         )
         .def(
             "__rmul__",
-            [](const APyFloatArray& a, APyFloat& b) { return a * b; },
+            [](const APyFloatArray& rhs, const APyFloat& lhs) { return rhs * lhs; },
             nb::is_operator()
         )
         .def(nb::self / nb::self)
         .def(
             "__truediv__",
-            [](const APyFloatArray& a, int b) {
-                if (b == 1) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot divide with int");
-                };
+            [](const APyFloatArray& lhs, const nb::int_& rhs) {
+                return lhs
+                    / APyFloat::from_integer(
+                           rhs, lhs.get_exp_bits(), lhs.get_man_bits(), lhs.get_bias()
+                    );
+            },
+            nb::is_operator()
+        )
+        .def(
+            "__rtruediv__",
+            [](const APyFloatArray& rhs, const nb::int_& lhs) {
+                return rhs.rtruediv(APyFloat::from_integer(
+                    lhs, rhs.get_exp_bits(), rhs.get_man_bits(), rhs.get_bias()
+                ));
             },
             nb::is_operator()
         )
         .def(
             "__truediv__",
-            [](const APyFloatArray& a, float b) {
-                if (b == 1.) {
-                    return a;
-                } else {
-                    throw nanobind::type_error("Cannot divide with float");
-                };
+            [](const APyFloatArray& lhs, double rhs) {
+                return lhs
+                    / APyFloat::from_double(
+                           rhs, lhs.get_exp_bits(), lhs.get_man_bits(), lhs.get_bias()
+                    );
+            },
+            nb::is_operator()
+        )
+        .def(
+            "__rtruediv__",
+            [](const APyFloatArray& rhs, double lhs) {
+                return rhs.rtruediv(APyFloat::from_double(
+                    lhs, rhs.get_exp_bits(), rhs.get_man_bits(), rhs.get_bias()
+                ));
             },
             nb::is_operator()
         )
         .def(
             "__truediv__",
-            [](const APyFloatArray& a, APyFloat& b) { return a / b; },
+            [](const APyFloatArray& lhs, const APyFloat& rhs) { return lhs / rhs; },
             nb::is_operator()
         )
         .def(
             "__rtruediv__",
-            [](const APyFloatArray& a, APyFloat& b) { return a.rtruediv(b); },
+            [](const APyFloatArray& rhs, const APyFloat& lhs) {
+                return rhs.rtruediv(lhs);
+            },
             nb::is_operator()
         )
         .def("__abs__", &APyFloatArray::abs)
@@ -328,16 +334,16 @@ void bind_float_array(nb::module_& m)
         .def_static(
             "from_float",
             &APyFloatArray::from_double,
-            nb::arg("float_sequence"),
+            nb::arg("number_sequence"),
             nb::arg("exp_bits"),
             nb::arg("man_bits"),
             nb::arg("bias") = nb::none(),
             R"pbdoc(
-            Create an :class:`APyFloatArray` object from a sequence of :class:`float`.
+            Create an :class:`APyFloatArray` object from a sequence of :class:`int` or :class:`float`.
 
             Parameters
             ----------
-            float_sequence : sequence of float
+            number_sequence : sequence of numbers
                 Floating point values to initialize from. The tensor shape will be taken
                 from the sequence shape.
             exp_bits : int
@@ -361,8 +367,8 @@ void bind_float_array(nb::module_& m)
                 # Array `a`, initialized from floating-point values.
                 a = APyFloatArray.from_float([1.0, 1.25, 1.49], exp_bits=10, man_bits=15)
 
-                # Array `b` (2 x 3 matrix), initialized from floating-point values.
-                b = APyFloatArray.from_float(
+                # Array `lhs` (2 x 3 matrix), initialized from floating-point values.
+                lhs = APyFloatArray.from_float(
                     [
                         [1.0, 2.0, 3.0],
                         [4.0, 5.0, 6.0],
diff --git a/src/broadcast.h b/src/broadcast.h
index a3780feaa..8259704dc 100644
--- a/src/broadcast.h
+++ b/src/broadcast.h
@@ -5,6 +5,9 @@
  * https://numpy.org/doc/stable/user/basics.broadcasting.html
  */
 
+#ifndef _BROADCAST_H
+#define _BROADCAST_H
+
 #include "apytypes_util.h"
 
 #include <algorithm> // std::any_of, std::copy_n
@@ -160,3 +163,5 @@ static APY_INLINE void broadcast_data_copy(
         std::for_each(broadcast_offsets.begin(), broadcast_offsets.end(), broadcast);
     }
 }
+
+#endif // _BROADCAST_H
diff --git a/src/python_util.h b/src/python_util.h
index a23f8e96f..c5028ec1d 100644
--- a/src/python_util.h
+++ b/src/python_util.h
@@ -2,6 +2,9 @@
  * Python utility functions and helpers
  */
 
+#ifndef _PYTHON_UTIL_H
+#define _PYTHON_UTIL_H
+
 // Python object access through Pybind
 #include <nanobind/nanobind.h>
 #include <nanobind/stl/string.h>
@@ -88,11 +91,22 @@
 }
 
 /*!
- * Python arbitrary long integer object to GMP limb vector
+ * Test if Python long integer (`nb::int_`) is negative
+ */
+[[maybe_unused]] static APY_INLINE bool
+python_long_is_negative(const nanobind::int_& py_long_int)
+{
+    const PyLongObject* py_long = (const PyLongObject*)py_long_int.ptr();
+    return PyLong_IsNegative(py_long);
+}
+
+/*!
+ * Python arbitrary long integer object to GMP limb vector. If `n_min_limbs` is set, at
+ * least that many limbs will be available in the result.
  */
 [[maybe_unused]] static APY_INLINE std::vector<mp_limb_t> python_long_to_limb_vec(
     const nanobind::int_& py_long_int,
-    std::optional<std::size_t> n_exact_limbs = std::nullopt
+    std::optional<std::size_t> n_min_limbs = std::nullopt
 )
 {
     static_assert(
@@ -104,7 +118,7 @@
     long py_long_digits = PyLong_DigitCount(py_long);
     bool py_long_is_negative = PyLong_IsNegative(py_long);
 
-    std::vector<mp_limb_t> result {};
+    std::vector<mp_limb_t> result;
     if (py_long_digits == 0) {
         // Python integer is zero
         result = { 0 };
@@ -129,8 +143,8 @@
 
         // Compute how many limbs to copy
         std::size_t limb_copy_count {};
-        if (n_exact_limbs.has_value()) {
-            limb_copy_count = std::min(*n_exact_limbs, mpz_size(mpz_from_py_long));
+        if (n_min_limbs.has_value()) {
+            limb_copy_count = std::min(*n_min_limbs, mpz_size(mpz_from_py_long));
         } else {
             limb_copy_count = mpz_size(mpz_from_py_long);
         }
@@ -148,17 +162,15 @@
     }
 
     // Possibly extend the vector
-    if (n_exact_limbs.has_value() && *n_exact_limbs > result.size()) {
-        std::fill_n(std::back_inserter(result), *n_exact_limbs - result.size(), 0);
+    if (n_min_limbs.has_value() && *n_min_limbs > result.size()) {
+        std::fill_n(std::back_inserter(result), *n_min_limbs - result.size(), 0);
     }
 
     // Negate limb vector if negative
     if (py_long_is_negative) {
-        std::transform(result.begin(), result.end(), result.begin(), std::bit_not {});
-        mpn_add_1(&result[0], &result[0], result.size(), 1);
+        limb_vector_negate(std::begin(result), std::end(result), std::begin(result));
     }
 
-    // Return the result
     return result;
 }
 
@@ -392,3 +404,5 @@ python_sequence_walk(const nanobind::sequence& py_seq)
     // Return the result
     return result;
 }
+
+#endif // _PYTHON_UTIL_H


Test patch:
diff --git a/lib/test/apyfixed/test_arithmetic.py b/lib/test/apyfixed/test_arithmetic.py
index 3df747e48..518006977 100644
--- a/lib/test/apyfixed/test_arithmetic.py
+++ b/lib/test/apyfixed/test_arithmetic.py
@@ -318,7 +318,7 @@ def test_prod():
         APyFixed.from_float(0.3, int_bits=2, frac_bits=5),
         APyFixed.from_float(0.7, int_bits=4, frac_bits=7),
     ]
-    assert math.prod(s).is_identical(APyFixed(900, bits=18, int_bits=6))
+    assert math.prod(s).is_identical(APyFixed(28800, bits=25, int_bits=8))
 
 
 def test_issue_198():
@@ -475,13 +475,53 @@ def test_issue_224():
     assert x.leading_fractional_zeros == 6
 
 
-def test_operation_with_numbers():
+def test_operation_with_integers():
     a = APyFixed(5, 6, 2)
-    # Integer
-    assert (a + 0).is_identical(a)
-    assert (0 + a).is_identical(a)
-    assert (a - 0).is_identical(a)
-    assert (0 - a).is_identical(-a)
-    assert (a * 1).is_identical(a)
-    assert (1 * a).is_identical(a)
-    assert (a / 1).is_identical(a)
+    one = APyFixed(4, 6, 2)
+    zero = APyFixed(0, 6, 2)
+
+    # Integer identities
+    assert (_ := a + 0).is_identical(_ := a + zero)
+    assert (_ := 0 + a).is_identical(_ := zero + a)
+    assert (_ := a - 0).is_identical(_ := a - zero)
+    assert (_ := 0 - a).is_identical(_ := zero - a)
+    assert (_ := a * 1).is_identical(_ := a * one)
+    assert (_ := 1 * a).is_identical(_ := one * a)
+    assert (_ := a / 1).is_identical(_ := a / one)
+
+    # Other integer operations
+    neg_two = APyFixed(248, bits=8, int_bits=6)
+    assert (_ := a + (-2)).is_identical(_ := a + neg_two)
+    assert (_ := (-2) + a).is_identical(_ := neg_two + a)
+    assert (_ := a - (-2)).is_identical(_ := a - neg_two)
+    assert (_ := (-2) - a).is_identical(_ := neg_two - a)
+    assert (_ := a * (-2)).is_identical(_ := a * neg_two)
+    assert (_ := (-2) * a).is_identical(_ := neg_two * a)
+    assert (_ := a / (-2)).is_identical(_ := a / neg_two)
+    assert (_ := (-2) / a).is_identical(_ := neg_two / a)
+
+
+def test_operation_with_floats():
+    a = APyFixed(5, 6, 2)
+    one = APyFixed(4, 6, 2)
+    zero = APyFixed(0, 6, 2)
+
+    # Identities
+    assert (_ := a + 0.0).is_identical(_ := a + zero)
+    assert (_ := 0.0 + a).is_identical(_ := zero + a)
+    assert (_ := a - 0.0).is_identical(_ := a - zero)
+    assert (_ := 0.0 - a).is_identical(_ := zero - a)
+    assert (_ := a * 1.0).is_identical(_ := a * one)
+    assert (_ := 1.0 * a).is_identical(_ := one * a)
+    assert (_ := a / 1.0).is_identical(_ := a / one)
+
+    # Other operations. 2.125 should quantize to -2.25
+    b = APyFixed(247, bits=8, int_bits=6)
+    assert (_ := a + (-2.125)).is_identical(_ := a + b)
+    assert (_ := (-2.125) + a).is_identical(_ := b + a)
+    assert (_ := a - (-2.125)).is_identical(_ := a - b)
+    assert (_ := (-2.125) - a).is_identical(_ := b - a)
+    assert (_ := a * (-2.125)).is_identical(_ := a * b)
+    assert (_ := (-2.125) * a).is_identical(_ := b * a)
+    assert (_ := a / (-2.125)).is_identical(_ := a / b)
+    assert (_ := (-2.125) / a).is_identical(_ := b / a)
diff --git a/lib/test/apyfixed/test_methods.py b/lib/test/apyfixed/test_methods.py
index 5983f9f81..d3de85483 100644
--- a/lib/test/apyfixed/test_methods.py
+++ b/lib/test/apyfixed/test_methods.py
@@ -120,3 +120,39 @@ def test_fixed_cast_throws():
     APyFixed(0, int_bits=10, frac_bits=5).cast(int_bits=5, frac_bits=2)
 
     # Casting to a negative number of `bits` casts
+
+
+def test_from_float_with_non_floats():
+    assert APyFixed.from_float(3, 5, 0).is_identical(
+        APyFixed(3, int_bits=5, frac_bits=0)
+    )
+    assert APyFixed.from_float(3, 5, 1).is_identical(
+        APyFixed(6, int_bits=5, frac_bits=1)
+    )
+    assert APyFixed.from_float(-3, 5, 0).is_identical(APyFixed(29, bits=5, int_bits=5))
+
+    # from_float currently wraps around
+    assert APyFixed.from_float(17, 5, 0).is_identical(
+        APyFixed(17, int_bits=5, frac_bits=0)
+    )
+    assert APyFixed.from_float(17, 4, 0).is_identical(
+        APyFixed(1, int_bits=4, frac_bits=0)
+    )
+
+    # Test long integer (256 bits)
+    int_val = 0xFF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF_FF
+    assert APyFixed.from_float(int_val, 257, 0).is_identical(
+        APyFixed(int_val, int_bits=257, frac_bits=0)
+    )
+
+    # Test negative number of fractioal bits
+    assert APyFixed.from_float(14, int_bits=6, frac_bits=-1).is_identical(
+        APyFixed(7, bits=5, int_bits=6)
+    )
+
+    # Test from bool
+    APyFixed.from_float(True, 5, 0).is_identical(APyFixed(1, int_bits=5, frac_bits=0))
+
+    # Raise for string
+    with pytest.raises(ValueError, match="Non supported type"):
+        APyFixed.from_float("14", 5, 0)
diff --git a/lib/test/apyfixed/test_not_implemented.py b/lib/test/apyfixed/test_not_implemented.py
index 290ca782c..b194cde03 100644
--- a/lib/test/apyfixed/test_not_implemented.py
+++ b/lib/test/apyfixed/test_not_implemented.py
@@ -19,14 +19,3 @@ def test__str__():
         APyFixed(0, 1, 0).__str__(base=16)
     with pytest.raises(ValueError, match=r"APyFixed::to_string"):
         APyFixed(0, 1, 0).__str__(base=37)
-
-
-def test_missing_arithmetic():
-    with pytest.raises(ValueError, match=r"Not implemented"):
-        _ = APyFixed(0, 1, 0) + 4
-    with pytest.raises(ValueError, match=r"Not implemented"):
-        _ = APyFixed(0, 1, 0) - 4
-    with pytest.raises(ValueError, match=r"Not implemented"):
-        _ = APyFixed(0, 1, 0) * 4
-    with pytest.raises(ValueError, match=r"Not implemented"):
-        _ = APyFixed(0, 1, 0) / 4
diff --git a/lib/test/apyfixedarray/test_arithmetic.py b/lib/test/apyfixedarray/test_arithmetic.py
index 1d1c95027..35ba11ab8 100644
--- a/lib/test/apyfixedarray/test_arithmetic.py
+++ b/lib/test/apyfixedarray/test_arithmetic.py
@@ -121,7 +121,7 @@ def test_array_add_scalar():
 
 def test_array_sum():
     a = APyFixedArray([-5, -6, 7, -1], bits=10, int_bits=5)
-    assert sum(a).is_identical(APyFixed(-5, bits=13, int_bits=8))
+    assert sum(a).is_identical(APyFixed(16379, bits=14, int_bits=9))
     b = APyFixedArray.from_float(
         [
             [1.0, 2.0, 3.0],
@@ -130,7 +130,7 @@ def test_array_sum():
         bits=8,
         int_bits=6,
     )
-    assert sum(b).is_identical(APyFixedArray([20, 28, 36], bits=9, int_bits=7))
+    assert sum(b).is_identical(APyFixedArray([20, 28, 36], bits=10, int_bits=8))
 
 
 def test_array_sub():
@@ -411,7 +411,7 @@ def test_array_div_scalar():
 
 def test_array_prod():
     a = APyFixedArray([-5, -6, 7, -1], bits=10, int_bits=5)
-    assert math.prod(a).is_identical(APyFixed(1099511627566, bits=40, int_bits=20))
+    assert math.prod(a).is_identical(APyFixed(1125899906835904, bits=50, int_bits=25))
     b = APyFixedArray.from_float(
         [
             [1.0, 2.0, 3.0],
@@ -421,7 +421,7 @@ def test_array_prod():
         int_bits=6,
     )
     assert math.prod(b).is_identical(
-        APyFixedArray([64, 160, 288], bits=16, int_bits=12)
+        APyFixedArray([256, 640, 1152], bits=24, int_bits=18)
     )
 
 
@@ -550,20 +550,53 @@ def test_long_matrix_addition():
     )
 
 
-def test_operation_with_numbers():
-    a = APyFixedArray([5, 7, -1], bits=6, int_bits=2)
-    # Integer
-    assert (a + 0).is_identical(a)
-    assert (0 + a).is_identical(a)
-    assert (a - 0).is_identical(a)
-    assert (0 - a).is_identical(-a)
-    assert (a * 1).is_identical(a)
-    assert (1 * a).is_identical(a)
-
-    # Float
-    assert (a + 0.0).is_identical(a)
-    assert (0.0 + a).is_identical(a)
-    assert (a - 0.0).is_identical(a)
-    assert (0.0 - a).is_identical(-a)
-    assert (a * 1.0).is_identical(a)
-    assert (1.0 * a).is_identical(a)
+def test_operation_with_integers():
+    a = APyFixedArray([5], 6, 2)
+    one = APyFixedArray([4], 6, 2)
+    zero = APyFixedArray([0], 6, 2)
+
+    # Integer identities
+    assert (_ := a + 0).is_identical(_ := a + zero)
+    assert (_ := 0 + a).is_identical(_ := zero + a)
+    assert (_ := a - 0).is_identical(_ := a - zero)
+    assert (_ := 0 - a).is_identical(_ := zero - a)
+    assert (_ := a * 1).is_identical(_ := a * one)
+    assert (_ := 1 * a).is_identical(_ := one * a)
+    assert (_ := a / 1).is_identical(_ := a / one)
+
+    # Other integer operations
+    neg_two = APyFixedArray([248], bits=8, int_bits=6)
+    assert (_ := a + (-2)).is_identical(_ := a + neg_two)
+    assert (_ := (-2) + a).is_identical(_ := neg_two + a)
+    assert (_ := a - (-2)).is_identical(_ := a - neg_two)
+    assert (_ := (-2) - a).is_identical(_ := neg_two - a)
+    assert (_ := a * (-2)).is_identical(_ := a * neg_two)
+    assert (_ := (-2) * a).is_identical(_ := neg_two * a)
+    assert (_ := a / (-2)).is_identical(_ := a / neg_two)
+    assert (_ := (-2) / a).is_identical(_ := neg_two / a)
+
+
+def test_operation_with_floats():
+    a = APyFixedArray([5], 6, 2)
+    one = APyFixedArray([4], 6, 2)
+    zero = APyFixedArray([0], 6, 2)
+
+    # Identities
+    assert (_ := a + 0.0).is_identical(_ := a + zero)
+    assert (_ := 0.0 + a).is_identical(_ := zero + a)
+    assert (_ := a - 0.0).is_identical(_ := a - zero)
+    assert (_ := 0.0 - a).is_identical(_ := zero - a)
+    assert (_ := a * 1.0).is_identical(_ := a * one)
+    assert (_ := 1.0 * a).is_identical(_ := one * a)
+    assert (_ := a / 1.0).is_identical(_ := a / one)
+
+    # Other operations. 2.125 should quantize to -2.25
+    b = APyFixed(247, bits=8, int_bits=6)
+    assert (_ := a + (-2.125)).is_identical(_ := a + b)
+    assert (_ := (-2.125) + a).is_identical(_ := b + a)
+    assert (_ := a - (-2.125)).is_identical(_ := a - b)
+    assert (_ := (-2.125) - a).is_identical(_ := b - a)
+    assert (_ := a * (-2.125)).is_identical(_ := a * b)
+    assert (_ := (-2.125) * a).is_identical(_ := b * a)
+    assert (_ := a / (-2.125)).is_identical(_ := a / b)
+    assert (_ := (-2.125) / a).is_identical(_ := b / a)
diff --git a/lib/test/apyfloat/test_arithmetic.py b/lib/test/apyfloat/test_arithmetic.py
index ec8fb3f30..60bd33ef7 100644
--- a/lib/test/apyfloat/test_arithmetic.py
+++ b/lib/test/apyfloat/test_arithmetic.py
@@ -1130,22 +1130,40 @@ def test_binary_logic():
 
 
 def test_operation_with_numbers():
-    a = APyFloat(0, 5, 10, 4, 5)
-    # Integer
+    a = APyFloat(0, 15, 2, 5, 2)  # 1.75
+
+    # Integer identities
     assert (a + 0).is_identical(a)
     assert (0 + a).is_identical(a)
     assert (a - 0).is_identical(a)
     assert (0 - a).is_identical(-a)
     assert (a * 1).is_identical(a)
     assert (1 * a).is_identical(a)
-
-    # Float
-    assert (a + 0.0).is_identical(a)
-    assert (0.0 + a).is_identical(a)
-    assert (a - 0.0).is_identical(a)
-    assert (0.0 - a).is_identical(-a)
-    assert (a * 1.0).is_identical(a)
-    assert (1.0 * a).is_identical(a)
+    assert (a / 1).is_identical(a)
+
+    # Operations with floats
+    q_eight = 9  # Should quantize to eight
+    eight = APyFloat(0, 18, 0, 5, 2)
+    assert (_ := a + q_eight).is_identical(a + eight)
+    assert (_ := q_eight + a).is_identical(eight + a)
+    assert (_ := a - q_eight).is_identical(a - eight)
+    assert (_ := q_eight - a).is_identical(eight - a)
+    assert (_ := a * q_eight).is_identical(a * eight)
+    assert (_ := q_eight * a).is_identical(eight * a)
+    assert (_ := a / q_eight).is_identical(a / eight)
+    assert (_ := q_eight / a).is_identical(eight / a)
+
+    # Operations with floats
+    q_one = 1.125  # Should quantize to one
+    q_zero = 0.125  # Should quantize to zero
+    assert (_ := a + q_zero).is_identical(a)
+    assert (_ := q_zero + a).is_identical(a)
+    assert (_ := a - q_zero).is_identical(a)
+    assert (_ := q_zero - a).is_identical(-a)
+    assert (_ := a * q_one).is_identical(a)
+    assert (_ := q_one * a).is_identical(a)
+    assert (_ := a / q_one).is_identical(a)
+    assert (_ := q_one / a).is_identical(APyFloat(0, 14, 1, 5, 2))
 
 
 def test_mantissa_full_range():
diff --git a/lib/test/apyfloat/test_methods.py b/lib/test/apyfloat/test_methods.py
index ea44b91bf..32d58416a 100644
--- a/lib/test/apyfloat/test_methods.py
+++ b/lib/test/apyfloat/test_methods.py
@@ -51,6 +51,11 @@ def test_scalar_from_float_raises():
         match="Mantissa bits must be a non-negative integer less or equal to .. but -300 was given",
     ):
         APyFloat.from_float(0, 5, -300)
+    with pytest.raises(
+        ValueError,
+        match="Non supported type",
+    ):
+        APyFloat.from_float("0", 5, 10)
 
 
 def test_scalar_from_bits_raises():
@@ -199,6 +204,57 @@ def test_from_float():
     b = APyFloat.from_float(5e-324, 11, 52)
     assert b.is_identical(a)
 
+    # Big float should become infinity
+    a = APyFloat.from_float(-(2**10), 4, 15)
+    assert a.is_identical(APyFloat(1, 15, 0, 4, 15))
+
+
+def test_from_float_with_non_floats():
+    a = APyFloat.from_float(16, exp_bits=4, man_bits=2)
+    assert a.is_identical(APyFloat(sign=0, exp=11, man=0, exp_bits=4, man_bits=2))
+
+    # Should quantize to 16.0
+    a = APyFloat.from_float(17, exp_bits=4, man_bits=2)
+    assert a.is_identical(APyFloat(sign=0, exp=11, man=0, exp_bits=4, man_bits=2))
+
+    # Should quantize to -20.0
+    a = APyFloat.from_float(-19, exp_bits=4, man_bits=2)
+    assert a.is_identical(APyFloat(sign=1, exp=11, man=1, exp_bits=4, man_bits=2))
+
+    # Tie break, should quantize to 16.0
+    a = APyFloat.from_float(18, exp_bits=4, man_bits=2)
+    assert a.is_identical(APyFloat(sign=0, exp=11, man=0, exp_bits=4, man_bits=2))
+
+    # Tie break, should quantize to 24.0
+    a = APyFloat.from_float(22, exp_bits=4, man_bits=2)
+    assert a.is_identical(APyFloat(sign=0, exp=11, man=2, exp_bits=4, man_bits=2))
+
+    # Should quantize to 28.0
+    a = APyFloat.from_float(29, exp_bits=4, man_bits=2)
+    assert a.is_identical(APyFloat(sign=0, exp=11, man=3, exp_bits=4, man_bits=2))
+
+    # Should quantize to 32.0
+    a = APyFloat.from_float(31, exp_bits=4, man_bits=2)
+    assert a.is_identical(APyFloat(sign=0, exp=12, man=0, exp_bits=4, man_bits=2))
+
+    # 152-bit number, should become negative infinity
+    a = APyFloat.from_float(
+        -0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, exp_bits=4, man_bits=2
+    )
+    assert a.is_identical(APyFloat(sign=1, exp=15, man=0, exp_bits=4, man_bits=2))
+
+    # Test with bool and non IEEE-like bias
+    a = APyFloat.from_float(True, exp_bits=4, man_bits=2, bias=4)
+    assert a.is_identical(
+        APyFloat(sign=0, exp=4, man=0, exp_bits=4, man_bits=2, bias=4)
+    )
+
+    # Test with big integer
+    a = APyFloat.from_float(2**2047, exp_bits=28, man_bits=2)
+    assert a.is_identical(
+        APyFloat(sign=0, exp=(2047 + (2**27) - 1), man=0, exp_bits=28, man_bits=2)
+    )
+
 
 def test_cast_special_cases():
     # Only change the number of exponent bits
diff --git a/lib/test/apyfloatarray/test_arithmetic.py b/lib/test/apyfloatarray/test_arithmetic.py
index 0c567e7af..50c4cc914 100644
--- a/lib/test/apyfloatarray/test_arithmetic.py
+++ b/lib/test/apyfloatarray/test_arithmetic.py
@@ -211,15 +211,6 @@ def test_array_add_scalar():
     assert (b + a).is_identical(ans)
 
 
-@pytest.mark.float_array
-def test_array_add_int_float():
-    a = APyFloatArray.from_float([12, 23, 34], 8, 10)
-    assert (a + 0).is_identical(a)
-    assert (0 + a).is_identical(a)
-    assert (a + 0.0).is_identical(a)
-    assert (0.0 + a).is_identical(a)
-
-
 @pytest.mark.float_array
 def test_array_sub():
     a = APyFloatArray.from_float([0, 0.125, 2.5, 12], 5, 7)
@@ -267,15 +258,6 @@ def test_array_sub_scalar():
     assert (a - z98).is_identical(APyFloatArray.from_float([12, 23, 34], 9, 10))
 
 
-@pytest.mark.float_array
-def test_array_sub_int_float():
-    a = APyFloatArray.from_float([12, 23, 34], 8, 10)
-    assert (a - 0).is_identical(a)
-    assert (a - 0.0).is_identical(a)
-    assert (0 - a).is_identical(-a)
-    assert (0 - a).is_identical(-a)
-
-
 @pytest.mark.float_array
 def test_array_mul():
     a = APyFloatArray.from_float([0, 0.125, 2.5, 12], 5, 7)
@@ -648,15 +630,6 @@ def test_array_mul_scalar():
     assert res.is_identical(APyFloatArray([0], [1], [0], exp_bits=11, man_bits=52))
 
 
-@pytest.mark.float_array
-def test_array_mul_int_float():
-    a = APyFloatArray.from_float([12, 23, 34], 8, 10)
-    assert (a * 1).is_identical(a)
-    assert (1 * a).is_identical(a)
-    assert (a * 1.0).is_identical(a)
-    assert (1.0 * a).is_identical(a)
-
-
 @pytest.mark.float_array
 def test_array_div():
     a = APyFloatArray.from_float([3, -0.09375, -12.5, 96], 6, 6)
@@ -727,13 +700,6 @@ def test_array_rdiv_scalar():
     assert (b / a).is_identical(APyFloatArray.from_float([2, 1.6, 0.25], 9, 10))
 
 
-@pytest.mark.float_array
-def test_array_div_int_float():
-    a = APyFloatArray.from_float([4, 12, 40], 9, 10)
-    assert (a / 1).is_identical(a)
-    assert (a / 1.0).is_identical(a)
-
-
 @pytest.mark.float_array
 def test_transpose():
 
@@ -1023,6 +989,7 @@ def test_array_add_sub_zero_sign(man, with_scalar):
         assert (_ := non_zero - rhs)[0].sign == True
 
 
+@pytest.mark.float_array
 @pytest.mark.float_add
 def test_array_infinity_saturation_add():
     # Array x Scalar
@@ -1062,6 +1029,7 @@ def test_array_infinity_saturation_add():
         assert res.is_identical(APyFloatArray([1], [30], [3], 5, 2))
 
 
+@pytest.mark.float_array
 @pytest.mark.float_div
 def test_array_infinity_saturation_div():
     # Array x Scalar
@@ -1101,6 +1069,7 @@ def test_array_infinity_saturation_div():
         assert res.is_identical(APyFloatArray([1], [30], [3], 5, 2))
 
 
+@pytest.mark.float_array
 @pytest.mark.float_add
 @pytest.mark.parametrize("with_scalar", [False, True])
 def test_array_add_mixed_bias_overflow(with_scalar):
@@ -1145,6 +1114,7 @@ def test_array_add_mixed_bias_overflow(with_scalar):
     )
 
 
+@pytest.mark.float_array
 @pytest.mark.float_add
 @pytest.mark.parametrize("with_scalar", [False, True])
 def test_array_add_mixed_bias_underflow(with_scalar):
@@ -1180,6 +1150,7 @@ def test_array_add_mixed_bias_underflow(with_scalar):
     )
 
 
+@pytest.mark.float_array
 @pytest.mark.float_sub
 @pytest.mark.parametrize("with_scalar", [False, True])
 def test_array_sub_mixed_bias(with_scalar):
@@ -1206,3 +1177,41 @@ def test_array_sub_mixed_bias(with_scalar):
     assert (_ := x - y).is_identical(
         APyFloatArray([0], [0], [0], exp_bits=5, man_bits=2, bias=17)
     )
+
+
+@pytest.mark.float_array
+def test_array_operation_with_numbers():
+    a = APyFloatArray([0], [15], [2], 5, 2)  # 1.75
+
+    # Integer identities
+    assert (a + 0).is_identical(a)
+    assert (0 + a).is_identical(a)
+    assert (a - 0).is_identical(a)
+    assert (0 - a).is_identical(-a)
+    assert (a * 1).is_identical(a)
+    assert (1 * a).is_identical(a)
+    assert (a / 1).is_identical(a)
+
+    # Operations with integers
+    q_eight = 9  # Should quantize to eight
+    eight = APyFloatArray([0], [18], [0], 5, 2)
+    assert (_ := a + q_eight).is_identical(a + eight)
+    assert (_ := q_eight + a).is_identical(eight + a)
+    assert (_ := a - q_eight).is_identical(a - eight)
+    assert (_ := q_eight - a).is_identical(eight - a)
+    assert (_ := a * q_eight).is_identical(a * eight)
+    assert (_ := q_eight * a).is_identical(eight * a)
+    assert (_ := a / q_eight).is_identical(a / eight)
+    assert (_ := q_eight / a).is_identical(eight / a)
+
+    # Operations with floats
+    q_one = 1.125  # Should quantize to one
+    q_zero = 0.125  # Should quantize to zero
+    assert (_ := a + q_zero).is_identical(a)
+    assert (_ := q_zero + a).is_identical(a)
+    assert (_ := a - q_zero).is_identical(a)
+    assert (_ := q_zero - a).is_identical(-a)
+    assert (_ := a * q_one).is_identical(a)
+    assert (_ := q_one * a).is_identical(a)
+    assert (_ := a / q_one).is_identical(a)
+    assert (_ := q_one / a).is_identical(APyFloatArray([0], [14], [1], 5, 2))
diff --git a/lib/test/apyfloatarray/test_methods.py b/lib/test/apyfloatarray/test_methods.py
index df11c4003..0c43155ef 100644
--- a/lib/test/apyfloatarray/test_methods.py
+++ b/lib/test/apyfloatarray/test_methods.py
@@ -40,6 +40,56 @@ def test_array_from_float_raises():
         match="Mantissa bits must be a non-negative integer less or equal to .. but -300 was given",
     ):
         APyFloatArray.from_float([0], 5, -300)
+    with pytest.raises(
+        ValueError,
+        match="python_sequence_extract_shape",
+    ):
+        APyFloatArray.from_float(["0"], 5, 10)
+
+
+def test_from_float_with_non_floats():
+    a = APyFloatArray.from_float([16], exp_bits=4, man_bits=2)
+    assert a.is_identical(APyFloatArray([0], [11], [0], exp_bits=4, man_bits=2))
+
+    # Should quantize to 16.0
+    a = APyFloatArray.from_float([17], exp_bits=4, man_bits=2)
+    assert a.is_identical(APyFloatArray([0], [11], [0], exp_bits=4, man_bits=2))
+
+    # Should quantize to -20.0
+    a = APyFloatArray.from_float([-19], exp_bits=4, man_bits=2)
+    assert a.is_identical(APyFloatArray([1], [11], [1], exp_bits=4, man_bits=2))
+
+    # Tie break, should quantize to 16.0
+    a = APyFloatArray.from_float([18], exp_bits=4, man_bits=2)
+    assert a.is_identical(APyFloatArray([0], [11], [0], exp_bits=4, man_bits=2))
+
+    # Tie break, should quantize to 24.0
+    a = APyFloatArray.from_float([22], exp_bits=4, man_bits=2)
+    assert a.is_identical(APyFloatArray([0], [11], [2], exp_bits=4, man_bits=2))
+
+    # Should quantize to 28.0
+    a = APyFloatArray.from_float([29], exp_bits=4, man_bits=2)
+    assert a.is_identical(APyFloatArray([0], [11], [3], exp_bits=4, man_bits=2))
+
+    # Should quantize to 32.0
+    a = APyFloatArray.from_float([31], exp_bits=4, man_bits=2)
+    assert a.is_identical(APyFloatArray([0], [12], [0], exp_bits=4, man_bits=2))
+
+    # 152-bit number, should become negative infinity
+    a = APyFloatArray.from_float(
+        [-0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF], exp_bits=4, man_bits=2
+    )
+    assert a.is_identical(APyFloatArray([1], [15], [0], exp_bits=4, man_bits=2))
+
+    # Test with bool and non IEEE-like bias
+    a = APyFloatArray.from_float([True], exp_bits=4, man_bits=2, bias=4)
+    assert a.is_identical(APyFloatArray([0], [4], [0], exp_bits=4, man_bits=2, bias=4))
+
+    # Test with big integer
+    a = APyFloatArray.from_float([2**2047], exp_bits=28, man_bits=2)
+    assert a.is_identical(
+        APyFloatArray([0], [(2047 + (2**27) - 1)], [0], exp_bits=28, man_bits=2)
+    )
 
 
 def test_array_cast_raises():