Skip to content

Commit 48d2f42

Browse files
authored
Merge pull request #214 from SwayamInSync/scanfunc-fromstr
2 parents 5505950 + ae6c27d commit 48d2f42

File tree

2 files changed

+258
-1
lines changed

2 files changed

+258
-1
lines changed

quaddtype/numpy_quaddtype/src/dtype.c

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <Python.h>
22
#include <sleef.h>
33
#include <sleefquad.h>
4+
#include <ctype.h>
45

56
#define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API
67
#define PY_UFUNC_UNIQUE_SYMBOL QuadPrecType_UFUNC_API
@@ -319,6 +320,80 @@ quadprec_fill(void *buffer, npy_intp length, void *arr_)
319320
return 0;
320321
}
321322

323+
static int
324+
quadprec_scanfunc(FILE *fp, void *dptr, char *ignore, PyArray_Descr *descr_generic)
325+
{
326+
QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)descr_generic;
327+
char buffer[512];
328+
int ch;
329+
size_t i = 0;
330+
331+
/* Skip whitespace */
332+
while ((ch = fgetc(fp)) != EOF && isspace(ch)) {
333+
/* continue */
334+
}
335+
336+
if (ch == EOF) {
337+
return EOF; /* Return EOF when end of file is reached */
338+
}
339+
340+
/* Read characters until we hit whitespace or EOF */
341+
buffer[i++] = (char)ch;
342+
while (i < sizeof(buffer) - 1) {
343+
ch = fgetc(fp);
344+
if (ch == EOF || isspace(ch)) {
345+
if (ch != EOF) {
346+
ungetc(ch, fp); /* Put back the whitespace for separator handling */
347+
}
348+
break;
349+
}
350+
buffer[i++] = (char)ch;
351+
}
352+
buffer[i] = '\0';
353+
354+
/* Convert string to quad precision */
355+
char *endptr;
356+
if (descr->backend == BACKEND_SLEEF) {
357+
Sleef_quad val = Sleef_strtoq(buffer, &endptr);
358+
if (endptr == buffer) {
359+
return 0; /* Return 0 on parse error (no items read) */
360+
}
361+
*(Sleef_quad *)dptr = val;
362+
}
363+
else {
364+
long double val = strtold(buffer, &endptr);
365+
if (endptr == buffer) {
366+
return 0; /* Return 0 on parse error (no items read) */
367+
}
368+
*(long double *)dptr = val;
369+
}
370+
371+
return 1; /* Return 1 on success (1 item read) */
372+
}
373+
374+
static int
375+
quadprec_fromstr(char *s, void *dptr, char **endptr, PyArray_Descr *descr_generic)
376+
{
377+
QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)descr_generic;
378+
379+
if (descr->backend == BACKEND_SLEEF) {
380+
Sleef_quad val = Sleef_strtoq(s, endptr);
381+
if (*endptr == s) {
382+
return -1;
383+
}
384+
*(Sleef_quad *)dptr = val;
385+
}
386+
else {
387+
long double val = strtold(s, endptr);
388+
if (*endptr == s) {
389+
return -1;
390+
}
391+
*(long double *)dptr = val;
392+
}
393+
394+
return 0;
395+
}
396+
322397
static PyType_Slot QuadPrecDType_Slots[] = {
323398
{NPY_DT_ensure_canonical, &ensure_canonical},
324399
{NPY_DT_common_instance, &common_instance},
@@ -329,6 +404,8 @@ static PyType_Slot QuadPrecDType_Slots[] = {
329404
{NPY_DT_default_descr, &quadprec_default_descr},
330405
{NPY_DT_get_constant, &quadprec_get_constant},
331406
{NPY_DT_PyArray_ArrFuncs_fill, &quadprec_fill},
407+
{NPY_DT_PyArray_ArrFuncs_scanfunc, &quadprec_scanfunc},
408+
{NPY_DT_PyArray_ArrFuncs_fromstr, &quadprec_fromstr},
332409
{0, NULL}};
333410

334411
static PyObject *

quaddtype/tests/test_quaddtype.py

Lines changed: 181 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3327,4 +3327,184 @@ def test_imag_real(value):
33273327
assert a.imag == QuadPrecision(0.0), "Imaginary part should be zero"
33283328
return
33293329
assert a.real == a, "Real part mismatch"
3330-
assert a.imag == QuadPrecision(0.0), "Imaginary part should be zero"
3330+
assert a.imag == QuadPrecision(0.0), "Imaginary part should be zero"
3331+
3332+
3333+
class TestStringParsing:
3334+
"""Test suite for string parsing functionality (fromstr and scanfunc)."""
3335+
3336+
def test_fromstring_simple(self):
3337+
"""Test np.fromstring with simple values."""
3338+
result = np.fromstring("1.5 2.5 3.5", sep=" ", dtype=QuadPrecDType(backend='sleef'))
3339+
expected = np.array([1.5, 2.5, 3.5], dtype=QuadPrecDType(backend='sleef'))
3340+
np.testing.assert_array_equal(result, expected)
3341+
3342+
def test_fromstring_high_precision(self):
3343+
"""Test np.fromstring preserves high precision values."""
3344+
# Create a high-precision value
3345+
finfo = np.finfo(QuadPrecision)
3346+
val = 1.0 + finfo.eps
3347+
val_str = str(val)
3348+
3349+
# Parse it back
3350+
result = np.fromstring(val_str, sep=" ", dtype=QuadPrecDType(backend='sleef'))
3351+
expected = np.array([val], dtype=QuadPrecDType(backend='sleef'))
3352+
3353+
# Should maintain precision
3354+
assert result[0] == expected[0], "High precision value not preserved"
3355+
3356+
def test_fromstring_multiple_values(self):
3357+
"""Test np.fromstring with multiple values."""
3358+
s = " 1.0 2.0 3.0 4.0 5.0"
3359+
result = np.fromstring(s, sep=" ", dtype=QuadPrecDType(backend='sleef'))
3360+
expected = np.array([1.0, 2.0, 3.0, 4.0, 5.0], dtype=QuadPrecDType(backend='sleef'))
3361+
np.testing.assert_array_equal(result, expected)
3362+
3363+
def test_fromstring_newline_separator(self):
3364+
"""Test np.fromstring with newline separator."""
3365+
s = "1.5\n2.5\n3.5"
3366+
result = np.fromstring(s, sep="\n", dtype=QuadPrecDType(backend='sleef'))
3367+
expected = np.array([1.5, 2.5, 3.5], dtype=QuadPrecDType(backend='sleef'))
3368+
np.testing.assert_array_equal(result, expected)
3369+
3370+
def test_fromstring_scientific_notation(self):
3371+
"""Test np.fromstring with scientific notation."""
3372+
s = "1.23e-10 4.56e20"
3373+
result = np.fromstring(s, sep=" ", dtype=QuadPrecDType(backend='sleef'))
3374+
expected = np.array([1.23e-10, 4.56e20], dtype=QuadPrecDType(backend='sleef'))
3375+
np.testing.assert_array_almost_equal(result, expected)
3376+
3377+
def test_fromstring_negative_values(self):
3378+
"""Test np.fromstring with negative values."""
3379+
s = "-1.5 -2.5 -3.5"
3380+
result = np.fromstring(s, sep=" ", dtype=QuadPrecDType(backend='sleef'))
3381+
expected = np.array([-1.5, -2.5, -3.5], dtype=QuadPrecDType(backend='sleef'))
3382+
np.testing.assert_array_equal(result, expected)
3383+
3384+
3385+
class TestFileIO:
3386+
"""Test suite for file I/O functionality (scanfunc)."""
3387+
3388+
def test_fromfile_simple(self):
3389+
"""Test np.fromfile with simple values."""
3390+
import tempfile
3391+
import os
3392+
3393+
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
3394+
f.write("1.5\n2.5\n3.5")
3395+
fname = f.name
3396+
3397+
try:
3398+
result = np.fromfile(fname, sep="\n", dtype=QuadPrecDType(backend='sleef'))
3399+
expected = np.array([1.5, 2.5, 3.5], dtype=QuadPrecDType(backend='sleef'))
3400+
np.testing.assert_array_equal(result, expected)
3401+
finally:
3402+
os.unlink(fname)
3403+
3404+
def test_fromfile_space_separator(self):
3405+
"""Test np.fromfile with space separator."""
3406+
import tempfile
3407+
import os
3408+
3409+
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
3410+
f.write("1.0 2.0 3.0 4.0 5.0")
3411+
fname = f.name
3412+
3413+
try:
3414+
result = np.fromfile(fname, sep=" ", dtype=QuadPrecDType(backend='sleef'))
3415+
expected = np.array([1.0, 2.0, 3.0, 4.0, 5.0], dtype=QuadPrecDType(backend='sleef'))
3416+
np.testing.assert_array_equal(result, expected)
3417+
finally:
3418+
os.unlink(fname)
3419+
3420+
def test_tofile_fromfile_roundtrip(self):
3421+
"""Test that tofile/fromfile roundtrips correctly."""
3422+
import tempfile
3423+
import os
3424+
3425+
original = np.array([1.5, 2.5, 3.5, 4.5], dtype=QuadPrecDType(backend='sleef'))
3426+
3427+
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
3428+
fname = f.name
3429+
3430+
try:
3431+
# Write to file
3432+
original.tofile(fname, sep=" ")
3433+
3434+
# Read back
3435+
result = np.fromfile(fname, sep=" ", dtype=QuadPrecDType(backend='sleef'))
3436+
3437+
np.testing.assert_array_equal(result, original)
3438+
finally:
3439+
os.unlink(fname)
3440+
3441+
def test_fromfile_high_precision(self):
3442+
"""Test np.fromfile preserves high precision values."""
3443+
import tempfile
3444+
import os
3445+
3446+
# Create a high-precision value
3447+
finfo = np.finfo(QuadPrecision)
3448+
val = 1.0 + finfo.eps
3449+
expected = np.array([val, val, val], dtype=QuadPrecDType(backend='sleef'))
3450+
3451+
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
3452+
for v in expected:
3453+
f.write(str(v) + '\n')
3454+
fname = f.name
3455+
3456+
try:
3457+
result = np.fromfile(fname, sep="\n", dtype=QuadPrecDType(backend='sleef'))
3458+
3459+
# Check each value maintains precision
3460+
for i in range(len(expected)):
3461+
assert result[i] == expected[i], f"High precision value {i} not preserved"
3462+
finally:
3463+
os.unlink(fname)
3464+
3465+
def test_fromfile_no_trailing_newline(self):
3466+
"""Test np.fromfile handles files without trailing newline."""
3467+
import tempfile
3468+
import os
3469+
3470+
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
3471+
# Write without trailing newline
3472+
f.write("1.5\n2.5\n3.5")
3473+
fname = f.name
3474+
3475+
try:
3476+
result = np.fromfile(fname, sep="\n", dtype=QuadPrecDType(backend='sleef'))
3477+
expected = np.array([1.5, 2.5, 3.5], dtype=QuadPrecDType(backend='sleef'))
3478+
np.testing.assert_array_equal(result, expected)
3479+
finally:
3480+
os.unlink(fname)
3481+
3482+
def test_fromfile_empty_file(self):
3483+
"""Test np.fromfile with empty file."""
3484+
import tempfile
3485+
import os
3486+
3487+
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
3488+
fname = f.name
3489+
3490+
try:
3491+
result = np.fromfile(fname, sep="\n", dtype=QuadPrecDType(backend='sleef'))
3492+
assert len(result) == 0, "Empty file should produce empty array"
3493+
finally:
3494+
os.unlink(fname)
3495+
3496+
def test_fromfile_single_value(self):
3497+
"""Test np.fromfile with single value."""
3498+
import tempfile
3499+
import os
3500+
3501+
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
3502+
f.write("42.0")
3503+
fname = f.name
3504+
3505+
try:
3506+
result = np.fromfile(fname, sep=" ", dtype=QuadPrecDType(backend='sleef'))
3507+
expected = np.array([42.0], dtype=QuadPrecDType(backend='sleef'))
3508+
np.testing.assert_array_equal(result, expected)
3509+
finally:
3510+
os.unlink(fname)

0 commit comments

Comments
 (0)