Skip to content

Commit 9b9aa18

Browse files
committed
merge conf
2 parents 30a24b5 + 650330a commit 9b9aa18

File tree

5 files changed

+99
-14
lines changed

5 files changed

+99
-14
lines changed

.github/dependabot.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: github-actions
4+
directory: /
5+
schedule:
6+
interval: weekly
7+
labels: [dependabot]
8+
commit-message:
9+
prefix: "🤖"
10+
groups:
11+
actions:
12+
patterns: ["*"]

quaddtype/numpy_quaddtype/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
get_quadblas_version
99
)
1010

11+
__version__ = "0.2.0"
12+
1113
__all__ = [
1214
'QuadPrecision', 'QuadPrecDType', 'SleefQuadPrecision', 'LongDoubleQuadPrecision',
1315
'SleefQuadPrecDType', 'LongDoubleQuadPrecDType', 'is_longdouble_128',

quaddtype/numpy_quaddtype/_quaddtype_main.pyi

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@ import numpy as np
44
from typing_extensions import Never, Self, override
55

66
_Backend: TypeAlias = Literal["sleef", "longdouble"]
7-
_IntoQuad: TypeAlias = QuadPrecision | float | str
8-
_CastsQuad: TypeAlias = _IntoQuad | np.floating[Any] | np.integer[Any] | np.bool_
7+
_IntoQuad: TypeAlias = (
8+
QuadPrecision
9+
| float
10+
| str
11+
| np.floating[Any]
12+
| np.integer[Any]
13+
| np.bool_
14+
) # fmt: skip
915

1016
@final
1117
class QuadPrecDType(np.dtype[QuadPrecision]): # type: ignore[misc, type-var] # pyright: ignore[reportGeneralTypeIssues, reportInvalidTypeArguments]
@@ -64,8 +70,15 @@ class QuadPrecDType(np.dtype[QuadPrecision]): # type: ignore[misc, type-var] #
6470
@override
6571
def __getitem__(self, key: Never, /) -> Self: ... # type: ignore[override]
6672

73+
# NOTE: Until `QuadPrecision` will become a subclass of `np.generic`, this class cannot
74+
# be considered "type-safe".
6775
@final
68-
class QuadPrecision: # NOTE: It doesn't inherit from `np.generic` which is type-unsafe
76+
class QuadPrecision:
77+
# NOTE: At runtime this constructor also accepts array-likes, for which it returns
78+
# `np.ndarray` instances with `dtype=QuadPrecDType()`.
79+
# But because of mypy limitations, it is currently impossible to annotate
80+
# constructors that do no return instances of their class (or a subclass thereof).
81+
# See https://github.com/python/mypy/issues/18343#issuecomment-2571784915
6982
def __new__(cls, /, value: _IntoQuad, backend: _Backend = "sleef") -> Self: ...
7083

7184
# Rich comparison operators
@@ -80,16 +93,16 @@ class QuadPrecision: # NOTE: It doesn't inherit from `np.generic` which is type
8093
def __ge__(self, other: _IntoQuad, /) -> bool: ...
8194

8295
# Binary operators
83-
def __add__(self, other: _CastsQuad, /) -> Self: ...
84-
def __radd__(self, other: _CastsQuad, /) -> Self: ...
85-
def __sub__(self, other: _CastsQuad, /) -> Self: ...
86-
def __rsub__(self, other: _CastsQuad, /) -> Self: ...
87-
def __mul__(self, other: _CastsQuad, /) -> Self: ...
88-
def __rmul__(self, other: _CastsQuad, /) -> Self: ...
89-
def __pow__(self, other: _CastsQuad, mod: None = None, /) -> Self: ...
90-
def __rpow__(self, other: _CastsQuad, mod: None = None, /) -> Self: ...
91-
def __truediv__(self, other: _CastsQuad, /) -> Self: ...
92-
def __rtruediv__(self, other: _CastsQuad, /) -> Self: ...
96+
def __add__(self, other: _IntoQuad, /) -> Self: ...
97+
def __radd__(self, other: _IntoQuad, /) -> Self: ...
98+
def __sub__(self, other: _IntoQuad, /) -> Self: ...
99+
def __rsub__(self, other: _IntoQuad, /) -> Self: ...
100+
def __mul__(self, other: _IntoQuad, /) -> Self: ...
101+
def __rmul__(self, other: _IntoQuad, /) -> Self: ...
102+
def __pow__(self, other: _IntoQuad, mod: None = None, /) -> Self: ...
103+
def __rpow__(self, other: _IntoQuad, mod: None = None, /) -> Self: ...
104+
def __truediv__(self, other: _IntoQuad, /) -> Self: ...
105+
def __rtruediv__(self, other: _IntoQuad, /) -> Self: ...
93106

94107
# Unary operators
95108
def __neg__(self, /) -> Self: ...

quaddtype/numpy_quaddtype/src/scalar.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,24 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend)
8787
}
8888
return self;
8989
}
90+
// Try as boolean
91+
else if (PyArray_IsScalar(value, Bool)) {
92+
PyObject *py_int = PyNumber_Long(value);
93+
if (py_int == NULL) {
94+
Py_DECREF(self);
95+
return NULL;
96+
}
97+
long long lval = PyLong_AsLongLong(py_int);
98+
Py_DECREF(py_int);
99+
100+
if (backend == BACKEND_SLEEF) {
101+
self->value.sleef_value = Sleef_cast_from_int64q1(lval);
102+
}
103+
else {
104+
self->value.longdouble_value = (long double)lval;
105+
}
106+
return self;
107+
}
90108
// For other scalar types, fall through to error handling
91109
Py_DECREF(self);
92110
}
@@ -351,6 +369,26 @@ static PyBufferProcs QuadPrecision_as_buffer = {
351369
.bf_releasebuffer = NULL,
352370
};
353371

372+
static PyObject *
373+
QuadPrecision_get_real(QuadPrecisionObject *self, void *closure)
374+
{
375+
Py_INCREF(self);
376+
return (PyObject *)self;
377+
}
378+
379+
static PyObject *
380+
QuadPrecision_get_imag(QuadPrecisionObject *self, void *closure)
381+
{
382+
// For real floating-point types, the imaginary part is always 0
383+
return (PyObject *)QuadPrecision_raw_new(self->backend);
384+
}
385+
386+
static PyGetSetDef QuadPrecision_getset[] = {
387+
{"real", (getter)QuadPrecision_get_real, NULL, "Real part of the scalar", NULL},
388+
{"imag", (getter)QuadPrecision_get_imag, NULL, "Imaginary part of the scalar (always 0 for real types)", NULL},
389+
{NULL} /* Sentinel */
390+
};
391+
354392
PyTypeObject QuadPrecision_Type = {
355393
PyVarObject_HEAD_INIT(NULL, 0).tp_name = "numpy_quaddtype.QuadPrecision",
356394
.tp_basicsize = sizeof(QuadPrecisionObject),
@@ -362,6 +400,7 @@ PyTypeObject QuadPrecision_Type = {
362400
.tp_as_number = &quad_as_scalar,
363401
.tp_as_buffer = &QuadPrecision_as_buffer,
364402
.tp_richcompare = (richcmpfunc)quad_richcompare,
403+
.tp_getset = QuadPrecision_getset,
365404
};
366405

367406
int

quaddtype/tests/test_quaddtype.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,18 @@ def test_create_from_numpy_float_scalars(self):
134134
assert isinstance(result, QuadPrecision)
135135
assert abs(float(result) - 1.5) < 1e-3
136136

137+
def test_create_from_numpy_bool_scalars(self):
138+
"""Test that QuadPrecision can create scalars from numpy boolean types."""
139+
# Test np.bool_(True) converts to 1.0
140+
result = QuadPrecision(np.bool_(True))
141+
assert isinstance(result, QuadPrecision)
142+
assert float(result) == 1.0
143+
144+
# Test np.bool_(False) converts to 0.0
145+
result = QuadPrecision(np.bool_(False))
146+
assert isinstance(result, QuadPrecision)
147+
assert float(result) == 0.0
148+
137149
def test_create_from_zero_dimensional_array(self):
138150
"""Test that QuadPrecision can create from 0-d numpy arrays."""
139151
# 0-d array from scalar
@@ -3076,4 +3088,11 @@ def test_frexp_very_small(self):
30763088
# Verify reconstruction using ldexp (preserves full quad precision)
30773089
reconstructed = np.ldexp(quad_m, int(quad_e))
30783090
assert reconstructed == quad_x, \
3079-
f"Reconstruction failed for small value: {reconstructed} != {quad_x}"
3091+
f"Reconstruction failed for small value: {reconstructed} != {quad_x}"
3092+
# testng buffer
3093+
def test_buffer():
3094+
a = QuadPrecision(1.0)
3095+
buff = a.data
3096+
3097+
reconstructed = np.frombuffer(buff, dtype=QuadPrecDType())[0]
3098+
assert reconstructed == a, "Buffer reconstruction failed"

0 commit comments

Comments
 (0)