Repository: numpy/numpy
Pull request: BUG: Casting bool_ to float16 
Fixes #19514

For the conversion `bool` -->  `float`  --> `half` made the `@is_half2@ && @is_bool1@` case which was not present.

Problem statement:
Casting `bool_` to `float16` reads internal representation
<!-- Please describe the issue in detail here, and fill in the fields below -->
Casting `np.bool_` array to `np.float16` reads internal representation.  For example, `np.float16(1) + np.array(42, np.int8).view(bool)` results 43.0.  Other types are fine.
```
>>> {ty: np.dtype(ty).type(1) + np.array(42, np.int8).view(bool) for ty in "?bhilBHILefdgFDGOm"}
{'?': True, 'b': 2, 'h': 2, 'i': 2, 'l': 2, 'B': 2, 'H': 2, 'I': 2, 'L': 2, 'e': 43.0, 'f': 2.0, 'd': 2.0, 'g': 2.0, 'F': (2+0j), 'D': (2+0j), 'G': (2+0j), 'O': 2, 'm': numpy.timedelta64(2)}
```

Values casted from `bool` are not necessarily 0 or 1 in C/C++, but it looks nice for NumPy to fix them for all dtypes.

### Reproducing code example:

<!-- A short code example that reproduces the problem/missing feature. It should be
self-contained, i.e., possible to run as-is via 'python myproblem.py' -->

```python
import numpy as np
np.testing.assert_equal(np.float16(np.int8([0, 3, -7]).view(bool)), [0, 1, 1])
```

The issue does not seem to apply to `np.bool_` scalars
```
>>> np.float16(np.int8(42).view(bool))
1.0
```
because
```
>>> np.int8(42).view(bool).flags.owndata
True
>>> np.int8(42).view(bool).view(np.int8)
1
```

### Error message:

```
AssertionError:
Arrays are not equal

Mismatched elements: 2 / 3 (66.7%)
Max absolute difference: 248.
Max relative difference: 248.
 x: array([  0.,   3., 249.], dtype=float16)
 y: array([0, 1, 1])
```

### NumPy/Python version information:

<!-- Output from 'import sys, numpy; print(numpy.__version__, sys.version)' -->
```
>>> import sys, numpy; print(numpy.__version__, sys.version)
1.21.0 3.9.6 (default, Jul  5 2021, 10:03:05)
[GCC 7.5.0]
```

Hints from issue discussion:
I expect the logic here needs to be slightly cleaned up, in case anyone wants to take a look:

https://github.com/numpy/numpy/blob/b5de1ceb1f1707d0539446b3d8883f3f8f80cb69/numpy/core/src/multiarray/lowlevel_strided_loops.c.src#L818-L832

hey @seberg , Can I solve this issue if this is not yet solved. Thanks

I think @yashasvimisra2798 just started looking into a day or two back so probably should give her a chance to finish.  Otherwise, of course though!
Yeah @seberg and @Bhavay192 I am working on this and will update soon on this. 
Hey @seberg, is there any other issue which can be beginner friendly for me to contribute??

Commit message: add test

Patch to review:
diff --git a/numpy/core/src/multiarray/lowlevel_strided_loops.c.src b/numpy/core/src/multiarray/lowlevel_strided_loops.c.src
index e533e4932f3e..e38873746d96 100644
--- a/numpy/core/src/multiarray/lowlevel_strided_loops.c.src
+++ b/numpy/core/src/multiarray/lowlevel_strided_loops.c.src
@@ -819,6 +819,10 @@ NPY_NO_EXPORT PyArrayMethod_StridedLoop *
 #    define _CONVERT_FN(x) npy_floatbits_to_halfbits(x)
 #  elif @is_double1@
 #    define _CONVERT_FN(x) npy_doublebits_to_halfbits(x)
+#  elif @is_half1@
+#    define _CONVERT_FN(x) (x)
+#  elif @is_bool1@
+#    define _CONVERT_FN(x) npy_float_to_half((float)(x!=0))
 #  else
 #    define _CONVERT_FN(x) npy_float_to_half((float)x)
 #  endif
diff --git a/numpy/core/tests/test_casting_unittests.py b/numpy/core/tests/test_casting_unittests.py
index 3f67f1832650..5874c53a3e2e 100644
--- a/numpy/core/tests/test_casting_unittests.py
+++ b/numpy/core/tests/test_casting_unittests.py
@@ -190,6 +190,12 @@ def get_data(self, dtype1, dtype2):
 
         return arr1, arr2, values
 
+    res = np.array([0, 3, -7], dtype=np.int8).view(bool)
+    expected = [0, 1, 1]
+
+    def conversion(self, res, expected):
+        assert_array_equal(res, expected)
+
     def get_data_variation(self, arr1, arr2, aligned=True, contig=True):
         """
         Returns a copy of arr1 that may be non-contiguous or unaligned, and a


Code review comments:
diff_hunk: @@ -190,6 +190,12 @@ def get_data(self, dtype1, dtype2):
 
         return arr1, arr2, values
 
+    res = np.array([0, 3, -7], dtype=np.int8).view(bool)
+    expected = [0, 1, 1]
+
+    def conversion(self, res, expected):
+        assert_array_equal(res, expected)
+
path: numpy/core/tests/test_casting_unittests.py
text: @user1:
Is this run at all? Please look at the other tests to see how this should be done. The test function should start with `def test_ ...` and the only parameter in your case should be `self`. You can make sure your test is run by checking that your test name appears in the output of
```
python runtests.py -t numpy/core/tests/test_casting_unittests.py -vv 
```

@author:
@user1 I am working on it. Thanks for pointing this out, it's my first time writing test cases.

@author:
@user1 kindly review the test again, I have added the fix.