Skip to content

Commit 5f04fe1

Browse files
authored
Merge pull request #244 from SwayamInSync/strdtype
2 parents 248b18c + d752f7c commit 5f04fe1

File tree

4 files changed

+340
-20
lines changed

4 files changed

+340
-20
lines changed

quaddtype/numpy_quaddtype/src/casts.cpp

Lines changed: 233 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ extern "C" {
2727
#include "dragon4.h"
2828
#include "ops.hpp"
2929

30-
#define NUM_CASTS 38 // 17 to_casts + 17 from_casts + 1 quad_to_quad + 1 void_to_quad
30+
#define NUM_CASTS 40 // 18 to_casts + 18 from_casts + 1 quad_to_quad + 1 void_to_quad
3131
#define QUAD_STR_WIDTH 50 // 42 is enough for scientific notation float128, just keeping some buffer
3232

3333
static NPY_CASTING
@@ -360,13 +360,44 @@ quad_to_string_adaptive(Sleef_quad *sleef_val, npy_intp unicode_size_chars)
360360
if (pos_len <= unicode_size_chars) {
361361
return positional_str; // Keep the positional string
362362
}
363-
else {
364-
Py_DECREF(positional_str);
365-
// Use scientific notation with full precision
366-
return Dragon4_Scientific_QuadDType(sleef_val, DigitMode_Unique,
367-
SLEEF_QUAD_DECIMAL_DIG, 0, 1,
368-
TrimMode_LeaveOneZero, 1, 2);
363+
Py_DECREF(positional_str);
364+
// Use scientific notation with full precision
365+
return Dragon4_Scientific_QuadDType(sleef_val, DigitMode_Unique,
366+
SLEEF_QUAD_DECIMAL_DIG, 0, 1,
367+
TrimMode_LeaveOneZero, 1, 2);
368+
}
369+
370+
static inline const char *
371+
quad_to_string_adaptive_cstr(Sleef_quad *sleef_val, npy_intp unicode_size_chars)
372+
{
373+
// Try positional format first to see if it would fit
374+
const char* positional_str = Dragon4_Positional_QuadDType_CStr(
375+
sleef_val, DigitMode_Unique, CutoffMode_TotalLength, SLEEF_QUAD_DECIMAL_DIG, 0, 1,
376+
TrimMode_LeaveOneZero, 1, 0);
377+
378+
if (positional_str == NULL) {
379+
PyErr_SetString(PyExc_RuntimeError, "Float formatting failed");
380+
return NULL;
381+
}
382+
383+
// no need to scan full, only checking if its longer
384+
npy_intp pos_len = strnlen(positional_str, unicode_size_chars + 1);
385+
386+
// If positional format fits, use it; otherwise use scientific notation
387+
if (pos_len <= unicode_size_chars) {
388+
return positional_str; // Keep the positional string
369389
}
390+
391+
// Use scientific notation with full precision
392+
const char *scientific_str = Dragon4_Scientific_QuadDType_CStr(sleef_val, DigitMode_Unique,
393+
SLEEF_QUAD_DECIMAL_DIG, 0, 1,
394+
TrimMode_LeaveOneZero, 1, 2);
395+
if (scientific_str == NULL) {
396+
PyErr_SetString(PyExc_RuntimeError, "Float formatting failed");
397+
return NULL;
398+
}
399+
return scientific_str;
400+
370401
}
371402

372403
template <bool Aligned>
@@ -605,6 +636,163 @@ quad_to_bytes_loop(PyArrayMethod_Context *context, char *const data[],
605636
return 0;
606637
}
607638

639+
// StringDType to QuadDType casting
640+
static NPY_CASTING
641+
stringdtype_to_quad_resolve_descriptors(PyObject *NPY_UNUSED(self), PyArray_DTypeMeta *dtypes[2],
642+
PyArray_Descr *given_descrs[2], PyArray_Descr *loop_descrs[2],
643+
npy_intp *view_offset)
644+
{
645+
if (given_descrs[1] == NULL) {
646+
loop_descrs[1] = (PyArray_Descr *)new_quaddtype_instance(BACKEND_SLEEF);
647+
if (loop_descrs[1] == nullptr) {
648+
return (NPY_CASTING)-1;
649+
}
650+
}
651+
else {
652+
Py_INCREF(given_descrs[1]);
653+
loop_descrs[1] = given_descrs[1];
654+
}
655+
656+
Py_INCREF(given_descrs[0]);
657+
loop_descrs[0] = given_descrs[0];
658+
659+
return NPY_UNSAFE_CASTING;
660+
}
661+
662+
// Note: StringDType elements are always aligned, so Aligned template parameter
663+
// is kept for API consistency but both versions use the same logic
664+
template <bool Aligned>
665+
static int
666+
stringdtype_to_quad_strided_loop(PyArrayMethod_Context *context, char *const data[],
667+
npy_intp const dimensions[], npy_intp const strides[],
668+
void *NPY_UNUSED(auxdata))
669+
{
670+
npy_intp N = dimensions[0];
671+
char *in_ptr = data[0];
672+
char *out_ptr = data[1];
673+
npy_intp in_stride = strides[0];
674+
npy_intp out_stride = strides[1];
675+
676+
PyArray_Descr *const *descrs = context->descriptors;
677+
PyArray_StringDTypeObject *str_descr = (PyArray_StringDTypeObject *)descrs[0];
678+
QuadPrecDTypeObject *descr_out = (QuadPrecDTypeObject *)descrs[1];
679+
QuadBackendType backend = descr_out->backend;
680+
681+
npy_string_allocator *allocator = NpyString_acquire_allocator(str_descr);
682+
683+
while (N--) {
684+
const npy_packed_static_string *ps = (npy_packed_static_string *)in_ptr;
685+
npy_static_string s = {0, NULL};
686+
int is_null = NpyString_load(allocator, ps, &s);
687+
688+
if (is_null == -1) {
689+
NpyString_release_allocator(allocator);
690+
PyErr_SetString(PyExc_MemoryError, "Failed to load string in StringDType to Quad cast");
691+
return -1;
692+
}
693+
else if (is_null) {
694+
// Handle null string - use the default string if available, otherwise error
695+
if (str_descr->has_string_na || str_descr->default_string.buf != NULL) {
696+
s = str_descr->default_string;
697+
}
698+
else {
699+
NpyString_release_allocator(allocator);
700+
PyErr_SetString(PyExc_ValueError, "Cannot convert null string to QuadPrecision");
701+
return -1;
702+
}
703+
}
704+
705+
quad_value out_val;
706+
if (bytes_to_quad_convert(s.buf, s.size, backend, &out_val) < 0) {
707+
NpyString_release_allocator(allocator);
708+
return -1;
709+
}
710+
711+
store_quad<Aligned>(out_ptr, out_val, backend);
712+
713+
in_ptr += in_stride;
714+
out_ptr += out_stride;
715+
}
716+
717+
NpyString_release_allocator(allocator);
718+
return 0;
719+
}
720+
721+
// QuadDType to StringDType casting
722+
static NPY_CASTING
723+
quad_to_stringdtype_resolve_descriptors(PyObject *NPY_UNUSED(self), PyArray_DTypeMeta *dtypes[2],
724+
PyArray_Descr *given_descrs[2], PyArray_Descr *loop_descrs[2],
725+
npy_intp *view_offset)
726+
{
727+
if (given_descrs[1] == NULL) {
728+
// Default StringDType() already has coerce=True
729+
loop_descrs[1] = (PyArray_Descr *)PyObject_CallNoArgs(
730+
(PyObject *)&PyArray_StringDType);
731+
if (loop_descrs[1] == NULL) {
732+
return (NPY_CASTING)-1;
733+
}
734+
}
735+
else {
736+
Py_INCREF(given_descrs[1]);
737+
loop_descrs[1] = given_descrs[1];
738+
}
739+
740+
Py_INCREF(given_descrs[0]);
741+
loop_descrs[0] = given_descrs[0];
742+
743+
return NPY_SAFE_CASTING;
744+
}
745+
746+
// Note: StringDType elements are always aligned, so Aligned template parameter
747+
// is kept for API consistency but both versions use the same logic
748+
template <bool Aligned>
749+
static int
750+
quad_to_stringdtype_strided_loop(PyArrayMethod_Context *context, char *const data[],
751+
npy_intp const dimensions[], npy_intp const strides[],
752+
void *NPY_UNUSED(auxdata))
753+
{
754+
npy_intp N = dimensions[0];
755+
char *in_ptr = data[0];
756+
char *out_ptr = data[1];
757+
npy_intp in_stride = strides[0];
758+
npy_intp out_stride = strides[1];
759+
760+
PyArray_Descr *const *descrs = context->descriptors;
761+
QuadPrecDTypeObject *descr_in = (QuadPrecDTypeObject *)descrs[0];
762+
PyArray_StringDTypeObject *str_descr = (PyArray_StringDTypeObject *)descrs[1];
763+
QuadBackendType backend = descr_in->backend;
764+
765+
npy_string_allocator *allocator = NpyString_acquire_allocator(str_descr);
766+
767+
while (N--) {
768+
quad_value in_val = load_quad<Aligned>(in_ptr, backend);
769+
Sleef_quad sleef_val = quad_to_sleef_quad(&in_val, backend);
770+
771+
// Get string representation with adaptive notation
772+
// Use a large buffer size to allow for full precision
773+
const char *str_buf = quad_to_string_adaptive_cstr(&sleef_val, QUAD_STR_WIDTH);
774+
if (str_buf == NULL) {
775+
NpyString_release_allocator(allocator);
776+
return -1;
777+
}
778+
779+
Py_ssize_t str_size = strnlen(str_buf, QUAD_STR_WIDTH);
780+
781+
npy_packed_static_string *out_ps = (npy_packed_static_string *)out_ptr;
782+
if (NpyString_pack(allocator, out_ps, str_buf, (size_t)str_size) < 0) {
783+
NpyString_release_allocator(allocator);
784+
PyErr_SetString(PyExc_MemoryError, "Failed to pack string in Quad to StringDType cast");
785+
return -1;
786+
}
787+
788+
in_ptr += in_stride;
789+
out_ptr += out_stride;
790+
}
791+
792+
NpyString_release_allocator(allocator);
793+
return 0;
794+
}
795+
608796
// Tag dispatching to ensure npy_bool/npy_ubyte and npy_half/npy_ushort do not alias in templates
609797
// see e.g. https://stackoverflow.com/q/32522279
610798
struct spec_npy_bool {};
@@ -1395,6 +1583,44 @@ init_casts_internal(void)
13951583
};
13961584
add_spec(quad_to_bytes_spec);
13971585

1586+
// StringDType to QuadPrecision cast
1587+
PyArray_DTypeMeta **stringdtype_to_quad_dtypes = new PyArray_DTypeMeta *[2]{&PyArray_StringDType, &QuadPrecDType};
1588+
PyType_Slot *stringdtype_to_quad_slots = new PyType_Slot[4]{
1589+
{NPY_METH_resolve_descriptors, (void *)&stringdtype_to_quad_resolve_descriptors},
1590+
{NPY_METH_strided_loop, (void *)&stringdtype_to_quad_strided_loop<true>},
1591+
{NPY_METH_unaligned_strided_loop, (void *)&stringdtype_to_quad_strided_loop<false>},
1592+
{0, nullptr}};
1593+
1594+
PyArrayMethod_Spec *stringdtype_to_quad_spec = new PyArrayMethod_Spec{
1595+
.name = "cast_StringDType_to_QuadPrec",
1596+
.nin = 1,
1597+
.nout = 1,
1598+
.casting = NPY_UNSAFE_CASTING,
1599+
.flags = static_cast<NPY_ARRAYMETHOD_FLAGS>(NPY_METH_SUPPORTS_UNALIGNED | NPY_METH_REQUIRES_PYAPI),
1600+
.dtypes = stringdtype_to_quad_dtypes,
1601+
.slots = stringdtype_to_quad_slots,
1602+
};
1603+
add_spec(stringdtype_to_quad_spec);
1604+
1605+
// QuadPrecision to StringDType cast
1606+
PyArray_DTypeMeta **quad_to_stringdtype_dtypes = new PyArray_DTypeMeta *[2]{&QuadPrecDType, &PyArray_StringDType};
1607+
PyType_Slot *quad_to_stringdtype_slots = new PyType_Slot[4]{
1608+
{NPY_METH_resolve_descriptors, (void *)&quad_to_stringdtype_resolve_descriptors},
1609+
{NPY_METH_strided_loop, (void *)&quad_to_stringdtype_strided_loop<true>},
1610+
{NPY_METH_unaligned_strided_loop, (void *)&quad_to_stringdtype_strided_loop<false>},
1611+
{0, nullptr}};
1612+
1613+
PyArrayMethod_Spec *quad_to_stringdtype_spec = new PyArrayMethod_Spec{
1614+
.name = "cast_QuadPrec_to_StringDType",
1615+
.nin = 1,
1616+
.nout = 1,
1617+
.casting = NPY_SAFE_CASTING,
1618+
.flags = static_cast<NPY_ARRAYMETHOD_FLAGS>(NPY_METH_SUPPORTS_UNALIGNED | NPY_METH_REQUIRES_PYAPI),
1619+
.dtypes = quad_to_stringdtype_dtypes,
1620+
.slots = quad_to_stringdtype_slots,
1621+
};
1622+
add_spec(quad_to_stringdtype_spec);
1623+
13981624
specs[spec_count] = nullptr;
13991625
return specs;
14001626
}

quaddtype/numpy_quaddtype/src/dragon4.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1954,6 +1954,15 @@ Dragon4_Positional_QuadDType_opt(Sleef_quad *val, Dragon4_Options *opt)
19541954
return ret;
19551955
}
19561956

1957+
const char *
1958+
Dragon4_Positional_QuadDType_opt_cstr(Sleef_quad *val, Dragon4_Options *opt)
1959+
{;
1960+
if (Dragon4_PrintFloat_Sleef_quad(val, opt) < 0) {
1961+
return NULL;
1962+
}
1963+
return _bigint_static.repr;
1964+
}
1965+
19571966
PyObject *
19581967
Dragon4_Positional_QuadDType(Sleef_quad *val, DigitMode digit_mode, CutoffMode cutoff_mode,
19591968
int precision, int min_digits, int sign, TrimMode trim, int pad_left,
@@ -1975,6 +1984,27 @@ Dragon4_Positional_QuadDType(Sleef_quad *val, DigitMode digit_mode, CutoffMode c
19751984
return Dragon4_Positional_QuadDType_opt(val, &opt);
19761985
}
19771986

1987+
const char *
1988+
Dragon4_Positional_QuadDType_CStr(Sleef_quad *val, DigitMode digit_mode, CutoffMode cutoff_mode,
1989+
int precision, int min_digits, int sign, TrimMode trim, int pad_left,
1990+
int pad_right)
1991+
{
1992+
Dragon4_Options opt;
1993+
1994+
opt.scientific = 0;
1995+
opt.digit_mode = digit_mode;
1996+
opt.cutoff_mode = cutoff_mode;
1997+
opt.precision = precision;
1998+
opt.min_digits = min_digits;
1999+
opt.sign = sign;
2000+
opt.trim_mode = trim;
2001+
opt.digits_left = pad_left;
2002+
opt.digits_right = pad_right;
2003+
opt.exp_digits = -1;
2004+
2005+
return Dragon4_Positional_QuadDType_opt_cstr(val, &opt);
2006+
}
2007+
19782008
PyObject *
19792009
Dragon4_Scientific_QuadDType_opt(Sleef_quad *val, Dragon4_Options *opt)
19802010
{
@@ -1986,6 +2016,15 @@ Dragon4_Scientific_QuadDType_opt(Sleef_quad *val, Dragon4_Options *opt)
19862016
return ret;
19872017
}
19882018

2019+
const char *
2020+
Dragon4_Scientific_QuadDType_opt_cstr(Sleef_quad *val, Dragon4_Options *opt)
2021+
{
2022+
if (Dragon4_PrintFloat_Sleef_quad(val, opt) < 0) {
2023+
return NULL;
2024+
}
2025+
return _bigint_static.repr;
2026+
}
2027+
19892028
PyObject *
19902029
Dragon4_Scientific_QuadDType(Sleef_quad *val, DigitMode digit_mode, int precision, int min_digits,
19912030
int sign, TrimMode trim, int pad_left, int exp_digits)
@@ -2006,6 +2045,26 @@ Dragon4_Scientific_QuadDType(Sleef_quad *val, DigitMode digit_mode, int precisio
20062045
return Dragon4_Scientific_QuadDType_opt(val, &opt);
20072046
}
20082047

2048+
const char *
2049+
Dragon4_Scientific_QuadDType_CStr(Sleef_quad *val, DigitMode digit_mode, int precision, int min_digits,
2050+
int sign, TrimMode trim, int pad_left, int exp_digits)
2051+
{
2052+
Dragon4_Options opt;
2053+
2054+
opt.scientific = 1;
2055+
opt.digit_mode = digit_mode;
2056+
opt.cutoff_mode = CutoffMode_TotalLength;
2057+
opt.precision = precision;
2058+
opt.min_digits = min_digits;
2059+
opt.sign = sign;
2060+
opt.trim_mode = trim;
2061+
opt.digits_left = pad_left;
2062+
opt.digits_right = -1;
2063+
opt.exp_digits = exp_digits;
2064+
2065+
return Dragon4_Scientific_QuadDType_opt_cstr(val, &opt);
2066+
}
2067+
20092068
PyObject *
20102069
Dragon4_Positional(PyObject *obj, DigitMode digit_mode, CutoffMode cutoff_mode, int precision,
20112070
int min_digits, int sign, TrimMode trim, int pad_left, int pad_right)

quaddtype/numpy_quaddtype/src/dragon4.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,18 @@ PyObject *Dragon4_Positional_QuadDType(Sleef_quad *val, DigitMode digit_mode,
5151
CutoffMode cutoff_mode, int precision, int min_digits,
5252
int sign, TrimMode trim, int pad_left, int pad_right);
5353

54+
const char *Dragon4_Positional_QuadDType_CStr(Sleef_quad *val, DigitMode digit_mode,
55+
CutoffMode cutoff_mode, int precision, int min_digits,
56+
int sign, TrimMode trim, int pad_left, int pad_right);
57+
5458
PyObject *Dragon4_Scientific_QuadDType(Sleef_quad *val, DigitMode digit_mode,
5559
int precision, int min_digits, int sign, TrimMode trim,
5660
int pad_left, int exp_digits);
5761

62+
const char *Dragon4_Scientific_QuadDType_CStr(Sleef_quad *val, DigitMode digit_mode,
63+
int precision, int min_digits, int sign, TrimMode trim,
64+
int pad_left, int exp_digits);
65+
5866
PyObject *Dragon4_Positional(PyObject *obj, DigitMode digit_mode,
5967
CutoffMode cutoff_mode, int precision, int min_digits,
6068
int sign, TrimMode trim, int pad_left, int pad_right);

0 commit comments

Comments
 (0)