Repository: apytypes/apytypes (language: python)

Issue:
Nanobind v2.0.0 kw-only arguments
### lib/test/test_contextmanagers.py
```python
@pytest.mark.xfail()
def test_context_kw_only():
    """
    This method should raise a `TypeError`, but since kw-only arguments are not part of
    Nanobind <= v1.9.2, we wait until Nanobind v2.0.0 get a tagged release.
    """
    with pytest.raises(TypeError):  # keyword only
        with AccumulatorContext(5, 2):
            pass
```


Discussion / hints:
Depends on: https://github.com/wjakob/nanobind/issues/420

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b4b1096bb..439fdce54 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,21 +10,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 
 - Added methods `next_up()` and `next_down()` for floating-point scalars.
-- Broadcasting support [as NumPy](https://numpy.org/doc/stable/user/basics.broadcasting.html)
+- Broadcasting support
+  [as NumPy](https://numpy.org/doc/stable/user/basics.broadcasting.html).
   - `APyFixedArray.broadcast_to()`
   - `APyFloatArray.broadcast_to()`
   - Automatic broadcasting on array arithmetic
 - Support arithmetic between APyTypes classes and Python's built-in floats and ints.
+- Support for automatic stub generation added.
 
 ### Changed
 
+- Updated [nanobind](https://github.com/wjakob/nanobind) dependency from v1.9.2 to
+  v2.0.0.
+
 ### Fixed
 
 - Smaller fixes in quantization for floating-point scalar multiplication.
 - Fix in floating-point `cast`-method when result became subnormal.
 - Fix bug in `APyFixedArray.__truediv__()` where program crashes on long-limb zero
   denominators.
-- Fix bug where single-dimensional tuple representations missed out on a comma
+- Fix bug where single-dimensional tuple representations missed out on a comma.
 
 ### Removed
 
diff --git a/lib/apytypes/__init__.pyi b/lib/apytypes/__init__.pyi
index 197bbfc5f..93d6c49a4 100644
--- a/lib/apytypes/__init__.pyi
+++ b/lib/apytypes/__init__.pyi
@@ -1,19 +1,21 @@
-from __future__ import annotations
-from apytypes._apytypes import APyFixed
-from apytypes._apytypes import APyFixedArray
-from apytypes._apytypes import APyFixedAccumulatorContext
-from apytypes._apytypes import APyFixedCastContext
-from apytypes._apytypes import APyFloat
-from apytypes._apytypes import APyFloatArray
-from apytypes._apytypes import APyFloatAccumulatorContext
-from apytypes._apytypes import OverflowMode
-from apytypes._apytypes import APyFloatQuantizationContext
-from apytypes._apytypes import QuantizationMode
-from apytypes._apytypes import get_float_quantization_mode
-from apytypes._apytypes import get_float_quantization_seed
-from apytypes._apytypes import set_float_quantization_mode
-from apytypes._apytypes import set_float_quantization_seed
-from apytypes._version import version as __version__
+import __future__
+
+from ._apytypes import (
+    APyFixed as APyFixed,
+    APyFixedAccumulatorContext as APyFixedAccumulatorContext,
+    APyFixedArray as APyFixedArray,
+    APyFixedCastContext as APyFixedCastContext,
+    APyFloat as APyFloat,
+    APyFloatAccumulatorContext as APyFloatAccumulatorContext,
+    APyFloatArray as APyFloatArray,
+    APyFloatQuantizationContext as APyFloatQuantizationContext,
+    OverflowMode as OverflowMode,
+    QuantizationMode as QuantizationMode,
+    get_float_quantization_mode as get_float_quantization_mode,
+    get_float_quantization_seed as get_float_quantization_seed,
+    set_float_quantization_mode as set_float_quantization_mode,
+    set_float_quantization_seed as set_float_quantization_seed,
+)
 
 __all__: list = [
     "APyFixed",
@@ -31,4 +33,7 @@ __all__: list = [
     "set_float_quantization_mode",
     "get_float_quantization_seed",
     "set_float_quantization_seed",
+    "_get_simd_version_str",
 ]
+
+annotations: __future__._Feature = ...
diff --git a/lib/apytypes/_apytypes.pyi b/lib/apytypes/_apytypes.pyi
index eb77cd05e..7aac655fb 100644
--- a/lib/apytypes/_apytypes.pyi
+++ b/lib/apytypes/_apytypes.pyi
@@ -1,59 +1,166 @@
-from __future__ import annotations
-import apytypes
-import numpy
-import typing
-
-__all__ = [
-    "APyFixed",
-    "APyFixedArray",
-    "APyFixedArrayIterator",
-    "APyFixedAccumulatorContext",
-    "APyFixedCastContext",
-    "APyFloat",
-    "APyFloatArray",
-    "APyFloatArrayIterator",
-    "APyFloatAccumulatorContext",
-    "ContextManager",
-    "OverflowMode",
-    "APyFloatQuantizationContext",
-    "QuantizationMode",
-    "get_float_quantization_mode",
-    "get_float_quantization_seed",
-    "set_float_quantization_mode",
-    "set_float_quantization_seed",
-]
+from collections.abc import Sequence
+import enum
+from typing import Annotated, overload
 
-class APyFixed:
-    """
+from numpy.typing import ArrayLike
 
-    Class for configurable fixed-point formats.
+class APyFixed:
+    r"""
+    Class for configurable scalar fixed-point formats.
+
+    :class:`APyFixed` is an arbitrary precision two's complement fixed-point scalar type. In
+    many ways it behaves like the built-in Python types :class:`int` and :class:`float`, in
+    that it can be used within ordinary arithmetic expressions. Every fixed-point instance
+    has an associated word length, determined by its :attr:`bits`, :attr:`int_bits`, and
+    :attr:`frac_bits` bit specifiers. These specifiers determine the location of the binary
+    fix-point and the total word length. Only two of three bit specifers need to be set to
+    uniquely determine the complete fixed-point format.
+
+    In general, the fixed-point representation is described by:
+
+    .. math::
+        \underbrace{
+            \underbrace{
+                x_{n-1} \; x_{n-2} \; \ldots \; x_{k+1} \; x_{k}
+            }_{\mathrm{int\_bits}}
+            \; . \;
+            \underbrace{
+                x_{k-1} \; x_{k-2} \; \ldots \; x_{1} \; x_{0}
+            }_{\mathrm{frac\_bits}}
+        }_{\mathrm{bits}}
+
+    The following is an example of a fixed-point number with :code:`bits=8`,
+    :code:`int_bits=5`, and :code:`frac_bits=3`, that has a stored value of -6.625:
+
+    .. math::
+        \begin{align*}
+            1 \, 1 \, 0 \, 0 \, 1 \,.\, 0 \, 1 \, 1_{2} = \frac{-53_{10}}{2^3} = -6.625_{10}
+        \end{align*}
+
+    APyFixed uses static word-length inference to determine word lengths of results to
+    arithmetic operations. This ensures that overflow or quantization **never** occurs
+    unless explicitly instructed to by a user through the :func:`cast` method. The following
+    table shows word lengths of elementary arithmetic operations.
+
+    .. list-table:: Word-length of fixed-point arithmetic operations
+       :widths: 12 44 44
+       :header-rows: 1
+
+       * - Operation
+         - Result :attr:`int_bits`
+         - Result :attr:`frac_bits`
+       * - :code:`a + b`
+         - :code:`max(a.int_bits, b.int_bits) + 1`
+         - :code:`max(a.frac_bits, b.frac_bits)`
+       * - :code:`a - b`
+         - :code:`max(a.int_bits, b.int_bits) + 1`
+         - :code:`max(a.frac_bits, b.frac_bits)`
+       * - :code:`a * b`
+         - :code:`a.int_bits + b.int_bits`
+         - :code:`a.frac_bits + b.frac_bits`
+       * - :code:`a / b`
+         - :code:`a.int_bits + b.frac_bits`
+         - :code:`a.frac_bits + b.int_bits`
+       * - :code:`-a`
+         - :code:`a.int_bits + 1`
+         - :code:`a.frac_bits`
     """
 
-    __hash__: typing.ClassVar[None] = None
-    @staticmethod
-    def from_float(
-        value: float,
+    def __init__(
+        self,
+        bit_pattern: int,
         int_bits: int | None = None,
         frac_bits: int | None = None,
         bits: int | None = None,
-    ) -> APyFixed:
+    ) -> None: ...
+    @overload
+    def __eq__(self, arg: APyFixed, /) -> bool: ...
+    @overload
+    def __eq__(self, arg: int, /) -> bool: ...
+    @overload
+    def __eq__(self, arg: float, /) -> bool: ...
+    @overload
+    def __ne__(self, arg: APyFixed, /) -> bool: ...
+    @overload
+    def __ne__(self, arg: int, /) -> bool: ...
+    @overload
+    def __ne__(self, arg: float, /) -> bool: ...
+    @overload
+    def __lt__(self, arg: APyFixed, /) -> bool: ...
+    @overload
+    def __lt__(self, arg: int, /) -> bool: ...
+    @overload
+    def __lt__(self, arg: float, /) -> bool: ...
+    @overload
+    def __le__(self, arg: APyFixed, /) -> bool: ...
+    @overload
+    def __le__(self, arg: int, /) -> bool: ...
+    @overload
+    def __le__(self, arg: float, /) -> bool: ...
+    @overload
+    def __gt__(self, arg: APyFixed, /) -> bool: ...
+    @overload
+    def __gt__(self, arg: int, /) -> bool: ...
+    @overload
+    def __gt__(self, arg: float, /) -> bool: ...
+    @overload
+    def __ge__(self, arg: APyFixed, /) -> bool: ...
+    @overload
+    def __ge__(self, arg: int, /) -> bool: ...
+    @overload
+    def __ge__(self, arg: float, /) -> bool: ...
+    @overload
+    def __add__(self, arg: APyFixed, /) -> APyFixed: ...
+    @overload
+    def __add__(self, arg: int, /) -> APyFixed: ...
+    @overload
+    def __add__(self, arg: float, /) -> APyFixed: ...
+    @overload
+    def __sub__(self, arg: APyFixed, /) -> APyFixed: ...
+    @overload
+    def __sub__(self, arg: int, /) -> APyFixed: ...
+    @overload
+    def __sub__(self, arg: float, /) -> APyFixed: ...
+    @overload
+    def __mul__(self, arg: APyFixed, /) -> APyFixed: ...
+    @overload
+    def __mul__(self, arg: int, /) -> APyFixed: ...
+    @overload
+    def __mul__(self, arg: float, /) -> APyFixed: ...
+    @overload
+    def __truediv__(self, arg: APyFixed, /) -> APyFixed: ...
+    @overload
+    def __truediv__(self, arg: int, /) -> APyFixed: ...
+    @overload
+    def __truediv__(self, arg: float, /) -> APyFixed: ...
+    def __neg__(self) -> APyFixed: ...
+    def __ilshift__(self, arg: int, /) -> APyFixed: ...
+    def __irshift__(self, arg: int, /) -> APyFixed: ...
+    @overload
+    def __radd__(self, arg: int, /) -> APyFixed: ...
+    @overload
+    def __radd__(self, arg: float, /) -> APyFixed: ...
+    @overload
+    def __radd__(self, arg: float, /) -> APyFixed: ...
+    @overload
+    def __rsub__(self, arg: int, /) -> APyFixed: ...
+    @overload
+    def __rsub__(self, arg: float, /) -> APyFixed: ...
+    @overload
+    def __rmul__(self, arg: int, /) -> APyFixed: ...
+    @overload
+    def __rmul__(self, arg: float, /) -> APyFixed: ...
+    @overload
+    def __rtruediv__(self, arg: int, /) -> APyFixed: ...
+    @overload
+    def __rtruediv__(self, arg: float, /) -> APyFixed: ...
+    def to_bits(self) -> int:
         """
-        Create an :class:`APyFixed` object from :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 three bit-specifiers
-        (`bits`, `int_bits`, `frac_bits`) must be set.
+        Retrieve underlying bit-pattern in an :class:`int`.
 
-        Parameters
-        ----------
-        value : float
-            Floating point value to initialize from
-        int_bits : int, optional
-            Number of integer bits in the created fixed-point object
-        frac_bits : int, optional
-            Number of fractional bits in the created fixed-point object
-        bits : int, optional
-            Total number of bits in the created fixed-point object
+        Returns
+        -------
+        :class:`int`
 
         Examples
         --------
@@ -61,181 +168,41 @@ class APyFixed:
 
             from apytypes import APyFixed
 
-            # Fixed-point `fx_a`, initialized from the floating-point value 1.234,
-            # rounded to 1.25 as it is the closest representable number
-            fx_a = APyFixed.from_float(1.234, int_bits=2, frac_bits=2)
-        """
+            # Create fixed-point number `fx_a` of value -5.75
+            fx_a = APyFixed.from_float(-5.75, int_bits=4, frac_bits=4)
 
-    @staticmethod
-    def from_str(
-        string_value: str,
-        int_bits: int | None = None,
-        frac_bits: int | None = None,
-        base: int = 10,
-        bits: int | None = None,
-    ) -> APyFixed:
+            # Returns: 164 == 0xA4 == 0b10100100
+            fx_a.to_bits()
         """
-        Create an :class:`APyFixed` object from :class:`str`.
-
-        Exactly two of three bit-specifiers (`bits`, `int_bits`, `frac_bits`) must be
-        set.
-
-        Parameters
-        ----------
-        string_value : str
-            String to initialize the value from
-        int_bits : int, optional
-            Number of integer bits in the created fixed-point object
-        frac_bits : int, optional
-            Number of fractional bits in the created fixed-point object
-        base : int, default: 10
-            Numeric base used in `string_value`
-        bits : int, optional
-            Total number of bits in the created fixed-point object
-
-        Examples
-        --------
-        .. code-block:: python
-
-            from apytypes import APyFixed
 
-            # Larger fixed-point value initialization from a string (base-10)
-            fx_a = APyFixed.from_str(
-                "-1376018206341311063223476816643087998331620501540496640."
-                "021222579872958058370179355618716816066859017361262100333952697594702"
-                "314679773970519809467311447652539955943903993200932791396783892142688"
-                "708904952458654442554723081083186210082207584128592922850820472478833"
-                "257136662269306798708182072507551281664490003441493733349403017982015"
-                "56238154807942919433116912841796875",
-                bits=511,
-                int_bits=199,
-                base=10
-            )
+    @property
+    def bits(self) -> int:
         """
+        Total number of bits.
 
-    def __abs__(self) -> APyFixed: ...
-    @typing.overload
-    def __add__(self, arg0: APyFixed) -> APyFixed: ...
-    @typing.overload
-    def __add__(self, arg0: int) -> APyFixed: ...
-    @typing.overload
-    def __eq__(self, arg0: APyFixed) -> bool: ...
-    @typing.overload
-    def __eq__(self, arg0: float) -> bool: ...
-    @typing.overload
-    def __eq__(self, arg0: int) -> bool: ...
-    def __float__(self) -> float: ...
-    @typing.overload
-    def __ge__(self, arg0: APyFixed) -> bool: ...
-    @typing.overload
-    def __ge__(self, arg0: float) -> bool: ...
-    @typing.overload
-    def __ge__(self, arg0: int) -> bool: ...
-    @typing.overload
-    def __gt__(self, arg0: APyFixed) -> bool: ...
-    @typing.overload
-    def __gt__(self, arg0: float) -> bool: ...
-    @typing.overload
-    def __gt__(self, arg0: int) -> bool: ...
-    def __init__(
-        self,
-        bit_pattern: int,
-        int_bits: int | None = None,
-        frac_bits: int | None = None,
-        bits: int | None = None,
-    ) -> None: ...
-    @typing.overload
-    def __le__(self, arg0: APyFixed) -> bool: ...
-    @typing.overload
-    def __le__(self, arg0: float) -> bool: ...
-    @typing.overload
-    def __le__(self, arg0: int) -> bool: ...
-    def __lshift__(self, shift_amnt: int) -> APyFixed: ...
-    @typing.overload
-    def __lt__(self, arg0: APyFixed) -> bool: ...
-    @typing.overload
-    def __lt__(self, arg0: float) -> bool: ...
-    @typing.overload
-    def __lt__(self, arg0: int) -> bool: ...
-    @typing.overload
-    def __mul__(self, arg0: APyFixed) -> APyFixed: ...
-    @typing.overload
-    def __mul__(self, arg0: int) -> APyFixed: ...
-    @typing.overload
-    def __ne__(self, arg0: APyFixed) -> bool: ...
-    @typing.overload
-    def __ne__(self, arg0: float) -> bool: ...
-    @typing.overload
-    def __ne__(self, arg0: int) -> bool: ...
-    @typing.overload
-    def __neg__(self) -> APyFixed: ...
-    @typing.overload
-    def __neg__(self) -> APyFixed: ...
-    def __radd__(self, arg0: int) -> APyFixed: ...
-    def __repr__(self) -> str: ...
-    def __rmul__(self, arg0: int) -> APyFixed: ...
-    def __rshift__(self, shift_amnt: int) -> APyFixed: ...
-    def __rsub__(self, arg0: int) -> APyFixed: ...
-    def __str__(self, base: int = 10) -> str: ...
-    @typing.overload
-    def __sub__(self, arg0: APyFixed) -> APyFixed: ...
-    @typing.overload
-    def __sub__(self, arg0: int) -> APyFixed: ...
-    def __truediv__(self, arg0: APyFixed) -> APyFixed: ...
-    def _repr_latex_(self) -> str: ...
-    def cast(
-        self,
-        int_bits: int | None = None,
-        frac_bits: int | None = None,
-        quantization: QuantizationMode | None = None,
-        overflow: OverflowMode | None = None,
-        bits: int | None = None,
-    ) -> APyFixed:
+        Returns
+        -------
+        :class:`int`
         """
-        Create a new resized fixed-point number based on the bit pattern in this
-        fixed-point number.
 
-        This is the primary method for performing quantization, truncation,
-        overflowing, and saturation when dealing with APyTypes fixed-point numbers.
-
-        Exactly two of three bit-specifiers (`bits`, `int_bits`, `frac_bits`) must be
-        set.
-
-        Parameters
-        ----------
-        int_bits : int, optional
-            Number of integer bits in the created fixed-point object
-        frac_bits : int, optional
-            Number of fractional bits in the created fixed-point object
-        quantization : :class:`QuantizationMode`, optional
-            Quantization mode to use in this cast
-        overflow : :class:`OverflowMode`, optional
-            Overflowing mode to use in this cast
-        bits : int, optional
-            Total number of bits in the created fixed-point object
+    @property
+    def frac_bits(self) -> int:
+        """
+        Number of fractional bits.
 
         Returns
         -------
-        :class:`APyFixed`
-
-        Examples
-        --------
-        .. code-block:: python
-
-            from apytypes import APyFixed
-            from apytypes import QuantizationMode
-            from apytypes import OverflowMode
-
-            fx = APyFixed.from_float(2.125, int_bits=3, frac_bits=3)
-
-            # Truncation (fx_a == 2.0)
-            fx_a = fx.cast(int_bits=3, frac_bits=2, quantization=QuantizationMode.TRN)
+        :class:`int`
+        """
 
-            # Quantization (fx_b == 2.25)
-            fx_b = fx.cast(int_bits=3, frac_bits=2, quantization=QuantizationMode.RND)
+    @property
+    def int_bits(self) -> int:
+        """
+        Number of integer bits.
 
-            # Two's complement overflowing (fx_c == -1.875)
-            fx_c = fx.cast(int_bits=2, frac_bits=3, overflow=OverflowMode.WRAP)
+        Returns
+        -------
+        :class:`int`
         """
 
     def is_identical(self, other: APyFixed) -> bool:
@@ -243,9 +210,9 @@ class APyFixed:
         Test if two fixed-point objects are exactly identical.
 
         Two `APyFixed` objects are considered exactly identical if, and only if,
-        they store the same fixed-point value, and have the exact same
+        they represent the same fixed-point value, and have the exact same
         bit-specification (`bits`, `int_bits`, and `frac_bits`). This is a more
-        restrictive test than ``==``,  that only tests equality of the stored
+        restrictive test than ``==``,  that only tests equality of the numerical
         fixed-point value.
 
         Parameters
@@ -273,73 +240,68 @@ class APyFixed:
             assert not(fx_a.is_identical(fx_b))
         """
 
-    def leading_fractional_zeros(self) -> int:
-        """
-        Retrieve the number of leading zeros after the binary fixed-point.
-
-        Returns
-        -------
-        :class:`int`
-        """
-
-    def leading_ones(self) -> int:
-        """
-        Retrieve the number of leading ones.
-
-        Returns
-        -------
-        :class:`int`
-        """
-
-    def leading_signs(self) -> int:
-        """
-        Retrieve the number of leading signs.
+    @property
+    def is_zero(self) -> bool:
+        """True if the value equals zero, false otherwise."""
 
-        Returns
-        -------
-        :class:`int`
+    def cast(
+        self,
+        int_bits: int | None = None,
+        frac_bits: int | None = None,
+        quantization: QuantizationMode | None = None,
+        overflow: OverflowMode | None = None,
+        bits: int | None = None,
+    ) -> APyFixed:
         """
+        Change format of the fixed-point number.
 
-    def leading_zeros(self) -> int:
-        """
-        Retrieve the number of leading zeros.
+        This is the primary method for performing quantization and
+        overflowing/saturation when dealing with APyTypes fixed-point numbers.
 
-        Returns
-        -------
-        :class:`int`
-        """
+        Exactly two of three bit-specifiers (`bits`, `int_bits`, `frac_bits`) needs
+        to be set.
 
-    def to_bits(self) -> int:
-        """
-        Retrieve underlying bit-pattern in an :class:`int`.
+        Parameters
+        ----------
+        int_bits : int, optional
+            Number of integer bits in the result.
+        frac_bits : int, optional
+            Number of fractional bits in the result.
+        quantization : :class:`QuantizationMode`, optional
+            Quantization mode to use in this cast.
+        overflow : :class:`OverflowMode`, optional
+            Overflowing mode to use in this cast.
+        bits : int, optional
+            Total number of bits in the result.
 
         Returns
         -------
-        :class:`int`
+        :class:`APyFixed`
 
         Examples
         --------
         .. code-block:: python
 
             from apytypes import APyFixed
+            from apytypes import QuantizationMode
+            from apytypes import OverflowMode
 
-            # Create fixed-point number `fx_a` of value -5.75
-            fx_a = APyFixed.from_float(-5.75, int_bits=4, frac_bits=4)
+            fx = APyFixed.from_float(2.125, int_bits=3, frac_bits=3)
 
-            # Returns: 164 == 0xA4 == 0b10100100
-            fx_a.to_bits()
+            # Truncation (fx_a == 2.0)
+            fx_a = fx.cast(int_bits=3, frac_bits=2, quantization=QuantizationMode.TRN)
+
+            # Quantization (fx_b == 2.25)
+            fx_b = fx.cast(int_bits=3, frac_bits=2, quantization=QuantizationMode.RND)
+
+            # Two's complement overflowing (fx_c == -1.875)
+            fx_c = fx.cast(int_bits=2, frac_bits=3, overflow=OverflowMode.WRAP)
         """
 
     @property
-    def _is_negative(self) -> bool: ...
-    @property
-    def _is_positive(self) -> bool: ...
-    @property
-    def _vector_size(self) -> int: ...
-    @property
-    def bits(self) -> int:
+    def leading_ones(self) -> int:
         """
-        Total number of bits.
+        Number of leading ones.
 
         Returns
         -------
@@ -347,9 +309,9 @@ class APyFixed:
         """
 
     @property
-    def frac_bits(self) -> int:
+    def leading_zeros(self) -> int:
         """
-        Number of fractional bits.
+        Number of leading zeros.
 
         Returns
         -------
@@ -357,9 +319,9 @@ class APyFixed:
         """
 
     @property
-    def int_bits(self) -> int:
+    def leading_fractional_zeros(self) -> int:
         """
-        Number of integer bits.
+        Number of leading zeros after the binary fixed-point.
 
         Returns
         -------
@@ -367,50 +329,384 @@ class APyFixed:
         """
 
     @property
-    def is_zero(self) -> bool:
+    def leading_signs(self) -> int:
         """
-        True if the stored value equals zero, false otherwise.
+        Number of leading signs.
+
+        Returns
+        -------
+        :class:`int`
         """
 
-class APyFixedArray:
+    def __abs__(self) -> APyFixed: ...
+    def __float__(self) -> float: ...
+    def __repr__(self) -> str: ...
+    def __str__(self, base: int = 10) -> str: ...
+    def __lshift__(self, shift_amnt: int) -> APyFixed: ...
+    def __rshift__(self, shift_amnt: int) -> APyFixed: ...
     @staticmethod
     def from_float(
-        float_sequence: typing.Sequence,
+        value: object,
         int_bits: int | None = None,
         frac_bits: int | None = None,
         bits: int | None = None,
-    ) -> APyFixedArray:
+    ) -> APyFixed:
         """
-        Create an :class:`APyFixedArray` object from a sequence of :class:`float`.
+        Create an :class:`APyFixed` object from an :class:`int` or :class:`float`.
 
-        The initialized fixed-point values are the ones closest to the input
-        floating-point values, rounded away from zero on ties. Exactly two of the three
-        bit-specifiers (`bits`, `int_bits`, `frac_bits`) must be set.
+        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
+        three bit-specifiers (`bits`, `int_bits`, `frac_bits`) must be set.
 
         Parameters
         ----------
-        float_sequence : sequence of float
-            Floating point values to initialize from. The tensor shape will be taken
-            from the sequence shape.
+        value : int, float
+            Floating point value to initialize from
         int_bits : int, optional
-            Number of integer bits in the created fixed-point tensor
+            Number of integer bits in the created fixed-point object
         frac_bits : int, optional
-            Number of fractional bits in the created fixed-point tensor
+            Number of fractional bits in the created fixed-point object
         bits : int, optional
-            Total number of bits in the created fixed-point tensor
-
-        Returns
-        -------
-        :class:`APyFixedArray`
+            Total number of bits in the created fixed-point object
 
         Examples
         --------
-
         .. code-block:: python
 
-            from apytypes import APyFixedArray
+            from apytypes import APyFixed
 
-            # Array `a`, initialized from floating-point values.
+            # Fixed-point `fx_a`, initialized from the floating-point value 1.234,
+            # rounded to 1.25 as it is the closest representable number
+            fx_a = APyFixed.from_float(1.234, int_bits=2, frac_bits=2)
+        """
+
+    @staticmethod
+    def from_str(
+        string_value: str,
+        int_bits: int | None = None,
+        frac_bits: int | None = None,
+        base: int = 10,
+        bits: int | None = None,
+    ) -> APyFixed:
+        """
+        Create an :class:`APyFixed` object from :class:`str`.
+
+        Exactly two of three bit-specifiers (`bits`, `int_bits`, `frac_bits`) must
+        be set.
+
+        Parameters
+        ----------
+        string_value : str
+            String to initialize the value from
+        int_bits : int, optional
+            Number of integer bits in the created fixed-point object
+        frac_bits : int, optional
+            Number of fractional bits in the created fixed-point object
+        base : int, default: 10
+            Numeric base used in `string_value`
+        bits : int, optional
+            Total number of bits in the created fixed-point object
+
+        Examples
+        --------
+        .. code-block:: python
+
+            from apytypes import APyFixed
+
+            # Larger fixed-point value initialization from a string (base-10)
+            fx_a = APyFixed.from_str(
+                "-1376018206341311063223476816643087998331620501540496640."
+                "021222579872958058370179355618716816066859017361262100333952697594702"
+                "314679773970519809467311447652539955943903993200932791396783892142688"
+                "708904952458654442554723081083186210082207584128592922850820472478833"
+                "257136662269306798708182072507551281664490003441493733349403017982015"
+                "56238154807942919433116912841796875",
+                bits=511,
+                int_bits=199,
+                base=10
+            )
+        """
+
+class APyFixedAccumulatorContext(ContextManager):
+    def __init__(
+        self,
+        int_bits: int | None = None,
+        frac_bits: int | None = None,
+        quantization: QuantizationMode | None = None,
+        overflow: OverflowMode | None = None,
+        bits: int | None = None,
+    ) -> None: ...
+    def __enter__(self) -> None: ...
+    def __exit__(
+        self,
+        exc_type: object | None = None,
+        exc_value: object | None = None,
+        traceback: object | None = None,
+    ) -> None: ...
+
+class APyFixedArray:
+    def __init__(
+        self,
+        bit_pattern_sequence: Sequence,
+        int_bits: int | None = None,
+        frac_bits: int | None = None,
+        bits: int | None = None,
+    ) -> None: ...
+    @overload
+    def __add__(self, arg: APyFixedArray, /) -> APyFixedArray: ...
+    @overload
+    def __add__(self, arg: int, /) -> APyFixedArray: ...
+    @overload
+    def __add__(self, arg: float, /) -> APyFixedArray: ...
+    @overload
+    def __add__(self, arg: APyFixed, /) -> APyFixedArray: ...
+    @overload
+    def __radd__(self, arg: int, /) -> APyFixedArray: ...
+    @overload
+    def __radd__(self, arg: float, /) -> APyFixedArray: ...
+    @overload
+    def __radd__(self, arg: APyFixed, /) -> APyFixedArray: ...
+    @overload
+    def __sub__(self, arg: APyFixedArray, /) -> APyFixedArray: ...
+    @overload
+    def __sub__(self, arg: int, /) -> APyFixedArray: ...
+    @overload
+    def __sub__(self, arg: float, /) -> APyFixedArray: ...
+    @overload
+    def __sub__(self, arg: APyFixed, /) -> APyFixedArray: ...
+    @overload
+    def __rsub__(self, arg: int, /) -> APyFixedArray: ...
+    @overload
+    def __rsub__(self, arg: float, /) -> APyFixedArray: ...
+    @overload
+    def __rsub__(self, arg: APyFixed, /) -> APyFixedArray: ...
+    @overload
+    def __mul__(self, arg: APyFixedArray, /) -> APyFixedArray: ...
+    @overload
+    def __mul__(self, arg: APyFixed, /) -> APyFixedArray: ...
+    @overload
+    def __mul__(self, arg: int, /) -> APyFixedArray: ...
+    @overload
+    def __mul__(self, arg: float, /) -> APyFixedArray: ...
+    @overload
+    def __rmul__(self, arg: APyFixed, /) -> APyFixedArray: ...
+    @overload
+    def __rmul__(self, arg: int, /) -> APyFixedArray: ...
+    @overload
+    def __rmul__(self, arg: float, /) -> APyFixedArray: ...
+    @overload
+    def __truediv__(self, arg: APyFixedArray, /) -> APyFixedArray: ...
+    @overload
+    def __truediv__(self, arg: APyFixed, /) -> APyFixedArray: ...
+    @overload
+    def __truediv__(self, arg: int, /) -> APyFixedArray: ...
+    @overload
+    def __truediv__(self, arg: float, /) -> APyFixedArray: ...
+    @overload
+    def __rtruediv__(self, arg: APyFixed, /) -> APyFixedArray: ...
+    @overload
+    def __rtruediv__(self, arg: int, /) -> APyFixedArray: ...
+    @overload
+    def __rtruediv__(self, arg: float, /) -> APyFixedArray: ...
+    def __neg__(self) -> APyFixedArray: ...
+    def __ilshift__(self, arg: int, /) -> APyFixedArray: ...
+    def __irshift__(self, arg: int, /) -> APyFixedArray: ...
+    @property
+    def bits(self) -> int:
+        """
+        Total number of bits.
+
+        Returns
+        -------
+        :class:`int`
+        """
+
+    @property
+    def int_bits(self) -> int:
+        """
+        Number of integer bits.
+
+        Returns
+        -------
+        :class:`int`
+        """
+
+    @property
+    def frac_bits(self) -> int:
+        """
+        Number of fractional bits.
+
+        Returns
+        -------
+        :class:`int`
+        """
+
+    @property
+    def shape(self) -> tuple:
+        """
+        The shape of the array.
+
+        Returns
+        -------
+        :class:`tuple` of :class:`int`
+        """
+
+    @property
+    def ndim(self) -> int:
+        """
+        Number of dimensions in the array.
+
+        Returns
+        -------
+        :class:`int`
+        """
+
+    @property
+    def T(self) -> APyFixedArray:
+        """
+        The transposition of the array.
+
+        Equivalent to calling :func:`APyFixedArray.transpose`.
+
+        Returns
+        -------
+        :class:`APyFixedArray`
+        """
+
+    def to_numpy(self) -> Annotated[ArrayLike, dict(dtype="float64")]:
+        """
+        Return array as a :class:`numpy.ndarray` of :class:`numpy.float64`.
+
+        The returned array has the same `shape` and values as `self`. This
+        method rounds away from infinity on ties.
+
+        Returns
+        -------
+        :class:`numpy.ndarray`
+        """
+
+    def is_identical(self, other: APyFixedArray) -> bool:
+        """
+        Test if two :class:`APyFixedArray` objects are identical.
+
+        Two :class:`APyFixedArray` objects are considered identical if, and only if:
+          * They represent exactly the same tensor shape
+          * They store the exact same fixed-point values in all tensor elements
+          * They have the exact same bit specification (`bits`, `int_bits`, and
+            `frac_bits` are all equal)
+
+        Returns
+        -------
+        :class:`bool`
+        """
+
+    def transpose(self) -> APyFixedArray:
+        """
+        Return the transposition of the array.
+
+        If the dimension of `self` is one, this method returns the a copy of `self`.
+        If the dimension of `self` is two, this method returns the matrix
+        transposition of `self`.
+
+        Higher order transposition has not been implemented and will raise a
+        `NotImplementedException`.
+
+        Returns
+        -------
+        :class:`APyFixedArray`
+        """
+
+    def cast(
+        self,
+        int_bits: int | None = None,
+        frac_bits: int | None = None,
+        quantization: QuantizationMode | None = None,
+        overflow: OverflowMode | None = None,
+        bits: int | None = None,
+    ) -> APyFixedArray:
+        """
+        Change format of the fixed-point array.
+
+        This is the primary method for performing quantization and overflowing/saturation
+        when dealing with APyTypes fixed-point arrays.
+
+        Exactly two of three bit-specifiers (`bits`, `int_bits`, `frac_bits`) must
+        be set.
+
+        Parameters
+        ----------
+        int_bits : int, optional
+            Number of integer bits in the result.
+        frac_bits : int, optional
+            Number of fractional bits in the result.
+        quantization : :class:`QuantizationMode`, optional
+            Quantization mode to use in this cast.
+        overflow : :class:`OverflowMode`, optional
+            Overflowing mode to use in this cast.
+        bits : int, optional
+            Total number of bits in the result.
+
+        Returns
+        -------
+        :class:`APyFixedArray`
+        """
+
+    def broadcast_to(self, shape: tuple | int) -> APyFixedArray:
+        """
+        Broadcast array to new shape.
+
+        .. versionadded:: 0.2
+
+        Parameters
+        ----------
+        shape : tuple or int
+            The shape to broadcast to. A single integer ``i`` is interpreted as ``(i,)``.
+
+        Returns
+        -------
+        :class:`APyFixedArray`
+        """
+
+    @staticmethod
+    def from_float(
+        float_sequence: Sequence,
+        int_bits: int | None = None,
+        frac_bits: int | None = None,
+        bits: int | None = None,
+    ) -> APyFixedArray:
+        """
+        Create an :class:`APyFixedArray` object from a sequence of :class:`float`.
+
+        The initialized fixed-point values are the one closest to the
+        input floating-point values, rounded away from zero on ties. Exactly two of
+        the three bit-specifiers (`bits`, `int_bits`, `frac_bits`) must be set.
+
+        Using NumPy arrays as input is in general faster than e.g. lists.
+
+        Parameters
+        ----------
+        float_sequence : sequence of float
+            Floating point values to initialize from. The tensor shape will be taken
+            from the sequence shape.
+        int_bits : int, optional
+            Number of integer bits in the created fixed-point tensor.
+        frac_bits : int, optional
+            Number of fractional bits in the created fixed-point tensor.
+        bits : int, optional
+            Total number of bits in the created fixed-point tensor.
+
+        Returns
+        -------
+        :class:`APyFixedArray`
+
+        Examples
+        --------
+
+        .. code-block:: python
+
+            from apytypes import APyFixedArray
+
+            # Array `a`, initialized from floating-point values.
             a = APyFixedArray.from_float([1.0, 1.25, 1.49], int_bits=2, frac_bits=2)
 
             # Array `b` (2 x 3 matrix), initialized from floating-point values.
@@ -426,7 +722,7 @@ class APyFixedArray:
 
     @staticmethod
     def from_array(
-        float_array,
+        ndarray: Annotated[ArrayLike, dict(order="C")],
         int_bits: int | None = None,
         frac_bits: int | None = None,
         bits: int | None = None,
@@ -434,23 +730,22 @@ class APyFixedArray:
         """
         Create an :class:`APyFixedArray` object from an ndarray.
 
-        The initialized fixed-point values are the ones closest to the input
-        floating-point values, rounded away from zero on ties. Exactly two of the three
-        bit-specifiers (`bits`, `int_bits`, `frac_bits`) must be set.
+        The initialized fixed-point values are the one closest to the
+        input floating-point value, rounded away from zero on ties. Exactly two of
+        the three bit-specifiers (`bits`, `int_bits`, `frac_bits`) must be set.
 
-        Using ndarrays as input is generally faster than e.g. lists.
+        Using NumPy arrays as input is in general faster than e.g. lists.
 
         Parameters
         ----------
-        float_sequence : sequence of float
-            Floating point values to initialize from. The tensor shape will be taken
-            from the sequence shape.
+        ndarray : ndarray
+            Values to initialize from. The tensor shape will be taken from the ndarray shape.
         int_bits : int, optional
-            Number of integer bits in the created fixed-point tensor
+            Number of integer bits in the created fixed-point tensor.
         frac_bits : int, optional
-            Number of fractional bits in the created fixed-point tensor
+            Number of fractional bits in the created fixed-point tensor.
         bits : int, optional
-            Total number of bits in the created fixed-point tensor
+            Total number of bits in the created fixed-point tensor.
 
         Returns
         -------
@@ -475,165 +770,480 @@ class APyFixedArray:
             )
         """
 
-    def __abs__(self) -> APyFixedArray: ...
-    @typing.overload
-    def __add__(self, arg0: APyFixedArray) -> APyFixedArray: ...
-    @typing.overload
-    def __add__(self, arg0: int) -> APyFixedArray: ...
-    @typing.overload
-    def __add__(self, arg0: float) -> APyFixedArray: ...
-    @typing.overload
-    def __add__(self, arg0: APyFixed) -> APyFixedArray: ...
-    def __array__(self) -> numpy.ndarray[numpy.float64]: ...
-    def __getitem__(self, idx: int) -> typing.Union[APyFixedArray, APyFixed]: ...
-    def __init__(
-        self,
-        bit_pattern_sequence: typing.Sequence,
-        int_bits: int | None = None,
-        frac_bits: int | None = None,
-        bits: int | None = None,
-    ) -> None: ...
-    def __iter__(self) -> APyFixedArrayIterator: ...
-    def __len__(self) -> int: ...
     def __lshift__(self, shift_amnt: int) -> APyFixedArray: ...
     def __matmul__(self, rhs: APyFixedArray) -> APyFixedArray: ...
-    @typing.overload
-    def __mul__(self, arg0: APyFixedArray) -> APyFixedArray: ...
-    @typing.overload
-    def __mul__(self, arg0: APyFixed) -> APyFixedArray: ...
-    @typing.overload
-    def __mul__(self, arg0: int) -> APyFixedArray: ...
-    @typing.overload
-    def __mul__(self, arg0: float) -> APyFixedArray: ...
-    def __neg__(self) -> APyFixedArray: ...
-    @typing.overload
-    def __radd__(self, arg0: int) -> APyFixedArray: ...
-    @typing.overload
-    def __radd__(self, arg0: float) -> APyFixedArray: ...
-    @typing.overload
-    def __radd__(self, arg0: APyFixed) -> APyFixedArray: ...
     def __repr__(self) -> str: ...
-    @typing.overload
-    def __rmul__(self, arg0: APyFixed) -> APyFixedArray: ...
-    @typing.overload
-    def __rmul__(self, arg0: int) -> APyFixedArray: ...
-    @typing.overload
-    def __rmul__(self, arg0: float) -> APyFixedArray: ...
     def __rshift__(self, shift_amnt: int) -> APyFixedArray: ...
-    @typing.overload
-    def __sub__(self, arg0: APyFixedArray) -> APyFixedArray: ...
-    @typing.overload
-    def __sub__(self, arg0: int) -> APyFixedArray: ...
-    @typing.overload
-    def __sub__(self, arg0: float) -> APyFixedArray: ...
-    @typing.overload
-    def __sub__(self, arg0: APyFixed) -> APyFixedArray: ...
-    @typing.overload
-    def __truediv__(self, arg0: APyFixedArray) -> APyFixedArray: ...
-    # @typing.overload
-    # def __truediv__(self, arg0: int) -> APyFixedArray: ...
-    # @typing.overload
-    # def __truediv__(self, arg0: float) -> APyFixedArray: ...
-    @typing.overload
-    def __truediv__(self, arg0: APyFixed) -> APyFixedArray: ...
-    @typing.overload
-    def __rtruediv__(self, arg0: APyFixed) -> APyFixedArray: ...
-    def cast(
+    def __abs__(self) -> APyFixedArray: ...
+    def __getitem__(self, idx: int) -> APyFixedArray | APyFixed: ...
+    def __len__(self) -> int: ...
+    def __iter__(self) -> APyFixedArrayIterator: ...
+    def __array__(self) -> Annotated[ArrayLike, dict(dtype="float64")]: ...
+
+class APyFixedArrayIterator:
+    def __iter__(self) -> APyFixedArrayIterator: ...
+    def __next__(self) -> APyFixedArray | APyFixed: ...
+
+class APyFixedCastContext(ContextManager):
+    def __init__(
         self,
-        int_bits: int | None = None,
-        frac_bits: int | None = None,
         quantization: QuantizationMode | None = None,
         overflow: OverflowMode | None = None,
-        bits: int | None = None,
-    ) -> APyFixedArray:
+    ) -> None: ...
+    def __enter__(self) -> None: ...
+    def __exit__(
+        self,
+        exc_type: object | None = None,
+        exc_value: object | None = None,
+        traceback: object | None = None,
+    ) -> None: ...
+
+class APyFloat:
+    r"""
+    Floating-point scalar with configurable format.
+
+    The implementation is a generalization of the IEEE 754 standard, meaning that features like
+    subnormals, infinities, and NaN, are still supported. The format is defined
+    by the number of exponent and mantissa bits, and a non-negative bias. These fields are
+    named :attr:`exp_bits`, :attr:`man_bits`, and :attr:`bias` respectively. Similar to the hardware
+    representation of a floating-point number, the value is stored using three fields;
+    a sign bit :attr:`sign`, a biased exponent :attr:`exp`, and an integral mantissa with a hidden one :attr:`man`.
+    The value of a *normal* number would thus be
+
+    .. math::
+        (-1)^{\texttt{sign}} \times 2^{\texttt{exp} - \texttt{bias}} \times (1 + \texttt{man} \times 2^{\texttt{-man_bits}}).
+
+    In general, if the bias is not explicitly given for a format :class:`APyFloat` will default to an IEEE-like bias
+    using the formula
+
+    .. math::
+        \texttt{bias} = 2^{\texttt{exp_bits - 1}} - 1.
+
+    Arithmetic can be performed similarly to the operations of the built-in type
+    :class:`float` in Python. The resulting word length from operations will be
+    the same as the input operands' by quantizing to nearest number with ties to even (:class:`QuantizationMode.TIES_EVEN`).
+    If the operands do not share the same format, the resulting
+    bit widths of the exponent and mantissa field will be the maximum of its inputs:
+
+    .. code-block:: Python
+
+        from apytypes import APyFloat
+        a = APyFixed.from_float(1.25, exp_bits=5, man_bits=2)
+        b = APyFixed.from_float(1.75, exp_bits=5, man_bits=2)
+        # Operands with same format, result will have exp_bits=5, man_bits=2
+        c = a + b
+
+        d = APyFixed.from_float(1.75, exp_bits=4, man_bits=4)
+        # Operands with different formats, result will have exp_bits=5, man_bits=4
+        e = a + d
+
+
+    If the operands of an arithmetic operation have IEEE-like biases, then the result will
+    also have an IEEE-like bias -- based on the resulting number of exponent bits.
+    To support operations with biases deviating from the standard, the bias of the resulting format
+    is calculated as the "average" of the inputs' biases:
+
+    .. math::
+        \texttt{bias}_3 = \frac{\left ( \left (\texttt{bias}_1 + 1 \right ) / 2^{\texttt{exp_bits}_1} + \left (\texttt{bias}_2 + 1 \right ) / 2^{\texttt{exp_bits}_2} \right ) \times 2^{\texttt{exp_bits}_3}}{2} - 1,
+
+    where exp_bits_1 and exp_bits_2 are the bit widths of the operands, bias_1 and bias_2 are the input biases, and exp_bits_3 is the target bit width.
+    Note that this formula still results in an IEEE-like bias when the inputs use IEEE-like biases.
+    """
+
+    def __init__(
+        self,
+        sign: int,
+        exp: int,
+        man: int,
+        exp_bits: int,
+        man_bits: int,
+        bias: int | None = None,
+    ) -> None:
         """
-        Create a new resized fixed-point array based on the bit pattern in this
-        fixed-point array.
+        Create an :class:`APyFloat` object.
 
-        This is the primary method for performing quantization, truncation, overflowing,
-        and saturation when dealing with APyTypes fixed-point numbers.
+        Parameters
+        ----------
+        sign : bool or int
+            The sign of the float. False/0 means positive. True/non-zero means negative.
+        exp : int
+            Exponent of the float as stored, i.e., actual value + bias.
+        man : int
+            Mantissa of the float as stored, i.e., without a hidden one.
+        exp_bits : int
+            Number of exponent bits.
+        man_bits : int
+            Number of mantissa bits.
+        bias : int, optional
+            Bias. If not provided, *bias* is ``2**exp_bits - 1``.
 
-        Exactly two of three bit-specifiers (`bits`, `int_bits`, `frac_bits`) must
-        be set.
+        Returns
+        -------
+        :class:`APyFloat`
+        """
+
+    @staticmethod
+    def from_float(
+        value: object, exp_bits: int, man_bits: int, bias: int | None = None
+    ) -> APyFloat:
+        """
+        Create an :class:`APyFloat` object from an :class:`int` or :class:`float`.
+
+        The quantization mode used is :class:`QuantizationMode.TIES_EVEN`.
 
         Parameters
         ----------
-        int_bits : int, optional
-            Number of integer bits in the created fixed-point array-
-        frac_bits : int, optional
-            Number of fractional bits in the created fixed-point array.
-        quantization : :class:`QuantizationMode`, optional
-            Quantization mode to use in this cast.
-        overflow : :class:`OverflowMode`, optional
-            Overflowing mode to use in this cast.
-        bits : int, optional
-            Total number of bits in the created fixed-point array.
+        value : int, float
+            Floating-point value to initialize from.
+        exp_bits : int
+            Number of exponent bits.
+        man_bits : int
+            Number of mantissa bits.
+        bias : int, optional
+            Bias. If not provided, *bias* is ``2**exp_bits - 1``.
 
         Returns
         -------
-        :class:`APyFixedArray`
+        :class:`APyFloat`
+
+        Examples
+        --------
+
+        .. code-block:: python
+
+            from apytypes import APyFloat
+
+            # `a`, initialized from floating-point values.
+            a = APyFloat.from_float(1.35, exp_bits=10, man_bits=15)
+
+        See also
+        --------
+        from_bits
         """
 
-    def broadcast_to(self, shape: tuple | int) -> APyFixedArray: ...
-    def is_identical(self, other: APyFixedArray) -> bool:
+    def __float__(self) -> float: ...
+    @staticmethod
+    def from_bits(
+        bits: int, exp_bits: int, man_bits: int, bias: int | None = None
+    ) -> APyFloat:
         """
-        Test if two :class:`APyFixedArray` objects are identical.
+        Create an :class:`APyFloat` object from a bit-representation.
 
-        Two :class:`APyFixedArray` objects are considered identical if, and only if:
-          * They represent exactly the same tensor shape
-          * They store the exact same fixed-point values in all tensor elements
-          * They have the exact same bit specification (`bits`, `int_bits`, and
-            `frac_bits` are all equal)
+        Parameters
+        ----------
+        bits : int
+            The bit-representation for the float.
+        exp_bits : int
+            Number of exponent bits.
+        man_bits : int
+            Number of mantissa bits.
+        bias : int, optional
+            Bias. If not provided, *bias* is ``2**exp_bits - 1``.
+
+        Returns
+        -------
+        :class:`APyFloat`
+
+        Examples
+        --------
+
+        .. code-block:: python
+
+            from apytypes import APyFloat
+
+            # `a`, initialized to -1.5 from a bit pattern.
+            a = APyFloat.from_bits(0b1_01111_10, exp_bits=5, man_bits=2)
+
+        See also
+        --------
+        to_bits
+        from_float
+        """
+
+    def to_bits(self) -> int:
+        """
+        Get the bit-representation of an :class:`APyFloat`.
+
+        Returns
+        -------
+        :class:`int`
+
+        Examples
+        --------
+
+        .. code-block:: python
+
+            from apytypes import APyFloat
+
+            # `a`, initialized to -1.5 from a bit pattern.
+            a = APyFloat.from_bits(0b1_01111_10, exp_bits=5, man_bits=2)
+            a.to_bits() == 0b1_01111_10 # True
+
+        See also
+        --------
+        from_bits
+        """
+
+    def __str__(self) -> str: ...
+    def __repr__(self) -> str: ...
+    def cast(
+        self,
+        exp_bits: int | None = None,
+        man_bits: int | None = None,
+        bias: int | None = None,
+        quantization: QuantizationMode | None = None,
+    ) -> APyFloat:
+        """
+        Change format of the floating-point number.
+
+        This is the primary method for performing quantization when dealing with
+        APyTypes floating-point numbers.
+
+        Parameters
+        ----------
+        exp_bits : int, optional
+            Number of exponent bits in the result.
+        man_bits : int, optional
+            Number of mantissa bits in the result.
+        bias : int, optional
+            Bias. If not provided, *bias* is ``2**exp_bits - 1``.
+        quantization : :class:`QuantizationMode`, optional.
+            Quantization mode to use in this cast. If None, use the global
+            quantization mode.
+
+        Returns
+        -------
+        :class:`APyFloat`
+        """
+
+    @overload
+    def __add__(self, arg: APyFloat, /) -> APyFloat: ...
+    @overload
+    def __add__(self, arg: int, /) -> APyFloat: ...
+    @overload
+    def __add__(self, arg: float, /) -> APyFloat: ...
+    def __neg__(self) -> APyFloat: ...
+    @overload
+    def __sub__(self, arg: APyFloat, /) -> APyFloat: ...
+    @overload
+    def __sub__(self, arg: int, /) -> APyFloat: ...
+    @overload
+    def __sub__(self, arg: float, /) -> APyFloat: ...
+    @overload
+    def __mul__(self, arg: APyFloat, /) -> APyFloat: ...
+    @overload
+    def __mul__(self, arg: int, /) -> APyFloat: ...
+    @overload
+    def __mul__(self, arg: float, /) -> APyFloat: ...
+    @overload
+    def __truediv__(self, arg: APyFloat, /) -> APyFloat: ...
+    @overload
+    def __truediv__(self, arg: int, /) -> APyFloat: ...
+    @overload
+    def __truediv__(self, arg: float, /) -> APyFloat: ...
+    @overload
+    def __radd__(self, arg: int, /) -> APyFloat: ...
+    @overload
+    def __radd__(self, arg: float, /) -> APyFloat: ...
+    @overload
+    def __rsub__(self, arg: int, /) -> APyFloat: ...
+    @overload
+    def __rsub__(self, arg: float, /) -> APyFloat: ...
+    @overload
+    def __rmul__(self, arg: int, /) -> APyFloat: ...
+    @overload
+    def __rmul__(self, arg: float, /) -> APyFloat: ...
+    @overload
+    def __rtruediv__(self, arg: int, /) -> APyFloat: ...
+    @overload
+    def __rtruediv__(self, arg: float, /) -> APyFloat: ...
+    def __abs__(self) -> APyFloat: ...
+    @overload
+    def __pow__(self, arg: APyFloat, /) -> APyFloat: ...
+    @overload
+    def __pow__(self, arg: int, /) -> APyFloat: ...
+    @overload
+    def __eq__(self, arg: APyFloat, /) -> bool: ...
+    @overload
+    def __eq__(self, arg: float, /) -> bool: ...
+    @overload
+    def __eq__(self, arg: APyFixed, /) -> bool: ...
+    @overload
+    def __ne__(self, arg: APyFloat, /) -> bool: ...
+    @overload
+    def __ne__(self, arg: float, /) -> bool: ...
+    @overload
+    def __ne__(self, arg: APyFixed, /) -> bool: ...
+    @overload
+    def __lt__(self, arg: APyFloat, /) -> bool: ...
+    @overload
+    def __lt__(self, arg: float, /) -> bool: ...
+    @overload
+    def __lt__(self, arg: APyFixed, /) -> bool: ...
+    @overload
+    def __gt__(self, arg: APyFloat, /) -> bool: ...
+    @overload
+    def __gt__(self, arg: float, /) -> bool: ...
+    @overload
+    def __gt__(self, arg: APyFixed, /) -> bool: ...
+    @overload
+    def __le__(self, arg: APyFloat, /) -> bool: ...
+    @overload
+    def __le__(self, arg: float, /) -> bool: ...
+    @overload
+    def __le__(self, arg: APyFixed, /) -> bool: ...
+    @overload
+    def __ge__(self, arg: APyFloat, /) -> bool: ...
+    @overload
+    def __ge__(self, arg: float, /) -> bool: ...
+    @overload
+    def __ge__(self, arg: APyFixed, /) -> bool: ...
+    def __and__(self, arg: APyFloat, /) -> APyFloat: ...
+    def __or__(self, arg: APyFloat, /) -> APyFloat: ...
+    def __xor__(self, arg: APyFloat, /) -> APyFloat: ...
+    def __invert__(self) -> APyFloat: ...
+    @property
+    def is_zero(self) -> bool:
+        """
+        True if and only if value is zero.
 
         Returns
         -------
         :class:`bool`
         """
 
-    def to_numpy(self) -> numpy.ndarray[numpy.float64]:
+    @property
+    def is_normal(self) -> bool:
         """
-        Return array as a :class:`numpy.ndarray` of :class:`numpy.float64`.
+        True if and only if value is normal (not zero, subnormal, infinite, or NaN).
 
-        The returned array has the same `shape` and stored value as `self`. This
-        method rounds away from infinity on ties.
+        Returns
+        -------
+        :class:`bool`
+        """
+
+    @property
+    def is_subnormal(self) -> bool:
+        """
+        True if and only if value is subnormal.
+
+        Zero is considered subnormal.
 
         Returns
         -------
-        :class:`numpy.ndarray`
+        :class:`bool`
+        """
+
+    @property
+    def is_finite(self) -> bool:
+        """
+        True if and only if value is zero, subnormal, or normal.
+
+        Returns
+        -------
+        :class:`bool`
+        """
+
+    @property
+    def is_nan(self) -> bool:
+        """
+        True if and only if value is NaN.
+
+        Returns
+        -------
+        :class:`bool`
+        """
+
+    @property
+    def is_inf(self) -> bool:
+        """
+        True if and only if value is infinite.
+
+        Returns
+        -------
+        :class:`bool`
+        """
+
+    def is_identical(self, other: APyFloat) -> bool:
+        """
+        Test if two :py:class:`APyFloat` objects are identical.
+
+        Two :py:class:`APyFloat` objects are considered identical if, and only if,  they have
+        the same sign, exponent, mantissa, and format.
+
+        Returns
+        -------
+        :class:`bool`
+        """
+
+    @property
+    def sign(self) -> bool:
+        """
+        Sign bit.
+
+        See also
+        --------
+        true_sign
+
+        Returns
+        -------
+        :class:`bool`
+        """
+
+    @property
+    def true_sign(self) -> int:
+        """
+        Sign value.
+
+        See also
+        --------
+        sign
+
+        Returns
+        -------
+        :class:`int`
         """
 
-    def transpose(self) -> APyFixedArray:
+    @property
+    def man(self) -> int:
         """
-        Return the transposition of the array.
+        Mantissa bits.
 
-        If the dimension of `self` is one, this method returns the a copy of `self`.
-        If the dimension of `self` is two, this method returns the matrix
-        transposition of `self`.
+        These are without a possible hidden one.
 
-        Higher order transposition has not been implemented and will raise a
-        `NotImplementedException`.
+        See also
+        --------
+        true_man
 
         Returns
         -------
-        :class:`APyFixedArray`
+        :class:`int`
         """
 
     @property
-    def T(self) -> APyFixedArray:
+    def true_man(self) -> int:
         """
-        The transposition of the array.
+        Mantissa value.
 
-        Equivalent to calling :func:`APyFixedArray.transpose`.
+        These are with a possible hidden one.
+
+        See also
+        --------
+        man
 
         Returns
         -------
-        :class:`APyFixedArray`
+        :class:`int`
         """
 
     @property
-    def bits(self) -> int:
+    def exp(self) -> int:
         """
-        Total number of bits.
+        Exponent bits with bias.
+
+        See also
+        --------
+        true_exp
 
         Returns
         -------
@@ -641,9 +1251,16 @@ class APyFixedArray:
         """
 
     @property
-    def frac_bits(self) -> int:
+    def true_exp(self) -> int:
         """
-        Number of fractional bits.
+        Exponent value.
+
+        The bias value is subtracted and exponent adjusted in case of
+        a subnormal number.
+
+        See also
+        --------
+        exp
 
         Returns
         -------
@@ -651,9 +1268,9 @@ class APyFixedArray:
         """
 
     @property
-    def int_bits(self) -> int:
+    def bias(self) -> int:
         """
-        Number of integer bits.
+        Exponent bias.
 
         Returns
         -------
@@ -661,9 +1278,9 @@ class APyFixedArray:
         """
 
     @property
-    def ndim(self) -> int:
+    def man_bits(self) -> int:
         """
-        Number of dimensions in the array.
+        Number of mantissa bits.
 
         Returns
         -------
@@ -671,194 +1288,54 @@ class APyFixedArray:
         """
 
     @property
-    def shape(self) -> tuple:
+    def exp_bits(self) -> int:
         """
-        The shape of the array.
+        Number of exponent bits.
 
         Returns
         -------
-        :class:`tuple` of :class:`int`
+        :class:`int`
         """
 
-class APyFixedArrayIterator:
-    def __iter__(self) -> APyFixedArrayIterator: ...
-    def __next__(self) -> typing.Union[APyFixedArray, APyFixed]: ...
-
-class APyFloat:
-    """
-
-    Class for configurable floating-point formats.
-    """
-
-    __hash__: typing.ClassVar[None] = None
-    @staticmethod
-    def from_bits(
-        bits: int, exp_bits: int, man_bits: int, bias: int | None = None
-    ) -> APyFloat: ...
-    @staticmethod
-    def from_float(
-        value: float,
-        exp_bits: int,
-        man_bits: int,
-        bias: int | None = None,
-        quantization: QuantizationMode | None = None,
-    ) -> APyFloat:
+    @property
+    def bits(self) -> int:
         """
-        Create an :class:`APyFloat` object from a :class:`float`.
-
-        Parameters
-        ----------
-        float_sequence : float
-            Floating-point value to initialize from.
-        exp_bits : int
-            Number of exponent bits.
-        man_bits : int
-            Number of mantissa bits.
-        bias : int, optional
-            Bias.
-        quantization : :class:`QuantizationMode`, optional
-            Quantization mode to use. If not provided, the global mode,
-            see :func:`get_float_quantization_mode`, is used.
+        Total number of bits.
 
         Returns
         -------
-        :class:`APyFloat`
-
-        Examples
-        --------
-
-        .. code-block:: python
-
-            from apytypes import APyFloat
-
-            # `a`, initialized from floating-point values.
-            a = APyFloat.from_float(1.35, exp_bits=10, man_bits=15)
+        :class:`int`
         """
 
-    def __abs__(self) -> APyFloat: ...
-    @typing.overload
-    def __add__(self, arg0: APyFloat) -> APyFloat: ...
-    @typing.overload
-    def __add__(self, arg0: int) -> APyFloat: ...
-    @typing.overload
-    def __add__(self, arg0: float) -> APyFloat: ...
-    def __and__(self, arg0: APyFloat) -> APyFloat: ...
-    @typing.overload
-    def __eq__(self, arg0: APyFloat) -> bool: ...
-    @typing.overload
-    def __eq__(self, arg0: float) -> bool: ...
-    def __float__(self) -> float: ...
-    @typing.overload
-    def __ge__(self, arg0: APyFloat) -> bool: ...
-    @typing.overload
-    def __ge__(self, arg0: float) -> bool: ...
-    @typing.overload
-    def __gt__(self, arg0: APyFloat) -> bool: ...
-    @typing.overload
-    def __gt__(self, arg0: float) -> bool: ...
-    def __init__(
-        self,
-        sign: bool,
-        exp: int,
-        man: int,
-        exp_bits: int,
-        man_bits: int,
-        bias: int | None = None,
-    ) -> None: ...
-    def __invert__(self) -> APyFloat: ...
-    @typing.overload
-    def __le__(self, arg0: APyFloat) -> bool: ...
-    @typing.overload
-    def __le__(self, arg0: float) -> bool: ...
-    @typing.overload
-    def __lt__(self, arg0: APyFloat) -> bool: ...
-    @typing.overload
-    def __lt__(self, arg0: float) -> bool: ...
-    @typing.overload
-    def __mul__(self, arg0: APyFloat) -> APyFloat: ...
-    @typing.overload
-    def __mul__(self, arg0: int) -> 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: ...
-    def __neg__(self) -> APyFloat: ...
-    def __or__(self, arg0: APyFloat) -> APyFloat: ...
-    @typing.overload
-    def __pow__(self, arg0: APyFloat) -> APyFloat: ...
-    @typing.overload
-    def __pow__(self, arg0: int) -> APyFloat: ...
-    @typing.overload
-    def __radd__(self, arg0: int) -> APyFloat: ...
-    @typing.overload
-    def __radd__(self, arg0: float) -> APyFloat: ...
-    def __repr__(self) -> str: ...
-    @typing.overload
-    def __rmul__(self, arg0: int) -> APyFloat: ...
-    @typing.overload
-    def __rmul__(self, arg0: float) -> APyFloat: ...
-    @typing.overload
-    def __rsub__(self, arg0: int) -> APyFloat: ...
-    @typing.overload
-    def __rsub__(self, arg0: float) -> APyFloat: ...
-    def __str__(self) -> str: ...
-    @typing.overload
-    def __sub__(self, arg0: APyFloat) -> APyFloat: ...
-    @typing.overload
-    def __sub__(self, arg0: int) -> APyFloat: ...
-    @typing.overload
-    def __sub__(self, arg0: float) -> APyFloat: ...
-    def __truediv__(self, arg0: APyFloat) -> APyFloat: ...
-    def __xor__(self, arg0: APyFloat) -> APyFloat: ...
-    def _repr_latex_(self) -> str: ...
-    def cast(
-        self,
-        exp_bits: int | None = None,
-        man_bits: int | None = None,
-        bias: int | None = None,
-        quantization: QuantizationMode | None = None,
-    ) -> APyFloat: ...
-    def cast_to_bfloat16(
-        self, quantization: QuantizationMode | None = None
-    ) -> APyFloat:
+    def cast_to_double(self, quantization: QuantizationMode | None = None) -> APyFloat:
         """
-        Cast to bfloat16 format.
+        Cast to IEEE 754 binary64 (double-precision) format.
 
         Convenience method corresponding to
 
         .. code-block:: python
 
-           f.cast(exp_bits=8, man_bits=7)
+           f.cast(exp_bits=11, man_bits=52)
 
         Parameters
         ----------
-
         quantization : :class:`QuantizationMode`, optional
             Quantization mode to use. If not provided, the global mode,
             see :func:`get_float_quantization_mode`, is used.
         """
 
-    def cast_to_double(self, quantization: QuantizationMode | None = None) -> APyFloat:
+    def cast_to_single(self, quantization: QuantizationMode | None = None) -> APyFloat:
         """
-        Cast to IEEE 754 binary64 (double-precision) format.
+        Cast to IEEE 754 binary32 (single-precision) format.
 
         Convenience method corresponding to
 
         .. code-block:: python
 
-           f.cast(exp_bits=11, man_bits=52)
+           f.cast(exp_bits=8, man_bits=23)
 
         Parameters
         ----------
-
         quantization : :class:`QuantizationMode`, optional
             Quantization mode to use. If not provided, the global mode,
             see :func:`get_float_quantization_mode`, is used.
@@ -876,139 +1353,302 @@ class APyFloat:
 
         Parameters
         ----------
-
         quantization : :class:`QuantizationMode`, optional
             Quantization mode to use. If not provided, the global mode,
             see :func:`get_float_quantization_mode`, is used.
         """
 
-    def cast_to_single(self, quantization: QuantizationMode | None = None) -> APyFloat:
+    def cast_to_bfloat16(
+        self, quantization: QuantizationMode | None = None
+    ) -> APyFloat:
         """
-        Cast to IEEE 754 binary32 (single-precision) format.
+        Cast to bfloat16 format.
 
         Convenience method corresponding to
 
         .. code-block:: python
 
-           f.cast(exp_bits=8, man_bits=23)
+           f.cast(exp_bits=8, man_bits=7)
 
         Parameters
         ----------
-
         quantization : :class:`QuantizationMode`, optional
             Quantization mode to use. If not provided, the global mode,
             see :func:`get_float_quantization_mode`, is used.
         """
 
-    def is_identical(self, other: APyFloat) -> bool:
+    def next_up(self) -> APyFloat:
         """
-        Test if two `APyFloat` objects are identical.
+        Get the smallest floating-point number in the same format that compares greater.
 
-        Two `APyFixed` objects are considered identical if, and only if,  they have
-        the same sign, exponent, mantissa, and format.
+        See also
+        --------
+        next_down
 
         Returns
         -------
-        :class:`bool`
+        :class:`APyFloat`
         """
 
-    def to_bits(self) -> int: ...
-    def next_up(self) -> APyFloat:
+    def next_down(self) -> APyFloat:
         """
-        Get the smallest floating-point number in the same format that compares greater.
+        Get the largest floating-point number in the same format that compares less.
+
+        See also
+        --------
+        next_up
 
         Returns
         -------
         :class:`APyFloat`
         """
 
-    def next_down(self) -> APyFloat:
+class APyFloatAccumulatorContext(ContextManager):
+    """
+    Context for using custom accumulators when performing inner products and matrix multiplications.
+
+    Inner products and matrix multiplications will by default perform the summation in the resulting format
+    of the operands, but with :class:`APyFloatAccumulatorContext` a custom accumulator can be simulated
+    as seen in the example below.
+
+    .. code-block:: Python
+
+        import numpy as np
+        from apytypes import APyFloatArray, QuantizationMode
+        from apytypes import APyFloatAccumulatorContext
+
+        An = np.random.normal(1, 2, size=(100, 100))
+        A = APyFloatArray.from_float(An, exp_bits=5, man_bits=10)
+
+        bn = np.random.uniform(0, 1, size=100),
+        b = APyFloatArray.from_float(bn, exp_bits=5, man_bits=10)
+
+        # Normal matrix multiplication
+        c = A @ b.T
+
+        # Matrix multiplication using stochastic quantization and a wider accumulator
+        m = QuantizationMode.STOCH_WEIGHTED
+        with APyFloatAccumulatorContext(exp_bits=6, man_bits=15, quantization=m):
+            d = A @ b.T
+
+
+    If no quantization mode is specified to the accumulator context it will fallback to the mode set globally,
+    see :class:`APyFloatQuantizationContext`.
+    """
+
+    def __init__(
+        self,
+        exp_bits: int | None = None,
+        man_bits: int | None = None,
+        bias: int | None = None,
+        quantization: QuantizationMode | None = None,
+    ) -> None: ...
+    def __enter__(self) -> None: ...
+    def __exit__(
+        self,
+        exc_type: object | None = None,
+        exc_value: object | None = None,
+        traceback: object | None = None,
+    ) -> None: ...
+
+class APyFloatArray:
+    """
+    Class for multidimensional arrays with configurable floating-point formats.
+
+    :class:`APyFloatArray` is a class for multidimensional arrays with configurable
+    floating-point formats. The interface is much like the one of NumPy,
+    and direct plotting is supported by most functions in Matplotlib.
+    :class:`APyFloatArray` should always be preferred, if possible, when working with
+    arrays as it allows for better performance, and integration with other features of APyTypes.
+
+    For information about the workings of floating-point numbers, see its scalar
+    equivalent :class:`APyFloat`.
+    """
+
+    def __init__(
+        self,
+        signs: Sequence,
+        exps: Sequence,
+        mans: Sequence,
+        exp_bits: int,
+        man_bits: int,
+        bias: int | None = None,
+    ) -> None:
         """
-        Get the largest floating-point number in the same format that compares less.
+        Create an :class:`APyFloat` object.
+
+        Parameters
+        ----------
+        signs : sequence of bools or ints
+            The sign of the float. False/0 means positive. True/non-zero means negative.
+        exps : sequence of ints
+            Exponents of the floats as stored, i.e., actual value + bias.
+        mans : sequence of ints
+            Mantissas of the floats as stored, i.e., without a hidden one.
+        exp_bits : int
+            Number of exponent bits.
+        man_bits : int
+            Number of mantissa bits.
+        bias : int, optional
+            Bias. If not provided, *bias* is ``2**exp_bits - 1``.
 
         Returns
         -------
-        :class:`APyFloat`
+        :class:`APyFloatArray`
         """
 
+    @overload
+    def __add__(self, arg: APyFloatArray, /) -> APyFloatArray: ...
+    @overload
+    def __add__(self, arg: int, /) -> APyFloatArray: ...
+    @overload
+    def __add__(self, arg: float, /) -> APyFloatArray: ...
+    @overload
+    def __add__(self, arg: APyFloat, /) -> APyFloatArray: ...
+    @overload
+    def __radd__(self, arg: int, /) -> APyFloatArray: ...
+    @overload
+    def __radd__(self, arg: float, /) -> APyFloatArray: ...
+    @overload
+    def __radd__(self, arg: APyFloat, /) -> APyFloatArray: ...
+    @overload
+    def __sub__(self, arg: APyFloatArray, /) -> APyFloatArray: ...
+    @overload
+    def __sub__(self, arg: int, /) -> APyFloatArray: ...
+    @overload
+    def __sub__(self, arg: float, /) -> APyFloatArray: ...
+    @overload
+    def __sub__(self, arg: APyFloat, /) -> APyFloatArray: ...
+    def __neg__(self) -> APyFloatArray: ...
+    @overload
+    def __rsub__(self, arg: int, /) -> APyFloatArray: ...
+    @overload
+    def __rsub__(self, arg: float, /) -> APyFloatArray: ...
+    @overload
+    def __rsub__(self, arg: APyFloat, /) -> APyFloatArray: ...
+    @overload
+    def __mul__(self, arg: APyFloatArray, /) -> APyFloatArray: ...
+    @overload
+    def __mul__(self, arg: int, /) -> APyFloatArray: ...
+    @overload
+    def __mul__(self, arg: float, /) -> APyFloatArray: ...
+    @overload
+    def __mul__(self, arg: APyFloat, /) -> APyFloatArray: ...
+    @overload
+    def __rmul__(self, arg: int, /) -> APyFloatArray: ...
+    @overload
+    def __rmul__(self, arg: float, /) -> APyFloatArray: ...
+    @overload
+    def __rmul__(self, arg: APyFloat, /) -> APyFloatArray: ...
+    @overload
+    def __truediv__(self, arg: APyFloatArray, /) -> APyFloatArray: ...
+    @overload
+    def __truediv__(self, arg: int, /) -> APyFloatArray: ...
+    @overload
+    def __truediv__(self, arg: float, /) -> APyFloatArray: ...
+    @overload
+    def __truediv__(self, arg: APyFloat, /) -> APyFloatArray: ...
+    @overload
+    def __rtruediv__(self, arg: int, /) -> APyFloatArray: ...
+    @overload
+    def __rtruediv__(self, arg: float, /) -> APyFloatArray: ...
+    @overload
+    def __rtruediv__(self, arg: APyFloat, /) -> APyFloatArray: ...
+    def __abs__(self) -> APyFloatArray: ...
     @property
-    def bias(self) -> int: ...
-    @property
-    def exp(self) -> int: ...
-    @property
-    def exp_bits(self) -> int: ...
-    @property
-    def is_finite(self) -> bool:
+    def exp_bits(self) -> int:
         """
-        True if and only if x is zero, subnormal, or normal.
+        Number of exponent bits.
 
         Returns
         -------
-        :class:`bool`
+        :class:`int`
         """
 
     @property
-    def is_inf(self) -> bool:
+    def man_bits(self) -> int:
         """
-        True if and only if x is infinite.
+        Number of mantissa bits.
 
         Returns
         -------
-        :class:`bool`
+        :class:`int`
         """
 
     @property
-    def is_nan(self) -> bool:
+    def bits(self) -> int:
         """
-        True if and only if x is NaN.
+        Total number of bits.
 
         Returns
         -------
-        :class:`bool`
+        :class:`int`
         """
 
     @property
-    def is_normal(self) -> bool:
+    def bias(self) -> int:
         """
-        True if and only if x is normal (not zero, subnormal, infinite, or NaN).
+        Bias.
 
         Returns
         -------
-        :class:`bool`
+        :class:`int`
         """
 
     @property
-    def is_subnormal(self) -> bool:
+    def shape(self) -> tuple:
         """
-        True if and only if x is normal (not zero, subnormal, infinite, or NaN).
+        The shape of the array.
 
         Returns
         -------
-        :class:`bool`
+        :class:`tuple` of :class:`int`
         """
 
     @property
-    def man(self) -> int: ...
-    @property
-    def man_bits(self) -> int: ...
+    def ndim(self) -> int:
+        """
+        Number of dimensions in the array.
+
+        Returns
+        -------
+        :class:`int`
+        """
+
     @property
-    def sign(self) -> bool: ...
+    def T(self) -> APyFloatArray:
+        """
+        The transposition of the array.
 
-class APyFloatArray:
-    @staticmethod
-    def from_float(
-        float_sequence: typing.Sequence,
-        exp_bits: int,
-        man_bits: int,
-        bias: int | None = None,
-        quantization: QuantizationMode | None = None,
+        Equivalent to calling :func:`APyFloatArray.transpose`.
+
+        Returns
+        -------
+        :class:`APyFloatArray`
+        """
+
+    def to_numpy(self) -> Annotated[ArrayLike, dict(dtype="float64")]:
+        """
+        Return array as a :class:`numpy.ndarray` of :class:`numpy.float64`.
+
+        The returned array has the same `shape` and values as `self`. This
+        method rounds away from infinity on ties.
+
+        Returns
+        -------
+        :class:`numpy.ndarray`
+        """
+
+    @staticmethod
+    def from_float(
+        number_sequence: Sequence, exp_bits: int, man_bits: int, bias: int | None = None
     ) -> APyFloatArray:
         """
-        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
@@ -1017,9 +1657,6 @@ class APyFloatArray:
             Number of mantissa bits in the created fixed-point tensor
         bias : int, optional
             Bias in the created fixed-point tensor
-        quantization : :class:`QuantizationMode`, optional
-            Quantization mode to use. If not provided, the global mode,
-            see :func:`get_float_quantization_mode`, is used.
 
         Returns
         -------
@@ -1035,8 +1672,8 @@ class APyFloatArray:
             # 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],
@@ -1048,29 +1685,24 @@ class APyFloatArray:
 
     @staticmethod
     def from_array(
-        float_sequence,
+        ndarray: Annotated[ArrayLike, dict(order="C")],
         exp_bits: int,
         man_bits: int,
         bias: int | None = None,
-        quantization: QuantizationMode | None = None,
     ) -> APyFloatArray:
         """
-        Create an :class:`APyFloatArray` object from a sequence of :class:`float`.
+        Create an :class:`APyFloatArray` object from an ndarray.
 
         Parameters
         ----------
-        float_sequence : sequence of float
-            Floating point values to initialize from. The tensor shape will be taken
-            from the sequence shape.
+        ndarray : ndarray
+            Values to initialize from. The tensor shape will be taken from the ndarray shape.
         exp_bits : int
             Number of exponent bits in the created fixed-point tensor
         man_bits : int
             Number of mantissa bits in the created fixed-point tensor
         bias : int, optional
             Bias in the created fixed-point tensor
-        quantization : :class:`QuantizationMode`, optional
-            Quantization mode to use. If not provided, the global mode,
-            see :func:`get_float_quantization_mode`, is used.
 
         Returns
         -------
@@ -1095,83 +1727,99 @@ class APyFloatArray:
             )
         """
 
-    @typing.overload
-    def __add__(self, arg0: APyFloatArray) -> APyFloatArray: ...
-    @typing.overload
-    def __add__(self, arg0: int) -> APyFloatArray: ...
-    @typing.overload
-    def __add__(self, arg0: float) -> APyFloatArray: ...
-    @typing.overload
-    def __add__(self, arg0: APyFloat) -> APyFloatArray: ...
-    def __array__(self) -> numpy.ndarray[numpy.float64]: ...
-    def __getitem__(self, idx: int) -> typing.Union[APyFloatArray, APyFloat]: ...
-    def __init__(
-        self,
-        signs: typing.Sequence,
-        exps: typing.Sequence,
-        mans: typing.Sequence,
-        exp_bits: int,
-        man_bits: int,
-        bias: int | None = None,
-    ) -> None: ...
-    def __iter__(self) -> APyFloatArrayIterator: ...
-    def __len__(self) -> int: ...
-    def __matmul__(self, rhs: APyFloatArray) -> APyFloatArray: ...
-    @typing.overload
-    def __mul__(self, arg0: APyFloatArray) -> APyFloatArray: ...
-    @typing.overload
-    def __mul__(self, arg0: int) -> APyFloatArray: ...
-    @typing.overload
-    def __mul__(self, arg0: float) -> APyFloatArray: ...
-    @typing.overload
-    def __mul__(self, arg0: APyFloat) -> APyFloatArray: ...
-    @typing.overload
-    def __radd__(self, arg0: int) -> APyFloatArray: ...
-    @typing.overload
-    def __radd__(self, arg0: float) -> APyFloatArray: ...
-    @typing.overload
-    def __radd__(self, arg0: APyFloat) -> APyFloatArray: ...
+    def __matmul__(self, rhs: APyFloatArray) -> APyFloatArray | APyFloat: ...
     def __repr__(self) -> str: ...
-    @typing.overload
-    def __rmul__(self, arg0: int) -> APyFloatArray: ...
-    @typing.overload
-    def __rmul__(self, arg0: float) -> APyFloatArray: ...
-    @typing.overload
-    def __rmul__(self, arg0: APyFloat) -> APyFloatArray: ...
-    @typing.overload
-    def __sub__(self, arg0: APyFloatArray) -> APyFloatArray: ...
-    @typing.overload
-    def __sub__(self, arg0: int) -> APyFloatArray: ...
-    @typing.overload
-    def __sub__(self, arg0: float) -> APyFloatArray: ...
-    @typing.overload
-    def __sub__(self, arg0: APyFloat) -> APyFloatArray: ...
-    @typing.overload
-    def __truediv__(self, arg0: APyFloatArray) -> APyFloatArray: ...
-    @typing.overload
-    def __truediv__(self, arg0: int) -> APyFloatArray: ...
-    @typing.overload
-    def __truediv__(self, arg0: float) -> APyFloatArray: ...
-    @typing.overload
-    def __truediv__(self, arg0: APyFloat) -> APyFloatArray: ...
+    def __len__(self) -> int: ...
+    def is_identical(self, other: APyFloatArray) -> bool:
+        """
+        Test if two :class:`APyFloatArray` objects are identical.
+
+        Two :class:`APyFloatArray` objects are considered identical if, and only if:
+          * They represent exactly the same tensor shape
+          * They store the exact same floating-ppint values in all tensor elements
+          * They have the exact same bit format (`exp_bits`, `man_bits`, and `bias`)
+
+        Returns
+        -------
+        :class:`bool`
+        """
+
+    def transpose(self) -> APyFloatArray:
+        """
+        Return the transposition of the array.
+
+        If the dimension of `self` is one, this method returns the a copy of `self`.
+        If the dimension of `self` is two, this method returns the matrix
+        transposition of `self`.
+
+        Higher order transposition has not been implemented and will raise a
+        `NotImplementedException`.
+
+        Returns
+        -------
+        :class:`APyFloatArray`
+        """
+
+    def broadcast_to(self, shape: tuple | int) -> APyFloatArray:
+        """
+        Broadcast array to new shape.
+
+        .. versionadded:: 0.2
+
+        Parameters
+        ----------
+        shape : tuple or int
+            The shape to broadcast to. A single integer ``i`` is interpreted as ``(i,)``.
+
+        Returns
+        -------
+        :class:`APyFloatArray`
+        """
+
+    def __getitem__(self, idx: int) -> APyFloatArray | APyFloat: ...
+    def __iter__(self) -> APyFloatArrayIterator: ...
+    def __array__(self) -> Annotated[ArrayLike, dict(dtype="float64")]: ...
     def cast(
         self,
         exp_bits: int | None = None,
         man_bits: int | None = None,
         bias: int | None = None,
         quantization: QuantizationMode | None = None,
-    ) -> APyFloatArray: ...
-    def cast_to_bfloat16(
+    ) -> APyFloatArray:
+        """
+        Change format of the floating-point number.
+
+        This is the primary method for performing quantization when dealing with
+        APyTypes floating-point numbers.
+
+        Parameters
+        ----------
+        exp_bits : int, optional
+            Number of exponent bits in the result.
+        man_bits : int, optional
+            Number of mantissa bits in the result.
+        bias : int, optional
+            Bias used in the result.
+        quantization : :class:`QuantizationMode`, optional.
+            Quantization mode to use in this cast. If None, use the global
+            quantization mode.
+
+        Returns
+        -------
+        :class:`APyFloat`
+        """
+
+    def cast_to_double(
         self, quantization: QuantizationMode | None = None
     ) -> APyFloatArray:
         """
-        Cast to bfloat16 format.
+        Cast to IEEE 754 binary64 (double-precision) format.
 
         Convenience method corresponding to
 
         .. code-block:: python
 
-           f.cast(exp_bits=8, man_bits=7)
+           f.cast(exp_bits=11, man_bits=52)
 
         Parameters
         ----------
@@ -1181,18 +1829,17 @@ class APyFloatArray:
             see :func:`get_float_quantization_mode`, is used.
         """
 
-    def broadcast_to(self, shape: tuple | int) -> APyFloatArray: ...
-    def cast_to_double(
+    def cast_to_single(
         self, quantization: QuantizationMode | None = None
     ) -> APyFloatArray:
         """
-        Cast to IEEE 754 binary64 (double-precision) format.
+        Cast to IEEE 754 binary32 (single-precision) format.
 
         Convenience method corresponding to
 
         .. code-block:: python
 
-           f.cast(exp_bits=11, man_bits=52)
+           f.cast(exp_bits=8, man_bits=23)
 
         Parameters
         ----------
@@ -1222,17 +1869,17 @@ class APyFloatArray:
             see :func:`get_float_quantization_mode`, is used.
         """
 
-    def cast_to_single(
+    def cast_to_bfloat16(
         self, quantization: QuantizationMode | None = None
     ) -> APyFloatArray:
         """
-        Cast to IEEE 754 binary32 (single-precision) format.
+        Cast to bfloat16 format.
 
         Convenience method corresponding to
 
         .. code-block:: python
 
-           f.cast(exp_bits=8, man_bits=23)
+           f.cast(exp_bits=8, man_bits=7)
 
         Parameters
         ----------
@@ -1242,328 +1889,209 @@ class APyFloatArray:
             see :func:`get_float_quantization_mode`, is used.
         """
 
-    def is_identical(self, other: APyFloatArray) -> bool:
-        """
-        Test if two :class:`APyFloatArray` objects are identical.
-
-        Two :class:`APyFloatArray` objects are considered identical if, and only if:
-          * They represent exactly the same tensor shape
-          * They store the exact same floating-ppint values in all tensor elements
-          * They have the exact same bit format (`exp_bits`, `man_bits`, and `bias`)
-
-        Returns
-        -------
-        :class:`bool`
-        """
+class APyFloatArrayIterator:
+    def __iter__(self) -> APyFloatArrayIterator: ...
+    def __next__(self) -> APyFloatArray | APyFloat: ...
 
-    def to_numpy(self) -> numpy.ndarray[numpy.float64]:
-        """
-        Return array as a :class:`numpy.ndarray` of :class:`numpy.float64`.
+class APyFloatQuantizationContext(ContextManager):
+    """
+    Context for changing the quantization mode used for floating-point operations.
 
-        The returned array has the same `shape` and stored value as `self`. This
-        method rounds away from infinity on ties.
+    If not specified explicitly, floating-point operations will use the quantization mode that is set globally,
+    which is :class:`QuantizationMode.TIES_EVEN` by default. The mode however can be changed using the static method
+    :func:`apytypes.set_float_quantization_mode`, or, preferably, by using a so-called quantization context.
+    With a quantization context one can change the quantization mode used by all operations within a specific section
+    in the code:
 
-        Returns
-        -------
-        :class:`numpy.ndarray`
-        """
+    .. code-block:: Python
 
-    def transpose(self) -> APyFloatArray:
-        """
-        Return the transposition of the array.
+        import random
+        from apytypes import APyFloat, QuantizationMode
+        from apytypes import APyFloatQuantizationContext
 
-        If the dimension of `self` is one, this method returns the a copy of `self`.
-        If the dimension of `self` is two, this method returns the matrix
-        transposition of `self`.
+        # Addition using the default round to nearest, ties even
+        a = APyFloat.from_float(random.uniform(-100, 100), exp_bits=5, man_bits=10)
+        b = APyFloat.from_float(random.uniform(-100, 100), exp_bits=5, man_bits=10)
+        c = a + b
 
-        Higher order transposition has not been implemented and will raise a
-        `NotImplementedException`.
+        # Addition using round towards zero
+        m = QuantizationMode.TO_ZERO
+        with APyFloatQuantizationContext(quantization=mode):
+            d = a + b
 
-        Returns
-        -------
-        :class:`APyFloatArray`
-        """
+        # Stochastic rounding with an optional seed
+        m = QuantizationMode.STOCH_WEIGHTED
+        s = 0x1234
+        with APyFloatQuantizationContext(quantization=mode, quantization_seed=s):
+            e = a + b
 
-    @property
-    def T(self) -> APyFloatArray:
-        """
-        The transposition of the array.
 
-        Equivalent to calling :func:`APyFloatArray.transpose`.
+    The quantization mode is reverted automatically upon exiting the context.
+    Nesting the contexts is also possible.
+    """
 
-        Returns
-        -------
-        :class:`APyFloatArray`
-        """
+    def __init__(
+        self, quantization: QuantizationMode, quantization_seed: int | None = None
+    ) -> None: ...
+    def __enter__(self) -> None: ...
+    def __exit__(
+        self,
+        exc_type: object | None = None,
+        exc_value: object | None = None,
+        traceback: object | None = None,
+    ) -> None: ...
 
-    @property
-    def bias(self) -> int:
-        """
-        Bias.
+class ContextManager:
+    pass
 
-        Returns
-        -------
-        :class:`int`
-        """
+class OverflowMode(enum.Enum):
+    WRAP = 0
+    """Two's complement overflow. Remove MSBs."""
 
-    @property
-    def exp_bits(self) -> int:
-        """
-        Number of exponent bits.
+    SAT = 1
+    """Saturate to the closest of most positive and most negative value."""
 
-        Returns
-        -------
-        :class:`int`
-        """
+    NUMERIC_STD = 2
+    """
+    Remove MSBs, but keep the most significant bit. As ieee.numeric_std resize for signed.
+    """
 
-    @property
-    def man_bits(self) -> int:
-        """
-        Number of mantissa bits.
+class QuantizationMode(enum.Enum):
+    TO_NEG = 0
+    """Alias. Round towards negative infinity."""
 
-        Returns
-        -------
-        :class:`int`
-        """
+    TO_ZERO = 2
+    """Alias. Round towards zero."""
 
-    @property
-    def ndim(self) -> int:
-        """
-        Number of dimensions in the array.
+    TO_POS = 1
+    """Alias. Round towards positive infinity."""
 
-        Returns
-        -------
-        :class:`int`
-        """
+    TO_AWAY = 3
+    """Alias. Truncate away from zero."""
 
-    @property
-    def shape(self) -> tuple:
-        """
-        The shape of the array.
+    TIES_ZERO = 6
+    """Alias. Round to nearest, ties toward zero."""
 
-        Returns
-        -------
-        :class:`tuple` of :class:`int`
-        """
+    TIES_AWAY = 7
+    """Alias. Round to nearest, ties away from zero."""
 
-class APyFloatArrayIterator:
-    def __iter__(self) -> APyFloatArrayIterator: ...
-    def __next__(self) -> typing.Union[APyFloatArray, APyFloat]: ...
+    TIES_EVEN = 9
+    """Alias. Round to nearest, ties to even."""
 
-class APyFloatAccumulatorContext(ContextManager):
-    def __enter__(self: ContextManager) -> None: ...
-    def __exit__(
-        self: ContextManager,
-        arg0: type | None,
-        arg1: typing.Any | None,
-        arg2: typing.Any | None,
-    ) -> None: ...
-    def __init__(
-        self,
-        *,
-        exp_bits: int | None = None,
-        man_bits: int | None = None,
-        bias: int | None = None,
-        quantization: QuantizationMode | None = None,
-    ) -> None: ...
+    TIES_ODD = 10
+    """Alias. Round to nearest, ties to odd."""
 
-class APyFixedAccumulatorContext(ContextManager):
-    def __enter__(self: ContextManager) -> None: ...
-    def __exit__(
-        self: ContextManager,
-        arg0: type | None,
-        arg1: typing.Any | None,
-        arg2: typing.Any | None,
-    ) -> None: ...
-    def __init__(
-        self,
-        *,
-        int_bits: int | None = None,
-        frac_bits: int | None = None,
-        quantization: QuantizationMode | None = None,
-        overflow: OverflowMode | None = None,
-        bits: int | None = None,
-    ) -> None: ...
+    TIES_NEG = 8
+    """Alias. Round to nearest, ties towards negative infinity."""
 
-class APyFixedCastContext(ContextManager):
-    def __enter__(self: ContextManager) -> None: ...
-    def __exit__(
-        self: ContextManager,
-        arg0: type | None,
-        arg1: typing.Any | None,
-        arg2: typing.Any | None,
-    ) -> None: ...
-    def __init__(
-        self,
-        *,
-        quantization: QuantizationMode | None = None,
-        overflow: OverflowMode | None = None,
-    ) -> None: ...
+    TIES_POS = 5
+    """Alias. Round to nearest, ties towards positive infinity."""
 
-class ContextManager:
-    pass
+    TRN = 0
+    """Truncation (remove bits, round towards negative infinity"""
 
-class OverflowMode:
-    """
-    Members:
+    TRN_ZERO = 2
+    """Unbiased magnitude truncation (round towards zero)."""
 
-      WRAP : Two's complement overflow. Remove MSBs.
+    TRN_INF = 1
+    """Round towards positive infinity."""
 
-      SAT : Saturate to the closest of most positive and most negative value.
+    TRN_AWAY = 3
+    """Truncate away from zero."""
 
-      NUMERIC_STD : Remove MSBs, but keep the most significant bit. As ieee.numeric_std resize for signed.
+    TRN_MAG = 4
     """
-
-    NUMERIC_STD: typing.ClassVar[OverflowMode]  # value = <OverflowMode.NUMERIC_STD: 2>
-    SAT: typing.ClassVar[OverflowMode]  # value = <OverflowMode.SAT: 1>
-    WRAP: typing.ClassVar[OverflowMode]  # value = <OverflowMode.WRAP: 0>
-    __members__: typing.ClassVar[
-        dict[str, OverflowMode]
-    ]  # value = {'WRAP': <OverflowMode.WRAP: 0>, 'SAT': <OverflowMode.SAT: 1>, 'NUMERIC_STD': <OverflowMode.NUMERIC_STD: 2>}
-    def __eq__(self, other: typing.Any) -> bool: ...
-    def __getstate__(self) -> int: ...
-    def __hash__(self) -> int: ...
-    def __index__(self) -> int: ...
-    def __init__(self, value: int) -> None: ...
-    def __int__(self) -> int: ...
-    def __ne__(self, other: typing.Any) -> bool: ...
-    def __repr__(self) -> str: ...
-    def __setstate__(self, state: int) -> None: ...
-    def __str__(self) -> str: ...
-    @property
-    def name(self) -> str: ...
-    @property
-    def value(self) -> int: ...
-
-class APyFloatQuantizationContext(ContextManager):
-    def __enter__(self: ContextManager) -> None: ...
-    def __exit__(
-        self: ContextManager,
-        arg0: type | None,
-        arg1: typing.Any | None,
-        arg2: typing.Any | None,
-    ) -> None: ...
-    def __init__(
-        self, quantization: QuantizationMode, quantization_seed: int | None = None
-    ) -> None: ...
-
-class QuantizationMode:
+    Magnitude truncation (round towards zero), fixed-point friendly variant (add sign-bit).
     """
-    Members:
-
-      TRN : Truncation (remove bits, round towards negative infinity
 
-      TRN_ZERO : Magnitude truncation (round towards zero).
+    RND = 5
+    """
+    Round to nearest, ties towards positive infinity (standard 'round' for fixed-point).
+    """
 
-      TRN_INF : Round towards positive infinity.
+    RND_ZERO = 6
+    """Round to nearest, ties toward zero."""
 
-      RND : Round to nearest, ties towards positive infinity (standard 'round' for fixed-point).
+    RND_INF = 7
+    """Round to nearest, ties away from zero."""
 
-      RND_ZERO : Round to nearest, ties toward zero.
+    RND_MIN_INF = 8
+    """Round to nearest, ties toward negative infinity."""
 
-      RND_INF : Round to nearest, ties away from zero.
+    RND_CONV = 9
+    """Round to nearest, ties to even."""
 
-      RND_MIN_INF : Round to nearest, ties toward negative infinity.
+    RND_CONV_ODD = 10
+    """Round to nearest, ties to odd."""
 
-      RND_CONV : Round to nearest, ties to even.
+    JAM = 11
+    """Jamming/von Neumann rounding. Set LSB to 1."""
 
-      RND_CONV_ODD : Round to nearest, ties to odd.
+    JAM_UNBIASED = 12
+    """
+    Unbiased jamming/von Neumann rounding. Set LSB to 1 unless the previous LSB and all the removed bits are 0.
+    """
 
-      JAM : Jamming/von Neumann rounding. Set LSB to 1.
+    STOCH_WEIGHTED = 13
+    """Stochastic rounding. Probability depends on the bits to remove."""
 
-      JAM_UNBIASED : Unbiased jamming/von Neumann rounding. Set LSB to 1 unless the previous LSB and all the removed bits are 0.
+    STOCH_EQUAL = 14
+    """Stochastic rounding with equal probability."""
 
-      STOCH_WEIGHTED : Stochastic rounding. Probability depends on the bits to remove.
+def get_float_quantization_mode() -> QuantizationMode:
+    """
+    Get current quantization context.
 
-      STOCH_EQUAL : Stochastic rounding with equal probability.
+    Returns
+    -------
+    :class:`QuantizationMode`
 
-      TO_NEG : Alias. Round towards negative infinity.
+    See also
+    --------
+    set_float_quantization_mode
+    """
 
-      TO_ZERO : Alias. Round towards zero.
+def get_float_quantization_seed() -> int:
+    """
+    Set current quantization seed.
 
-      TO_POS : Alias. Round towards positive infinity.
+    The quantization seed is used for stochastic quantization.
 
-      TIES_ZERO : Alias. Round to nearest, ties toward zero.
+    Returns
+    -------
+    :class:`int`
 
-      TIES_AWAY : Alias. Round to nearest, ties away from zero.
+    See also
+    --------
+    set_float_quantization_seed
+    """
 
-      TIES_EVEN : Alias. Round to nearest, ties to even.
+def set_float_quantization_mode(mode: QuantizationMode) -> None:
+    """
+    Set current quantization context.
 
-      TIES_ODD : Alias. Round to nearest, ties to odd.
+    Parameters
+    ----------
+    mode : :class:`QuantizationMode`
+        The quantization mode to use.
 
-      TIES_NEG : Alias. Round to nearest, ties towards negative infinity.
+    See also
+    --------
+    get_float_quantization_mode
+    """
 
-      TIES_POS : Alias. Round to nearest, ties towards positive infinity.
+def set_float_quantization_seed(seed: int) -> None:
     """
+    Set current quantization seed.
 
-    JAM: typing.ClassVar[QuantizationMode]  # value = <QuantizationMode.JAM: 9>
-    JAM_UNBIASED: typing.ClassVar[
-        QuantizationMode
-    ]  # value = <QuantizationMode.JAM_UNBIASED: 10>
-    RND: typing.ClassVar[QuantizationMode]  # value = <QuantizationMode.RND: 3>
-    RND_CONV: typing.ClassVar[
-        QuantizationMode
-    ]  # value = <QuantizationMode.RND_CONV: 7>
-    RND_CONV_ODD: typing.ClassVar[
-        QuantizationMode
-    ]  # value = <QuantizationMode.RND_CONV_ODD: 8>
-    RND_INF: typing.ClassVar[QuantizationMode]  # value = <QuantizationMode.RND_INF: 5>
-    RND_MIN_INF: typing.ClassVar[
-        QuantizationMode
-    ]  # value = <QuantizationMode.RND_MIN_INF: 6>
-    RND_ZERO: typing.ClassVar[
-        QuantizationMode
-    ]  # value = <QuantizationMode.RND_ZERO: 4>
-    STOCH_EQUAL: typing.ClassVar[
-        QuantizationMode
-    ]  # value = <QuantizationMode.STOCH_EQUAL: 12>
-    STOCH_WEIGHTED: typing.ClassVar[
-        QuantizationMode
-    ]  # value = <QuantizationMode.STOCH_WEIGHTED: 11>
-    TIES_AWAY: typing.ClassVar[
-        QuantizationMode
-    ]  # value = <QuantizationMode.RND_INF: 5>
-    TIES_EVEN: typing.ClassVar[
-        QuantizationMode
-    ]  # value = <QuantizationMode.RND_CONV: 7>
-    TIES_NEG: typing.ClassVar[
-        QuantizationMode
-    ]  # value = <QuantizationMode.RND_MIN_INF: 6>
-    TIES_ODD: typing.ClassVar[
-        QuantizationMode
-    ]  # value = <QuantizationMode.RND_CONV_ODD: 8>
-    TIES_POS: typing.ClassVar[QuantizationMode]  # value = <QuantizationMode.RND: 3>
-    TIES_ZERO: typing.ClassVar[
-        QuantizationMode
-    ]  # value = <QuantizationMode.RND_ZERO: 4>
-    TO_NEG: typing.ClassVar[QuantizationMode]  # value = <QuantizationMode.TRN: 0>
-    TO_POS: typing.ClassVar[QuantizationMode]  # value = <QuantizationMode.TRN_INF: 1>
-    TO_ZERO: typing.ClassVar[QuantizationMode]  # value = <QuantizationMode.TRN_ZERO: 2>
-    TRN: typing.ClassVar[QuantizationMode]  # value = <QuantizationMode.TRN: 0>
-    TRN_INF: typing.ClassVar[QuantizationMode]  # value = <QuantizationMode.TRN_INF: 1>
-    TRN_MAG: typing.ClassVar[QuantizationMode]
-    TRN_ZERO: typing.ClassVar[
-        QuantizationMode
-    ]  # value = <QuantizationMode.TRN_ZERO: 2>
-    __members__: typing.ClassVar[
-        dict[str, QuantizationMode]
-    ]  # value = {'TRN': <QuantizationMode.TRN: 0>, 'TRN_ZERO': <QuantizationMode.TRN_ZERO: 2>, 'TRN_INF': <QuantizationMode.TRN_INF: 1>, 'RND': <QuantizationMode.RND: 3>, 'RND_ZERO': <QuantizationMode.RND_ZERO: 4>, 'RND_INF': <QuantizationMode.RND_INF: 5>, 'RND_MIN_INF': <QuantizationMode.RND_MIN_INF: 6>, 'RND_CONV': <QuantizationMode.RND_CONV: 7>, 'RND_CONV_ODD': <QuantizationMode.RND_CONV_ODD: 8>, 'JAM': <QuantizationMode.JAM: 9>, 'JAM_UNBIASED': <QuantizationMode.JAM_UNBIASED: 10>, 'STOCH_WEIGHTED': <QuantizationMode.STOCH_WEIGHTED: 11>, 'STOCH_EQUAL': <QuantizationMode.STOCH_EQUAL: 12>, 'TO_NEG': <QuantizationMode.TRN: 0>, 'TO_ZERO': <QuantizationMode.TRN_ZERO: 2>, 'TO_POS': <QuantizationMode.TRN_INF: 1>, 'TIES_ZERO': <QuantizationMode.RND_ZERO: 4>, 'TIES_AWAY': <QuantizationMode.RND_INF: 5>, 'TIES_EVEN': <QuantizationMode.RND_CONV: 7>, 'TIES_ODD': <QuantizationMode.RND_CONV_ODD: 8>, 'TIES_NEG': <QuantizationMode.RND_MIN_INF: 6>, 'TIES_POS': <QuantizationMode.RND: 3>}
-    def __eq__(self, other: typing.Any) -> bool: ...
-    def __getstate__(self) -> int: ...
-    def __hash__(self) -> int: ...
-    def __index__(self) -> int: ...
-    def __init__(self, value: int) -> None: ...
-    def __int__(self) -> int: ...
-    def __ne__(self, other: typing.Any) -> bool: ...
-    def __repr__(self) -> str: ...
-    def __setstate__(self, state: int) -> None: ...
-    def __str__(self) -> str: ...
-    @property
-    def name(self) -> str: ...
-    @property
-    def value(self) -> int: ...
+    The quantization seed is used for stochastic quantization.
 
-def get_float_quantization_mode() -> QuantizationMode: ...
-def get_float_quantization_seed() -> int: ...
-def set_float_quantization_mode(arg0: QuantizationMode) -> None: ...
-def set_float_quantization_seed(arg0: int) -> None: ...
+    Parameters
+    ----------
+    seed : int
+        The quantization seed to use.
+
+    See also
+    --------
+    get_float_quantization_seed
+    """
diff --git a/pyproject.toml b/pyproject.toml
index c20b8ca92..ae85aa4c5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -43,7 +43,7 @@ docs = [
     "docutils<=0.20",  # Pin to get around limitation in m2r2
 ]
 test = ["numpy>=1.25", "pytest>=8.2"]
-dev = ["nanobind>=1.9.2"]
+dev = ["nanobind>=2.0.0"]
 benchmark = ["numpy>=1.25", "pytest>=8.2", "pytest-benchmark"]
 comparison = [
     "fpbinary",
diff --git a/src/apyfixed_wrapper.cc b/src/apyfixed_wrapper.cc
index 165d340ba..9233be6fb 100644
--- a/src/apyfixed_wrapper.cc
+++ b/src/apyfixed_wrapper.cc
@@ -355,7 +355,6 @@ void bind_fixed(nb::module_& m)
          */
         .def("__abs__", &APyFixed::abs)
         .def("__float__", &APyFixed::operator double)
-        .def("__neg__", [](APyFixed& fix) { return -fix; })
         .def("__repr__", &APyFixed::repr)
         .def("__str__", &APyFixed::to_string, nb::arg("base") = 10)
         .def(
diff --git a/src/apyfloatarray_wrapper.cc b/src/apyfloatarray_wrapper.cc
index 46e1abdd2..7c9dd43f7 100644
--- a/src/apyfloatarray_wrapper.cc
+++ b/src/apyfloatarray_wrapper.cc
@@ -277,7 +277,7 @@ void bind_float_array(nb::module_& m)
             :class:`int`
             )pbdoc")
 
-        .def_prop_ro("bits", &APyFloat::get_bits, R"pbdoc(
+        .def_prop_ro("bits", &APyFloatArray::get_bits, R"pbdoc(
             Total number of bits.
 
             Returns
diff --git a/src/apytypes_context_wrapper.cc b/src/apytypes_context_wrapper.cc
index 5fdd8ec9e..2afe9086b 100644
--- a/src/apytypes_context_wrapper.cc
+++ b/src/apytypes_context_wrapper.cc
@@ -50,8 +50,6 @@ void bind_cast_context(nb::module_& m)
     nb::class_<APyFixedCastContext, ContextManager>(m, "APyFixedCastContext")
         .def(
             nb::init<std::optional<QuantizationMode>, std::optional<OverflowMode>>(),
-            // nb::kw_only() is added in NanoBind v2.0.0
-            // nb::kw_only(), // All parameters are keyword only
             nb::arg("quantization") = nb::none(),
             nb::arg("overflow") = nb::none()
         )
@@ -77,8 +75,6 @@ void bind_accumulator_context(nb::module_& m)
                 std::optional<QuantizationMode>,
                 std::optional<OverflowMode>,
                 std::optional<int>>(),
-            // nb::kw_only() is added in NanoBind v2.0.0
-            // nb::kw_only(), // All parameters are keyword only
             nb::arg("int_bits") = nb::none(),
             nb::arg("frac_bits") = nb::none(),
             nb::arg("quantization") = nb::none(),
@@ -103,8 +99,6 @@ void bind_accumulator_context(nb::module_& m)
                 std::optional<int>,
                 std::optional<exp_t>,
                 std::optional<QuantizationMode>>(),
-            // nb::kw_only() is added in NanoBind v2.0.0
-            // nb::kw_only(), // All parameters are keyword only
             nb::arg("exp_bits") = nb::none(),
             nb::arg("man_bits") = nb::none(),
             nb::arg("bias") = nb::none(),
diff --git a/stubgen.sh b/stubgen.sh
new file mode 100755
index 000000000..5ea1c6ad6
--- /dev/null
+++ b/stubgen.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env sh
+python3 -m nanobind.stubgen -m apytypes -o lib/apytypes/__init__.pyi
+python3 -m nanobind.stubgen -m apytypes._apytypes -o lib/apytypes/_apytypes.pyi
diff --git a/subprojects/nanobind.wrap b/subprojects/nanobind.wrap
index 3b92036ba..b2c4b2925 100644
--- a/subprojects/nanobind.wrap
+++ b/subprojects/nanobind.wrap
@@ -1,7 +1,7 @@
 [wrap-git]
 method = cmake
 url = https://github.com/wjakob/nanobind/
-revision = v1.9.2
+revision = v2.0.0
 depth = 1
 clone-recursive = True
 


Test patch:
diff --git a/lib/test/test_contextmanagers.py b/lib/test/test_contextmanagers.py
index 672f38208..16c3ed8d0 100644
--- a/lib/test/test_contextmanagers.py
+++ b/lib/test/test_contextmanagers.py
@@ -3,17 +3,6 @@
 import pytest
 
 
-@pytest.mark.xfail()
-def test_context_kw_only():
-    """
-    This method should raise a `TypeError`, but since kw-only arguments are not part of
-    Nanobind <= v1.9.2, we wait with this until gets a tagged release.
-    """
-    with pytest.raises(TypeError):  # keyword only
-        with APyFixedAccumulatorContext(5, 2):
-            pass
-
-
 class TestCastContext:
     """
     This test class doesn't test if cast itself works,