Skip to content
Merged
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
77 changes: 77 additions & 0 deletions quaddtype/numpy_quaddtype/src/dtype.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <Python.h>
#include <sleef.h>
#include <sleefquad.h>
#include <ctype.h>

#define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API
#define PY_UFUNC_UNIQUE_SYMBOL QuadPrecType_UFUNC_API
Expand Down Expand Up @@ -319,6 +320,80 @@ quadprec_fill(void *buffer, npy_intp length, void *arr_)
return 0;
}

static int
quadprec_scanfunc(FILE *fp, void *dptr, char *ignore, PyArray_Descr *descr_generic)
{
QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)descr_generic;
char buffer[512];
int ch;
size_t i = 0;

/* Skip whitespace */
while ((ch = fgetc(fp)) != EOF && isspace(ch)) {
/* continue */
}

if (ch == EOF) {
return EOF; /* Return EOF when end of file is reached */
}

/* Read characters until we hit whitespace or EOF */
buffer[i++] = (char)ch;
while (i < sizeof(buffer) - 1) {
ch = fgetc(fp);
if (ch == EOF || isspace(ch)) {
if (ch != EOF) {
ungetc(ch, fp); /* Put back the whitespace for separator handling */
}
break;
}
buffer[i++] = (char)ch;
}
buffer[i] = '\0';

/* Convert string to quad precision */
char *endptr;
if (descr->backend == BACKEND_SLEEF) {
Sleef_quad val = Sleef_strtoq(buffer, &endptr);
if (endptr == buffer) {
return 0; /* Return 0 on parse error (no items read) */
}
*(Sleef_quad *)dptr = val;
}
else {
long double val = strtold(buffer, &endptr);
if (endptr == buffer) {
return 0; /* Return 0 on parse error (no items read) */
}
*(long double *)dptr = val;
}

return 1; /* Return 1 on success (1 item read) */
}

static int
quadprec_fromstr(char *s, void *dptr, char **endptr, PyArray_Descr *descr_generic)
{
QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)descr_generic;

if (descr->backend == BACKEND_SLEEF) {
Sleef_quad val = Sleef_strtoq(s, endptr);
if (*endptr == s) {
return -1;
}
*(Sleef_quad *)dptr = val;
}
else {
long double val = strtold(s, endptr);
if (*endptr == s) {
return -1;
}
*(long double *)dptr = val;
}

return 0;
}

static PyType_Slot QuadPrecDType_Slots[] = {
{NPY_DT_ensure_canonical, &ensure_canonical},
{NPY_DT_common_instance, &common_instance},
Expand All @@ -329,6 +404,8 @@ static PyType_Slot QuadPrecDType_Slots[] = {
{NPY_DT_default_descr, &quadprec_default_descr},
{NPY_DT_get_constant, &quadprec_get_constant},
{NPY_DT_PyArray_ArrFuncs_fill, &quadprec_fill},
{NPY_DT_PyArray_ArrFuncs_scanfunc, &quadprec_scanfunc},
{NPY_DT_PyArray_ArrFuncs_fromstr, &quadprec_fromstr},
{0, NULL}};

static PyObject *
Expand Down
182 changes: 181 additions & 1 deletion quaddtype/tests/test_quaddtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -3327,4 +3327,184 @@ def test_imag_real(value):
assert a.imag == QuadPrecision(0.0), "Imaginary part should be zero"
return
assert a.real == a, "Real part mismatch"
assert a.imag == QuadPrecision(0.0), "Imaginary part should be zero"
assert a.imag == QuadPrecision(0.0), "Imaginary part should be zero"


class TestStringParsing:
"""Test suite for string parsing functionality (fromstr and scanfunc)."""

def test_fromstring_simple(self):
"""Test np.fromstring with simple values."""
result = np.fromstring("1.5 2.5 3.5", sep=" ", dtype=QuadPrecDType(backend='sleef'))
expected = np.array([1.5, 2.5, 3.5], dtype=QuadPrecDType(backend='sleef'))
np.testing.assert_array_equal(result, expected)

def test_fromstring_high_precision(self):
"""Test np.fromstring preserves high precision values."""
# Create a high-precision value
finfo = np.finfo(QuadPrecision)
val = 1.0 + finfo.eps
val_str = str(val)

# Parse it back
result = np.fromstring(val_str, sep=" ", dtype=QuadPrecDType(backend='sleef'))
expected = np.array([val], dtype=QuadPrecDType(backend='sleef'))

# Should maintain precision
assert result[0] == expected[0], "High precision value not preserved"

def test_fromstring_multiple_values(self):
"""Test np.fromstring with multiple values."""
s = " 1.0 2.0 3.0 4.0 5.0"
result = np.fromstring(s, sep=" ", dtype=QuadPrecDType(backend='sleef'))
expected = np.array([1.0, 2.0, 3.0, 4.0, 5.0], dtype=QuadPrecDType(backend='sleef'))
np.testing.assert_array_equal(result, expected)

def test_fromstring_newline_separator(self):
"""Test np.fromstring with newline separator."""
s = "1.5\n2.5\n3.5"
result = np.fromstring(s, sep="\n", dtype=QuadPrecDType(backend='sleef'))
expected = np.array([1.5, 2.5, 3.5], dtype=QuadPrecDType(backend='sleef'))
np.testing.assert_array_equal(result, expected)

def test_fromstring_scientific_notation(self):
"""Test np.fromstring with scientific notation."""
s = "1.23e-10 4.56e20"
result = np.fromstring(s, sep=" ", dtype=QuadPrecDType(backend='sleef'))
expected = np.array([1.23e-10, 4.56e20], dtype=QuadPrecDType(backend='sleef'))
np.testing.assert_array_almost_equal(result, expected)

def test_fromstring_negative_values(self):
"""Test np.fromstring with negative values."""
s = "-1.5 -2.5 -3.5"
result = np.fromstring(s, sep=" ", dtype=QuadPrecDType(backend='sleef'))
expected = np.array([-1.5, -2.5, -3.5], dtype=QuadPrecDType(backend='sleef'))
np.testing.assert_array_equal(result, expected)


class TestFileIO:
"""Test suite for file I/O functionality (scanfunc)."""

def test_fromfile_simple(self):
"""Test np.fromfile with simple values."""
import tempfile
import os

with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
f.write("1.5\n2.5\n3.5")
fname = f.name

try:
result = np.fromfile(fname, sep="\n", dtype=QuadPrecDType(backend='sleef'))
expected = np.array([1.5, 2.5, 3.5], dtype=QuadPrecDType(backend='sleef'))
np.testing.assert_array_equal(result, expected)
finally:
os.unlink(fname)

def test_fromfile_space_separator(self):
"""Test np.fromfile with space separator."""
import tempfile
import os

with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
f.write("1.0 2.0 3.0 4.0 5.0")
fname = f.name

try:
result = np.fromfile(fname, sep=" ", dtype=QuadPrecDType(backend='sleef'))
expected = np.array([1.0, 2.0, 3.0, 4.0, 5.0], dtype=QuadPrecDType(backend='sleef'))
np.testing.assert_array_equal(result, expected)
finally:
os.unlink(fname)

def test_tofile_fromfile_roundtrip(self):
"""Test that tofile/fromfile roundtrips correctly."""
import tempfile
import os

original = np.array([1.5, 2.5, 3.5, 4.5], dtype=QuadPrecDType(backend='sleef'))

with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
fname = f.name

try:
# Write to file
original.tofile(fname, sep=" ")

# Read back
result = np.fromfile(fname, sep=" ", dtype=QuadPrecDType(backend='sleef'))

np.testing.assert_array_equal(result, original)
finally:
os.unlink(fname)

def test_fromfile_high_precision(self):
"""Test np.fromfile preserves high precision values."""
import tempfile
import os

# Create a high-precision value
finfo = np.finfo(QuadPrecision)
val = 1.0 + finfo.eps
expected = np.array([val, val, val], dtype=QuadPrecDType(backend='sleef'))

with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
for v in expected:
f.write(str(v) + '\n')
fname = f.name

try:
result = np.fromfile(fname, sep="\n", dtype=QuadPrecDType(backend='sleef'))

# Check each value maintains precision
for i in range(len(expected)):
assert result[i] == expected[i], f"High precision value {i} not preserved"
finally:
os.unlink(fname)

def test_fromfile_no_trailing_newline(self):
"""Test np.fromfile handles files without trailing newline."""
import tempfile
import os

with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
# Write without trailing newline
f.write("1.5\n2.5\n3.5")
fname = f.name

try:
result = np.fromfile(fname, sep="\n", dtype=QuadPrecDType(backend='sleef'))
expected = np.array([1.5, 2.5, 3.5], dtype=QuadPrecDType(backend='sleef'))
np.testing.assert_array_equal(result, expected)
finally:
os.unlink(fname)

def test_fromfile_empty_file(self):
"""Test np.fromfile with empty file."""
import tempfile
import os

with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
fname = f.name

try:
result = np.fromfile(fname, sep="\n", dtype=QuadPrecDType(backend='sleef'))
assert len(result) == 0, "Empty file should produce empty array"
finally:
os.unlink(fname)

def test_fromfile_single_value(self):
"""Test np.fromfile with single value."""
import tempfile
import os

with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
f.write("42.0")
fname = f.name

try:
result = np.fromfile(fname, sep=" ", dtype=QuadPrecDType(backend='sleef'))
expected = np.array([42.0], dtype=QuadPrecDType(backend='sleef'))
np.testing.assert_array_equal(result, expected)
finally:
os.unlink(fname)