Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/csrc/casts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1216,7 +1216,7 @@ inline npy_bool
from_quad<spec_npy_bool>(const quad_value *x, QuadBackendType backend)
{
if (backend == BACKEND_SLEEF) {
return Sleef_cast_to_int64q1(x->sleef_value) != 0;
return !Sleef_icmpeqq1(x->sleef_value, QUAD_PRECISION_ZERO);
}
else {
return x->longdouble_value != 0;
Expand Down
7 changes: 4 additions & 3 deletions src/csrc/scalar_ops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ extern "C" {
#include "ops.hpp"
#include "scalar_ops.h"
#include "quad_common.h"
#include "constants.hpp"

template <unary_op_quad_def sleef_op, unary_op_longdouble_def longdouble_op>
static PyObject *
Expand All @@ -36,14 +37,14 @@ quad_unary_func(QuadPrecisionObject *self)
return (PyObject *)res;
}

PyObject *
int
quad_nonzero(QuadPrecisionObject *self)
{
if (self->backend == BACKEND_SLEEF) {
return PyBool_FromLong(Sleef_icmpneq1(self->value.sleef_value, Sleef_cast_from_int64q1(0)));
return !Sleef_icmpeqq1(self->value.sleef_value, QUAD_PRECISION_ZERO);
}
else {
return PyBool_FromLong(self->value.longdouble_value != 0.0L);
return self->value.longdouble_value != 0.0L;
}
}

Expand Down
60 changes: 60 additions & 0 deletions tests/test_quaddtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -1531,6 +1531,66 @@ def test_unary_logical_not(x):
assert isinstance(quad_result, (bool, np.bool_)), f"Result should be bool, got {type(quad_result)}"


@pytest.mark.parametrize("val", [
# Small positive values that truncate to 0 when cast to int
0.5, 0.1, 0.01, 0.001, 0.0001,
1e-10, 1e-50, 1e-100, 1e-300,
# Small negative values
-0.5, -0.1, -0.01, -0.001, -0.0001,
-1e-10, -1e-50, -1e-100, -1e-300,
# Values just above/below 1
0.9999999999, 1.0000000001,
# Regular non-zero values (sanity check)
1.0, -1.0, 2.0, 100.0, 1e10,
])
def test_bool_cast_small_nonzero_values_are_truthy(val):
"""
Test that small non-zero values correctly cast to True.

This tests for a bug where casting to bool via int truncation
would make small values like 0.5 falsely become False.
"""
quad_val = QuadPrecision(str(val))

# Cast to bool array
bool_result = np.array([quad_val]).astype(bool)[0]
py_bool = bool(quad_val)

# All non-zero values should be True
assert bool_result == True, f"QuadPrecision({val}) should be truthy, got {bool_result}"
assert py_bool == True, f"Python bool(QuadPrecision({val})) should be truthy, got {py_bool}"


@pytest.mark.parametrize("val", [
0.0, -0.0,
])
def test_bool_cast_zero_is_falsy(val):
"""Test that zero values correctly cast to False."""
quad_val = QuadPrecision(str(val))

# Cast to bool array
bool_result = np.array([quad_val]).astype(bool)[0]
py_bool = bool(quad_val)

# Zero values should be False
assert bool_result == False, f"QuadPrecision({val}) should be falsy, got {bool_result}"
assert py_bool == False, f"Python bool(QuadPrecision({val})) should be falsy, got {py_bool}"

def test_bool_cast_array():
"""Test boolean casting on arrays with mixed values."""
# Array with zeros and small non-zero values
values = ["0.0", "0.5", "-0.0", "1e-100", "1.0", "-1e-50"]
quad_arr = np.array([QuadPrecision(v) for v in values])

bool_arr = quad_arr.astype(bool)

# Expected: [False, True, False, True, True, True]
expected = [False, True, False, True, True, True]

for i, (got, exp) in enumerate(zip(bool_arr, expected)):
assert got == exp, f"Index {i} (value={values[i]}): expected {exp}, got {got}"


@pytest.mark.parametrize("op", ["amin", "amax", "nanmin", "nanmax"])
@pytest.mark.parametrize("a", ["3.0", "12.5", "100.0", "0.0", "-0.0", "inf", "-inf", "nan", "-nan"])
@pytest.mark.parametrize("b", ["3.0", "12.5", "100.0", "0.0", "-0.0", "inf", "-inf", "nan", "-nan"])
Expand Down
Loading