Skip to content

Commit 74fa409

Browse files
committed
fixing operation behaviours
1 parent b3a7a1e commit 74fa409

File tree

3 files changed

+122
-10
lines changed

3 files changed

+122
-10
lines changed

quaddtype/numpy_quaddtype/src/scalar.c

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,21 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend)
191191
else if (PyUnicode_Check(value)) {
192192
const char *s = PyUnicode_AsUTF8(value);
193193
char *endptr = NULL;
194-
int err = cstring_to_quad(s, backend, &self->value, &endptr, true);
194+
int err = NumPyOS_ascii_strtoq(s, backend, &self->value, &endptr);
195195
if (err < 0) {
196196
PyErr_SetString(PyExc_ValueError, "Unable to parse string to QuadPrecision");
197197
Py_DECREF(self);
198198
return NULL;
199199
}
200+
// Skip trailing whitespace (matches Python's float() behavior)
201+
while (ascii_isspace(*endptr)) {
202+
endptr++;
203+
}
204+
if (*endptr != '\0') {
205+
PyErr_SetString(PyExc_ValueError, "Unable to parse string to QuadPrecision");
206+
Py_DECREF(self);
207+
return NULL;
208+
}
200209
}
201210
else if (PyBytes_Check(value)) {
202211
const char *s = PyBytes_AsString(value);
@@ -205,12 +214,21 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend)
205214
return NULL;
206215
}
207216
char *endptr = NULL;
208-
int err = cstring_to_quad(s, backend, &self->value, &endptr, true);
217+
int err = NumPyOS_ascii_strtoq(s, backend, &self->value, &endptr);
209218
if (err < 0) {
210219
PyErr_SetString(PyExc_ValueError, "Unable to parse bytes to QuadPrecision");
211220
Py_DECREF(self);
212221
return NULL;
213222
}
223+
// Skip trailing whitespace (matches Python's float() behavior)
224+
while (ascii_isspace(*endptr)) {
225+
endptr++;
226+
}
227+
if (*endptr != '\0') {
228+
PyErr_SetString(PyExc_ValueError, "Unable to parse bytes to QuadPrecision");
229+
Py_DECREF(self);
230+
return NULL;
231+
}
214232
}
215233
else if (PyLong_Check(value)) {
216234
return quad_from_py_int(value, backend, self);

quaddtype/numpy_quaddtype/src/utilities.c

Lines changed: 99 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,17 @@ NumPyOS_ascii_strtoq(const char *s, QuadBackendType backend, quad_value *out_val
134134
}
135135
}
136136

137-
// Set NaN value (sign is ignored for NaN)
137+
// Set NaN value with sign preserved
138138
if (backend == BACKEND_SLEEF) {
139-
out_value->sleef_value = QUAD_PRECISION_NAN;
139+
Sleef_quad nan_val = QUAD_PRECISION_NAN;
140+
// Apply sign to NaN (negative NaN has sign bit set)
141+
if (sign < 0) {
142+
nan_val = Sleef_negq1(nan_val);
143+
}
144+
out_value->sleef_value = nan_val;
140145
}
141146
else {
142-
out_value->longdouble_value = nanl("");
147+
out_value->longdouble_value = sign < 0 ? -nanl("") : nanl("");
143148
}
144149

145150
if (endptr) {
@@ -157,15 +162,103 @@ int cstring_to_quad(const char *str, QuadBackendType backend, quad_value *out_va
157162
char **endptr, bool require_full_parse)
158163
{
159164
if(backend == BACKEND_SLEEF) {
160-
out_value->sleef_value = Sleef_strtoq(str, endptr);
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+
// Track start of number (after whitespace)
177+
const char *num_start = p;
178+
179+
// Handle optional sign
180+
if (*p == '+' || *p == '-') {
181+
p++;
182+
}
183+
184+
// Must have at least one digit or decimal point followed by digit
185+
int has_digits = 0;
186+
int has_decimal = 0;
187+
188+
// Parse integer part
189+
while (ascii_isdigit(*p)) {
190+
has_digits = 1;
191+
p++;
192+
}
193+
194+
// Parse decimal point and fractional part
195+
if (*p == '.') {
196+
has_decimal = 1;
197+
p++;
198+
while (ascii_isdigit(*p)) {
199+
has_digits = 1;
200+
p++;
201+
}
202+
}
203+
204+
// Must have at least one digit somewhere
205+
if (!has_digits) {
206+
if (endptr) *endptr = (char *)str;
207+
return -1;
208+
}
209+
210+
// Parse optional exponent
211+
if (*p == 'e' || *p == 'E') {
212+
const char *exp_start = p;
213+
p++;
214+
215+
// Optional sign in exponent
216+
if (*p == '+' || *p == '-') {
217+
p++;
218+
}
219+
220+
// Must have at least one digit in exponent
221+
if (!ascii_isdigit(*p)) {
222+
// Invalid exponent, backtrack
223+
p = exp_start;
224+
} else {
225+
while (ascii_isdigit(*p)) {
226+
p++;
227+
}
228+
}
229+
}
230+
231+
// Now p points to where valid parsing ends
232+
// SLEEF 4.0's Sleef_strtoq has a bug where it doesn't properly stop at whitespace
233+
// or other delimiters. We need to create a null-terminated substring.
234+
size_t len = p - str;
235+
char *temp = (char *)malloc(len + 1);
236+
if (!temp) {
237+
if (endptr) *endptr = (char *)str;
238+
return -1;
239+
}
240+
memcpy(temp, str, len);
241+
temp[len] = '\0';
242+
243+
// Call Sleef_strtoq with the bounded string
244+
char *sleef_endptr;
245+
out_value->sleef_value = Sleef_strtoq(temp, &sleef_endptr);
246+
free(temp);
247+
248+
// Set endptr to our calculated position
249+
if (endptr) {
250+
*endptr = (char *)p;
251+
}
252+
161253
} else {
162254
out_value->longdouble_value = strtold(str, endptr);
163255
}
164-
if(*endptr == str)
256+
257+
if(endptr && *endptr == str)
165258
return -1; // parse error - nothing was parsed
166259

167260
// If full parse is required
168-
if(require_full_parse && **endptr != '\0')
261+
if(require_full_parse && endptr && **endptr != '\0')
169262
return -1; // parse error - characters remain to be converted
170263

171264
return 0; // success

quaddtype/tests/test_quaddtype.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -404,14 +404,15 @@ def test_bytes_backend_consistency(self, backend, bytes_val):
404404
# Leading whitespace is OK (consumed by parser)
405405
(b" 1.0", "1.0"),
406406
(b" 3.14", "3.14"),
407+
# Trailing whitespace is OK (matches Python's float() behavior)
408+
(b"1.0 ", "1.0"),
409+
(b"1.0 ", "1.0"),
407410
])
408411
def test_bytes_whitespace_valid(self, bytes_val, expected_str):
409412
"""Test handling of valid whitespace in bytes input."""
410413
assert QuadPrecision(bytes_val) == QuadPrecision(expected_str)
411414

412415
@pytest.mark.parametrize("invalid_bytes", [
413-
b"1.0 ", # Trailing whitespace
414-
b"1.0 ", # Multiple trailing spaces
415416
b"1 .0", # Internal whitespace
416417
b"1. 0", # Internal whitespace
417418
])

0 commit comments

Comments
 (0)