Skip to content

Commit cdfe02c

Browse files
committed
Merge branch 'main' into same-value
2 parents fe04b3b + 248b18c commit cdfe02c

8 files changed

Lines changed: 118 additions & 121 deletions

File tree

.github/workflows/big_endian.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ jobs:
6666
sudo apt install -y ninja-build gcc-${TOOLCHAIN_NAME} g++-${TOOLCHAIN_NAME} gfortran-${TOOLCHAIN_NAME}
6767
6868
- name: Cache docker container
69-
uses: actions/cache@v4
69+
uses: actions/cache@v5
7070
id: container-cache
7171
with:
7272
path: ~/docker_${{ matrix.BUILD_PROP[1] }}

.github/workflows/build_wheels.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
python -m cibuildwheel --output-dir wheelhouse
6262
working-directory: ./quaddtype
6363

64-
- uses: actions/upload-artifact@v5
64+
- uses: actions/upload-artifact@v6
6565
with:
6666
path: ./quaddtype/wheelhouse/*.whl
6767
name: wheels-linux
@@ -125,7 +125,7 @@ jobs:
125125
python -m cibuildwheel --output-dir wheelhouse
126126
working-directory: ./quaddtype
127127

128-
- uses: actions/upload-artifact@v5
128+
- uses: actions/upload-artifact@v6
129129
with:
130130
path: ./quaddtype/wheelhouse/*.whl
131131
name: wheels-${{ matrix.os }}
@@ -185,7 +185,7 @@ jobs:
185185
if (-not (Test-Path wheelhouse/*.whl)) { throw "Wheel was not created" }
186186
working-directory: ./quaddtype
187187

188-
- uses: actions/upload-artifact@v5
188+
- uses: actions/upload-artifact@v6
189189
with:
190190
path: ./quaddtype/wheelhouse/*.whl
191191
name: wheels-windows-${{ matrix.architecture }}
@@ -222,7 +222,7 @@ jobs:
222222
working-directory: ./quaddtype
223223

224224
- name: Upload SDist artifact
225-
uses: actions/upload-artifact@v5
225+
uses: actions/upload-artifact@v6
226226
with:
227227
name: sdist
228228
path: ./quaddtype/dist/*.tar.gz
@@ -248,7 +248,7 @@ jobs:
248248

249249
steps:
250250
- name: Download all workflow run artifacts
251-
uses: actions/download-artifact@v6
251+
uses: actions/download-artifact@v7
252252
with:
253253
pattern: "*"
254254
path: dist

.github/workflows/typecheck.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ jobs:
2222
steps:
2323
- uses: actions/checkout@v6
2424

25-
- uses: astral-sh/setup-uv@v7.1.4
25+
- uses: astral-sh/setup-uv@v7.1.6
2626
with:
2727
activate-environment: true
28-
python-version: "3.11"
28+
python-version: "3.12"
2929

3030
- name: install
3131
working-directory: quaddtype

quaddtype/numpy_quaddtype/_quaddtype_main.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ class QuadPrecision(np.floating[_128Bit]):
129129
@override
130130
def __add__(self, other: _IntoQuad, /) -> Self: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
131131
@override
132-
def __radd__(self, other: _IntoQuad, /) -> Self: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
132+
def __radd__(self, other: _IntoQuad, /) -> Self: ... # type: ignore[override, misc] # pyright: ignore[reportIncompatibleMethodOverride]
133133
@override
134134
def __sub__(self, other: _IntoQuad, /) -> Self: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
135135
@override

quaddtype/numpy_quaddtype/src/dtype.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -354,11 +354,11 @@ quadprec_scanfunc(FILE *fp, void *dptr, char *ignore, PyArray_Descr *descr_gener
354354
}
355355
buffer[i] = '\0';
356356

357-
/* Convert string to quad precision */
357+
/* Convert string to quad precision (supports inf/nan) */
358358
char *endptr;
359359
quad_value val;
360-
int err = cstring_to_quad(buffer, descr->backend, &val, &endptr, true);
361-
if (err < 0) {
360+
int err = NumPyOS_ascii_strtoq(buffer, descr->backend, &val, &endptr);
361+
if (err < 0 || *endptr != '\0') {
362362
return 0; /* Return 0 on parse error (no items read) */
363363
}
364364
if (descr->backend == BACKEND_SLEEF) {
@@ -376,7 +376,7 @@ quadprec_fromstr(char *s, void *dptr, char **endptr, PyArray_Descr *descr_generi
376376
{
377377
QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)descr_generic;
378378
quad_value val;
379-
int err = cstring_to_quad(s, descr->backend, &val, endptr, false);
379+
int err = NumPyOS_ascii_strtoq(s, descr->backend, &val, endptr);
380380
if (err < 0) {
381381
return -1;
382382
}

quaddtype/numpy_quaddtype/src/ops.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ typedef Sleef_quad (*unary_op_quad_def)(const Sleef_quad *);
1313
// Unary Quad operations with 2 outputs (for modf, frexp)
1414
typedef void (*unary_op_2out_quad_def)(const Sleef_quad *, Sleef_quad *, Sleef_quad *);
1515

16-
[[maybe_unused]] static Sleef_quad
16+
static inline Sleef_quad
1717
quad_negative(const Sleef_quad *op)
1818
{
1919
return Sleef_negq1(*op);
2020
}
2121

22-
[[maybe_unused]] static Sleef_quad
22+
static inline Sleef_quad
2323
quad_positive(const Sleef_quad *op)
2424
{
2525
return *op;

quaddtype/numpy_quaddtype/src/utilities.c

Lines changed: 103 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,105 @@ ascii_strncasecmp(const char *s1, const char *s2, size_t n)
6262
return 0;
6363
}
6464

65+
/*
66+
* Internal helper: Parse numeric string to quad-precision.
67+
* Assumes no leading whitespace and no inf/nan (caller handles those).
68+
* The 'start' parameter points to where the original string started (for endptr on error).
69+
*/
70+
static int
71+
cstring_to_quad_internal(const char *str, const char *start, QuadBackendType backend,
72+
quad_value *out_value, char **endptr)
73+
{
74+
if (backend == BACKEND_SLEEF) {
75+
// SLEEF 4.0's Sleef_strtoq doesn't properly set endptr to indicate
76+
// where parsing stopped. We need to manually validate and track the parse position.
77+
78+
const char *p = str;
79+
80+
// Handle optional sign
81+
if (*p == '+' || *p == '-') {
82+
p++;
83+
}
84+
85+
// Must have at least one digit or decimal point followed by digit
86+
int has_digits = 0;
87+
88+
// Parse integer part
89+
while (ascii_isdigit(*p)) {
90+
has_digits = 1;
91+
p++;
92+
}
93+
94+
// Parse decimal point and fractional part
95+
if (*p == '.') {
96+
p++;
97+
while (ascii_isdigit(*p)) {
98+
has_digits = 1;
99+
p++;
100+
}
101+
}
102+
103+
// Must have at least one digit somewhere
104+
if (!has_digits) {
105+
if (endptr) *endptr = (char *)start;
106+
return -1;
107+
}
108+
109+
// Parse optional exponent
110+
if (*p == 'e' || *p == 'E') {
111+
const char *exp_start = p;
112+
p++;
113+
114+
// Optional sign in exponent
115+
if (*p == '+' || *p == '-') {
116+
p++;
117+
}
118+
119+
// Must have at least one digit in exponent
120+
if (!ascii_isdigit(*p)) {
121+
// Invalid exponent, backtrack
122+
p = exp_start;
123+
} else {
124+
while (ascii_isdigit(*p)) {
125+
p++;
126+
}
127+
}
128+
}
129+
130+
// Now p points to where valid parsing ends
131+
// Create a null-terminated substring for SLEEF
132+
size_t len = p - str;
133+
char *temp = (char *)malloc(len + 1);
134+
if (!temp) {
135+
if (endptr) *endptr = (char *)start;
136+
return -1;
137+
}
138+
memcpy(temp, str, len);
139+
temp[len] = '\0';
140+
141+
// Call Sleef_strtoq with the bounded string
142+
char *sleef_endptr;
143+
out_value->sleef_value = Sleef_strtoq(temp, &sleef_endptr);
144+
free(temp);
145+
146+
// Set endptr to our calculated position
147+
if (endptr) {
148+
*endptr = (char *)p;
149+
}
150+
151+
} else {
152+
out_value->longdouble_value = strtold(str, endptr);
153+
}
154+
155+
if (endptr && *endptr == str) {
156+
// Nothing was parsed - set endptr to original start
157+
*endptr = (char *)start;
158+
return -1;
159+
}
160+
161+
return 0; // success
162+
}
163+
65164
/*
66165
* NumPyOS_ascii_strtoq:
67166
*
@@ -71,7 +170,7 @@ ascii_strncasecmp(const char *s1, const char *s2, size_t n)
71170
* This function:
72171
* - Skips leading whitespace
73172
* - Recognizes inf/nan case-insensitively with optional signs and payloads
74-
* - Delegates to cstring_to_quad for numeric parsing
173+
* - Parses numeric values
75174
*
76175
* Returns:
77176
* 0 on success
@@ -153,110 +252,9 @@ NumPyOS_ascii_strtoq(const char *s, QuadBackendType backend, quad_value *out_val
153252
return 0;
154253
}
155254

156-
// For numeric values, delegate to cstring_to_quad
157-
// Pass the original string position (after whitespace, includes sign if present)
158-
return cstring_to_quad(s, backend, out_value, endptr, false);
159-
}
160-
161-
int cstring_to_quad(const char *str, QuadBackendType backend, quad_value *out_value,
162-
char **endptr, bool require_full_parse)
163-
{
164-
if(backend == BACKEND_SLEEF) {
165-
// SLEEF 4.0's Sleef_strtoq doesn't properly set endptr to indicate
166-
// where parsing stopped. It always sets endptr to the end of the string.
167-
// We need to manually validate and track the parse position.
168-
169-
const char *p = str;
170-
171-
// Skip leading whitespace
172-
while (ascii_isspace(*p)) {
173-
p++;
174-
}
175-
176-
// Handle optional sign
177-
if (*p == '+' || *p == '-') {
178-
p++;
179-
}
180-
181-
// Must have at least one digit or decimal point followed by digit
182-
int has_digits = 0;
183-
184-
// Parse integer part
185-
while (ascii_isdigit(*p)) {
186-
has_digits = 1;
187-
p++;
188-
}
189-
190-
// Parse decimal point and fractional part
191-
if (*p == '.') {
192-
p++;
193-
while (ascii_isdigit(*p)) {
194-
has_digits = 1;
195-
p++;
196-
}
197-
}
198-
199-
// Must have at least one digit somewhere
200-
if (!has_digits) {
201-
if (endptr) *endptr = (char *)str;
202-
return -1;
203-
}
204-
205-
// Parse optional exponent
206-
if (*p == 'e' || *p == 'E') {
207-
const char *exp_start = p;
208-
p++;
209-
210-
// Optional sign in exponent
211-
if (*p == '+' || *p == '-') {
212-
p++;
213-
}
214-
215-
// Must have at least one digit in exponent
216-
if (!ascii_isdigit(*p)) {
217-
// Invalid exponent, backtrack
218-
p = exp_start;
219-
} else {
220-
while (ascii_isdigit(*p)) {
221-
p++;
222-
}
223-
}
224-
}
225-
226-
// Now p points to where valid parsing ends
227-
// SLEEF 4.0's Sleef_strtoq has a bug where it doesn't properly stop at whitespace
228-
// or other delimiters. We need to create a null-terminated substring.
229-
size_t len = p - str;
230-
char *temp = (char *)malloc(len + 1);
231-
if (!temp) {
232-
if (endptr) *endptr = (char *)str;
233-
return -1;
234-
}
235-
memcpy(temp, str, len);
236-
temp[len] = '\0';
237-
238-
// Call Sleef_strtoq with the bounded string
239-
char *sleef_endptr;
240-
out_value->sleef_value = Sleef_strtoq(temp, &sleef_endptr);
241-
free(temp);
242-
243-
// Set endptr to our calculated position
244-
if (endptr) {
245-
*endptr = (char *)p;
246-
}
247-
248-
} else {
249-
out_value->longdouble_value = strtold(str, endptr);
250-
}
251-
252-
if(endptr && *endptr == str)
253-
return -1; // parse error - nothing was parsed
254-
255-
// If full parse is required
256-
if(require_full_parse && endptr && **endptr != '\0')
257-
return -1; // parse error - characters remain to be converted
258-
259-
return 0; // success
255+
// For numeric values, parse starting from 's' (includes sign if present)
256+
// The sign is part of the number, not handled separately like inf/nan
257+
return cstring_to_quad_internal(s, s, backend, out_value, endptr);
260258
}
261259

262260
// Helper function: Convert quad_value to Sleef_quad for Dragon4

quaddtype/numpy_quaddtype/src/utilities.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ extern "C" {
1010
#include <sleefquad.h>
1111
#include <stdbool.h>
1212

13-
int cstring_to_quad(const char *str, QuadBackendType backend, quad_value *out_value, char **endptr, bool require_full_parse);
1413
int ascii_isspace(int c);
1514
int ascii_isalpha(char c);
1615
int ascii_isdigit(char c);

0 commit comments

Comments
 (0)