Repository: apytypes/apytypes (language: python)

Issue:
APyFixedArray conversion resulting in wrong number while APyFixed remains correct
I have an example where creating an APyFixedArray from numpy array seems to produce the wrong result, while APyFixed remains correct. Could you take a look?

```python
from apytypes import APyFixed, APyFixedArray
import numpy as np

fl = 1.2124003311558797e-16
npa = np.array([fl])

fx = APyFixed.from_float(fl, 1, 11)
fl2fxa = APyFixedArray.from_float([fl], 1, 11)
npa2fxa = APyFixedArray.from_array(npa, 1, 11)
npa2fx = APyFixed.from_float(npa[0], 1, 11)

print(
    f"{fl=}\n",
    f"{npa=}\n",
    f"{fx=}\n",
    f"{fl2fxa=}\n",
    f"{npa2fxa=} ({npa2fxa[0].to_bits()})\n",
    f"{npa2fx=} ({npa2fx.to_bits()})\n",
)
```

Notice below how the numpy array to APyFixedArray (**npa2fxa**) diverges from numpy array to APyFixed (**npa2fx**):
```
fl=1.2124003311558797e-16
npa=array([1.21240033e-16])
fx=APyFixed(0, bits=12, int_bits=1)
fl2fxa=APyFixedArray([0], shape=(1,), bits=12, int_bits=1)
npa2fxa=APyFixedArray([990], shape=(1,), bits=12, int_bits=1) (990)
npa2fx=APyFixed(0, bits=12, int_bits=1) (0)
```

Test on Python 3.9.17, both on both APyTypes v0.1.0 and the latest commit (3f6f90bb6e81e08166b8d262baae17f911dd044b).
APyFixedArray conversion resulting in wrong number while APyFixed remains correct
I have an example where creating an APyFixedArray from numpy array seems to produce the wrong result, while APyFixed remains correct. Could you take a look?

```python
from apytypes import APyFixed, APyFixedArray
import numpy as np

fl = 1.2124003311558797e-16
npa = np.array([fl])

fx = APyFixed.from_float(fl, 1, 11)
fl2fxa = APyFixedArray.from_float([fl], 1, 11)
npa2fxa = APyFixedArray.from_array(npa, 1, 11)
npa2fx = APyFixed.from_float(npa[0], 1, 11)

print(
    f"{fl=}\n",
    f"{npa=}\n",
    f"{fx=}\n",
    f"{fl2fxa=}\n",
    f"{npa2fxa=} ({npa2fxa[0].to_bits()})\n",
    f"{npa2fx=} ({npa2fx.to_bits()})\n",
)
```

Notice below how the numpy array to APyFixedArray (**npa2fxa**) diverges from numpy array to APyFixed (**npa2fx**):
```
fl=1.2124003311558797e-16
npa=array([1.21240033e-16])
fx=APyFixed(0, bits=12, int_bits=1)
fl2fxa=APyFixedArray([0], shape=(1,), bits=12, int_bits=1)
npa2fxa=APyFixedArray([990], shape=(1,), bits=12, int_bits=1) (990)
npa2fx=APyFixed(0, bits=12, int_bits=1) (0)
```

Test on Python 3.9.17, both on both APyTypes v0.1.0 and the latest commit (3f6f90bb6e81e08166b8d262baae17f911dd044b).


Discussion / hints:
Hi @rrbtmp,

Thanks for submitting this issue. I will look into it.
Hi @rrbtmp,

Thanks for submitting this issue. I will look into it.

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 02e985689..bff5bf717 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,7 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   - `APyFixedArray.broadcast_to()`
   - `APyFloatArray.broadcast_to()`
   - Automatic broadcasting on array arithmetic
-- Support arithmetic between APyTypes classes and Python's built-in floats and ints.
+- Support arithmetic between APyTypes classes and Python's built-in floats and
+  ints.
 - Support for automatic stub generation added.
 - Docstrings are made compatible with
   [doctest](https://docs.python.org/3/library/doctest.html), which is
@@ -46,11 +47,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - 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 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 in comparison between floating-point infinity and fixed-point numbers.
 - Start uploading source distributions of APyTypes to PyPI on releases.
+- Fixed bug where `APyFixedArray.from_array()` initialized the wrong stored
+  value from floating-point values that are too small for its representation.
+  (#487)
 
 ### Removed
 
diff --git a/src/apyfixed_util.cc b/src/apyfixed_util.cc
index 73929f361..80b29ccbf 100644
--- a/src/apyfixed_util.cc
+++ b/src/apyfixed_util.cc
@@ -27,14 +27,20 @@ mp_limb_t get_data_from_double(double value, int bits, int frac_bits, int shift_
     // correct position
     auto left_shift_amnt = exp + frac_bits - 52 - 1023;
     if (left_shift_amnt >= 0) {
-        man <<= left_shift_amnt;
+        if (left_shift_amnt < 64) {
+            man <<= left_shift_amnt;
+        } else {
+            man = 0;
+        }
     } else {
         auto right_shift_amount = -left_shift_amnt;
-        if (right_shift_amount - 1 < 64) {
-            // Round the value
+        if (right_shift_amount < 55) {
+            // Round the value and shift
             man += mp_limb_t(1) << (right_shift_amount - 1);
+            man >>= right_shift_amount;
+        } else {
+            man = 0;
         }
-        man >>= right_shift_amount;
     }
     // Adjust result from sign
     if (sign_of_double(value)) {
diff --git a/src/apyfixedarray.cc b/src/apyfixedarray.cc
index c3b815f39..66a9c476a 100644
--- a/src/apyfixedarray.cc
+++ b/src/apyfixedarray.cc
@@ -32,7 +32,6 @@ namespace nb = nanobind;
 #include "array_utils.h"
 #include "broadcast.h"
 #include "python_util.h"
-#include <iostream>
 
 #include <fmt/format.h>
 
diff --git a/src/array_utils.h b/src/array_utils.h
index 9a758c0b2..63956ae64 100644
--- a/src/array_utils.h
+++ b/src/array_utils.h
@@ -5,7 +5,6 @@
 #include "broadcast.h"
 #include "nanobind/ndarray.h"
 #include <cstddef>
-#include <iostream>
 #include <nanobind/nanobind.h>
 #include <ostream>
 #include <set>


Test patch:
diff --git a/lib/test/apyfixed/test_constructors.py b/lib/test/apyfixed/test_constructors.py
index 617c84a96..faf2ad053 100644
--- a/lib/test/apyfixed/test_constructors.py
+++ b/lib/test/apyfixed/test_constructors.py
@@ -142,3 +142,17 @@ def test_incorrect_double_construction():
 
     with pytest.raises(ValueError, match="Cannot convert inf to fixed-point"):
         APyFixed.from_float(float("inf"), 4, 4)
+
+
+def test_issue_487():
+    """
+    Test for GitHub issue #487
+    https://github.com/apytypes/apytypes/issues/487
+    """
+    np = pytest.importorskip("numpy")
+    fl = 1.2124003311558797e-16
+    npa = np.array([fl])
+    fx = APyFixed.from_float(fl, 1, 11)
+    npa2fx = APyFixed.from_float(npa[0], 1, 11)
+    assert fx.is_identical(APyFixed(0, 1, 11))
+    assert npa2fx.is_identical(APyFixed(0, 1, 11))
diff --git a/lib/test/apyfixedarray/test_constructors.py b/lib/test/apyfixedarray/test_constructors.py
index 36ca1abe4..dd9f931a5 100644
--- a/lib/test/apyfixedarray/test_constructors.py
+++ b/lib/test/apyfixedarray/test_constructors.py
@@ -194,3 +194,42 @@ def test_c_striding():
     assert APyFixedArray(a.T, int_bits=10, frac_bits=0).is_identical(
         APyFixedArray([[1, 4], [2, 5], [3, 6]], int_bits=10, frac_bits=0)
     )
+
+
+def test_issue_487():
+    """
+    Test for GitHub issue #487
+    https://github.com/apytypes/apytypes/issues/487
+    """
+    np = pytest.importorskip("numpy")
+
+    fl = 1.2124003311558797e-16
+    fl2fxa = APyFixedArray.from_float([fl], int_bits=1, frac_bits=11)
+    npa2fxa = APyFixedArray.from_array(np.array([fl]), int_bits=1, frac_bits=11)
+    assert fl2fxa.is_identical(APyFixedArray([0], int_bits=1, frac_bits=11))
+    assert npa2fxa.is_identical(APyFixedArray([0], int_bits=1, frac_bits=11))
+
+    fl = 1.25e16
+    fl2fxa = APyFixedArray.from_float([fl], int_bits=60, frac_bits=0)
+    npa2fxa = APyFixedArray.from_array(np.array([fl]), int_bits=60, frac_bits=0)
+    assert fl2fxa.to_numpy() == np.array([fl])
+    assert npa2fxa.to_numpy() == np.array([fl])
+
+    fl = 1.25e160
+    fl2fxa = APyFixedArray.from_float([fl], int_bits=60, frac_bits=0)
+    npa2fxa = APyFixedArray.from_array(np.array([fl]), int_bits=60, frac_bits=0)
+    assert fl2fxa.to_numpy() == np.array([0])
+    assert npa2fxa.to_numpy() == np.array([0])
+
+
+def test_fizz_fuzz():
+    """
+    Tests discovered through some fuzzing
+    """
+    np = pytest.importorskip("numpy")
+
+    # 1
+    fl = [4.06997806e-09]
+    a = APyFixedArray.from_float(fl, int_bits=16, frac_bits=16)
+    b = APyFixedArray.from_array(np.array(fl), int_bits=16, frac_bits=16)
+    assert a.is_identical(b)