Skip to content

Commit 8e3eba4

Browse files
committed
to and from bytes cast
1 parent 13d62b1 commit 8e3eba4

File tree

2 files changed

+695
-1
lines changed

2 files changed

+695
-1
lines changed

quaddtype/numpy_quaddtype/src/casts.cpp

Lines changed: 324 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ extern "C" {
1414
#include "numpy/dtype_api.h"
1515
}
1616
#include <cstring>
17+
#include <cstdlib>
1718
#include "sleef.h"
1819
#include "sleefquad.h"
1920

@@ -26,7 +27,7 @@ extern "C" {
2627
#include "dragon4.h"
2728
#include "ops.hpp"
2829

29-
#define NUM_CASTS 36 // 17 to_casts + 17 from_casts + 1 quad_to_quad + 1 void_to_quad
30+
#define NUM_CASTS 38 // 17 to_casts + 17 from_casts + 1 quad_to_quad + 1 void_to_quad
3031
#define QUAD_STR_WIDTH 50 // 42 is enough for scientific notation float128, just keeping some buffer
3132

3233
static NPY_CASTING
@@ -525,6 +526,290 @@ quad_to_unicode_loop_aligned(PyArrayMethod_Context *context, char *const data[],
525526
return 0;
526527
}
527528

529+
// Bytes to QuadDType casting
530+
static NPY_CASTING
531+
bytes_to_quad_resolve_descriptors(PyObject *NPY_UNUSED(self), PyArray_DTypeMeta *dtypes[2],
532+
PyArray_Descr *given_descrs[2], PyArray_Descr *loop_descrs[2],
533+
npy_intp *view_offset)
534+
{
535+
// Bytes dtype doesn't have byte order concerns like Unicode
536+
Py_INCREF(given_descrs[0]);
537+
loop_descrs[0] = given_descrs[0];
538+
539+
if (given_descrs[1] == NULL) {
540+
loop_descrs[1] = (PyArray_Descr *)new_quaddtype_instance(BACKEND_SLEEF);
541+
if (loop_descrs[1] == nullptr) {
542+
Py_DECREF(loop_descrs[0]);
543+
return (NPY_CASTING)-1;
544+
}
545+
}
546+
else {
547+
Py_INCREF(given_descrs[1]);
548+
loop_descrs[1] = given_descrs[1];
549+
}
550+
551+
return NPY_UNSAFE_CASTING;
552+
}
553+
554+
// Helper function: Convert bytes string to quad_value
555+
static inline int
556+
bytes_to_quad_convert(const char *bytes_str, npy_intp bytes_size,
557+
QuadBackendType backend, quad_value *out_val)
558+
{
559+
// Create a null-terminated copy since bytes might not be null-terminated
560+
char *temp_str = (char *)malloc(bytes_size + 1);
561+
if (temp_str == NULL) {
562+
PyErr_NoMemory();
563+
return -1;
564+
}
565+
566+
memcpy(temp_str, bytes_str, bytes_size);
567+
temp_str[bytes_size] = '\0';
568+
569+
// Find the actual end (null byte or first occurrence)
570+
npy_intp actual_len = 0;
571+
while (actual_len < bytes_size && temp_str[actual_len] != '\0') {
572+
actual_len++;
573+
}
574+
temp_str[actual_len] = '\0';
575+
576+
char *endptr;
577+
int err = NumPyOS_ascii_strtoq(temp_str, backend, out_val, &endptr);
578+
579+
if (err < 0) {
580+
PyErr_Format(PyExc_ValueError,
581+
"could not convert bytes to QuadPrecision: np.bytes_(%s)", temp_str);
582+
free(temp_str);
583+
return -1;
584+
}
585+
586+
while (ascii_isspace(*endptr)) {
587+
endptr++;
588+
}
589+
590+
if (*endptr != '\0') {
591+
PyErr_Format(PyExc_ValueError,
592+
"could not convert bytes to QuadPrecision: np.bytes_(%s)", temp_str);
593+
free(temp_str);
594+
return -1;
595+
}
596+
597+
free(temp_str);
598+
return 0;
599+
}
600+
601+
static int
602+
bytes_to_quad_strided_loop_unaligned(PyArrayMethod_Context *context, char *const data[],
603+
npy_intp const dimensions[], npy_intp const strides[],
604+
void *NPY_UNUSED(auxdata))
605+
{
606+
npy_intp N = dimensions[0];
607+
char *in_ptr = data[0];
608+
char *out_ptr = data[1];
609+
npy_intp in_stride = strides[0];
610+
npy_intp out_stride = strides[1];
611+
612+
PyArray_Descr *const *descrs = context->descriptors;
613+
QuadPrecDTypeObject *descr_out = (QuadPrecDTypeObject *)descrs[1];
614+
QuadBackendType backend = descr_out->backend;
615+
616+
npy_intp bytes_size = descrs[0]->elsize;
617+
size_t elem_size = (backend == BACKEND_SLEEF) ? sizeof(Sleef_quad) : sizeof(long double);
618+
619+
while (N--) {
620+
quad_value out_val;
621+
622+
if (bytes_to_quad_convert(in_ptr, bytes_size, backend, &out_val) < 0) {
623+
return -1;
624+
}
625+
626+
memcpy(out_ptr, &out_val, elem_size);
627+
628+
in_ptr += in_stride;
629+
out_ptr += out_stride;
630+
}
631+
632+
return 0;
633+
}
634+
635+
static int
636+
bytes_to_quad_strided_loop_aligned(PyArrayMethod_Context *context, char *const data[],
637+
npy_intp const dimensions[], npy_intp const strides[],
638+
void *NPY_UNUSED(auxdata))
639+
{
640+
npy_intp N = dimensions[0];
641+
char *in_ptr = data[0];
642+
char *out_ptr = data[1];
643+
npy_intp in_stride = strides[0];
644+
npy_intp out_stride = strides[1];
645+
646+
PyArray_Descr *const *descrs = context->descriptors;
647+
QuadPrecDTypeObject *descr_out = (QuadPrecDTypeObject *)descrs[1];
648+
QuadBackendType backend = descr_out->backend;
649+
650+
npy_intp bytes_size = descrs[0]->elsize;
651+
652+
while (N--) {
653+
quad_value out_val;
654+
655+
if (bytes_to_quad_convert(in_ptr, bytes_size, backend, &out_val) < 0) {
656+
return -1;
657+
}
658+
659+
if (backend == BACKEND_SLEEF) {
660+
*(Sleef_quad *)(out_ptr) = out_val.sleef_value;
661+
}
662+
else {
663+
*(long double *)(out_ptr) = out_val.longdouble_value;
664+
}
665+
666+
in_ptr += in_stride;
667+
out_ptr += out_stride;
668+
}
669+
670+
return 0;
671+
}
672+
673+
// QuadDType to bytes
674+
static NPY_CASTING
675+
quad_to_bytes_resolve_descriptors(PyObject *NPY_UNUSED(self), PyArray_DTypeMeta *dtypes[2],
676+
PyArray_Descr *given_descrs[2], PyArray_Descr *loop_descrs[2],
677+
npy_intp *view_offset)
678+
{
679+
npy_intp required_size_bytes = QUAD_STR_WIDTH;
680+
681+
if (given_descrs[1] == NULL) {
682+
PyArray_Descr *new_descr = PyArray_DescrNewFromType(NPY_STRING);
683+
if (new_descr == NULL) {
684+
return (NPY_CASTING)-1;
685+
}
686+
new_descr->elsize = required_size_bytes;
687+
loop_descrs[1] = new_descr;
688+
}
689+
else {
690+
Py_INCREF(given_descrs[1]);
691+
loop_descrs[1] = given_descrs[1];
692+
}
693+
694+
Py_INCREF(given_descrs[0]);
695+
loop_descrs[0] = given_descrs[0];
696+
697+
*view_offset = 0;
698+
699+
// If target descriptor is wide enough, it's a safe cast
700+
if (loop_descrs[1]->elsize >= required_size_bytes) {
701+
return NPY_SAFE_CASTING;
702+
}
703+
return NPY_SAME_KIND_CASTING;
704+
}
705+
706+
// Helper function: Copy string to bytes output buffer
707+
static inline void
708+
copy_string_to_bytes(const char *str, char *out_bytes, npy_intp bytes_size)
709+
{
710+
npy_intp str_len = strlen(str);
711+
712+
npy_intp copy_len = (str_len < bytes_size) ? str_len : bytes_size;
713+
memcpy(out_bytes, str, copy_len);
714+
715+
// Pad remaining space with null bytes
716+
for (npy_intp i = copy_len; i < bytes_size; i++) {
717+
out_bytes[i] = '\0';
718+
}
719+
}
720+
721+
static int
722+
quad_to_bytes_loop_unaligned(PyArrayMethod_Context *context, char *const data[],
723+
npy_intp const dimensions[], npy_intp const strides[],
724+
void *NPY_UNUSED(auxdata))
725+
{
726+
npy_intp N = dimensions[0];
727+
char *in_ptr = data[0];
728+
char *out_ptr = data[1];
729+
npy_intp in_stride = strides[0];
730+
npy_intp out_stride = strides[1];
731+
732+
PyArray_Descr *const *descrs = context->descriptors;
733+
QuadPrecDTypeObject *descr_in = (QuadPrecDTypeObject *)descrs[0];
734+
QuadBackendType backend = descr_in->backend;
735+
736+
npy_intp bytes_size = descrs[1]->elsize;
737+
size_t elem_size = (backend == BACKEND_SLEEF) ? sizeof(Sleef_quad) : sizeof(long double);
738+
739+
while (N--) {
740+
quad_value in_val;
741+
if (backend == BACKEND_SLEEF) {
742+
memcpy(&in_val.sleef_value, in_ptr, sizeof(Sleef_quad));
743+
}
744+
else {
745+
memcpy(&in_val.longdouble_value, in_ptr, sizeof(long double));
746+
}
747+
Sleef_quad sleef_val = quad_to_sleef_quad(&in_val, backend);
748+
PyObject *py_str = quad_to_string_adaptive(&sleef_val, bytes_size);
749+
if (py_str == NULL) {
750+
return -1;
751+
}
752+
const char *temp_str = PyUnicode_AsUTF8(py_str);
753+
if (temp_str == NULL) {
754+
Py_DECREF(py_str);
755+
return -1;
756+
}
757+
758+
copy_string_to_bytes(temp_str, out_ptr, bytes_size);
759+
760+
Py_DECREF(py_str);
761+
762+
in_ptr += in_stride;
763+
out_ptr += out_stride;
764+
}
765+
766+
return 0;
767+
}
768+
769+
static int
770+
quad_to_bytes_loop_aligned(PyArrayMethod_Context *context, char *const data[],
771+
npy_intp const dimensions[], npy_intp const strides[],
772+
void *NPY_UNUSED(auxdata))
773+
{
774+
npy_intp N = dimensions[0];
775+
char *in_ptr = data[0];
776+
char *out_ptr = data[1];
777+
npy_intp in_stride = strides[0];
778+
npy_intp out_stride = strides[1];
779+
780+
PyArray_Descr *const *descrs = context->descriptors;
781+
QuadPrecDTypeObject *descr_in = (QuadPrecDTypeObject *)descrs[0];
782+
QuadBackendType backend = descr_in->backend;
783+
784+
npy_intp bytes_size = descrs[1]->elsize;
785+
786+
while (N--) {
787+
quad_value in_val;
788+
if (backend == BACKEND_SLEEF) {
789+
in_val.sleef_value = *(Sleef_quad *)in_ptr;
790+
}
791+
else {
792+
in_val.longdouble_value = *(long double *)in_ptr;
793+
}
794+
Sleef_quad sleef_val = quad_to_sleef_quad(&in_val, backend);
795+
PyObject *py_str = quad_to_string_adaptive(&sleef_val, bytes_size);
796+
if (py_str == NULL) {
797+
return -1;
798+
}
799+
const char *temp_str = PyUnicode_AsUTF8(py_str);
800+
if (temp_str == NULL) {
801+
Py_DECREF(py_str);
802+
return -1;
803+
}
804+
805+
copy_string_to_bytes(temp_str, out_ptr, bytes_size); Py_DECREF(py_str);
806+
in_ptr += in_stride;
807+
out_ptr += out_stride;
808+
}
809+
810+
return 0;
811+
}
812+
528813
// Tag dispatching to ensure npy_bool/npy_ubyte and npy_half/npy_ushort do not alias in templates
529814
// see e.g. https://stackoverflow.com/q/32522279
530815
struct spec_npy_bool {};
@@ -1277,6 +1562,44 @@ init_casts_internal(void)
12771562
};
12781563
add_spec(quad_to_unicode_spec);
12791564

1565+
// Bytes to QuadPrecision cast
1566+
PyArray_DTypeMeta **bytes_to_quad_dtypes = new PyArray_DTypeMeta *[2]{&PyArray_BytesDType, &QuadPrecDType};
1567+
PyType_Slot *bytes_to_quad_slots = new PyType_Slot[4]{
1568+
{NPY_METH_resolve_descriptors, (void *)&bytes_to_quad_resolve_descriptors},
1569+
{NPY_METH_strided_loop, (void *)&bytes_to_quad_strided_loop_aligned},
1570+
{NPY_METH_unaligned_strided_loop, (void *)&bytes_to_quad_strided_loop_unaligned},
1571+
{0, nullptr}};
1572+
1573+
PyArrayMethod_Spec *bytes_to_quad_spec = new PyArrayMethod_Spec{
1574+
.name = "cast_Bytes_to_QuadPrec",
1575+
.nin = 1,
1576+
.nout = 1,
1577+
.casting = NPY_UNSAFE_CASTING,
1578+
.flags = NPY_METH_SUPPORTS_UNALIGNED,
1579+
.dtypes = bytes_to_quad_dtypes,
1580+
.slots = bytes_to_quad_slots,
1581+
};
1582+
add_spec(bytes_to_quad_spec);
1583+
1584+
// QuadPrecision to Bytes
1585+
PyArray_DTypeMeta **quad_to_bytes_dtypes = new PyArray_DTypeMeta *[2]{&QuadPrecDType, &PyArray_BytesDType};
1586+
PyType_Slot *quad_to_bytes_slots = new PyType_Slot[4]{
1587+
{NPY_METH_resolve_descriptors, (void *)&quad_to_bytes_resolve_descriptors},
1588+
{NPY_METH_strided_loop, (void *)&quad_to_bytes_loop_aligned},
1589+
{NPY_METH_unaligned_strided_loop, (void *)&quad_to_bytes_loop_unaligned},
1590+
{0, nullptr}};
1591+
1592+
PyArrayMethod_Spec *quad_to_bytes_spec = new PyArrayMethod_Spec{
1593+
.name = "cast_QuadPrec_to_Bytes",
1594+
.nin = 1,
1595+
.nout = 1,
1596+
.casting = NPY_UNSAFE_CASTING,
1597+
.flags = NPY_METH_SUPPORTS_UNALIGNED,
1598+
.dtypes = quad_to_bytes_dtypes,
1599+
.slots = quad_to_bytes_slots,
1600+
};
1601+
add_spec(quad_to_bytes_spec);
1602+
12801603
specs[spec_count] = nullptr;
12811604
return specs;
12821605
}

0 commit comments

Comments
 (0)