diff options
Diffstat (limited to 'numpy')
| -rw-r--r-- | numpy/core/src/multiarray/dragon4.c | 264 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/dragon4.h | 68 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/multiarraymodule.c | 161 |
3 files changed, 432 insertions, 61 deletions
diff --git a/numpy/core/src/multiarray/dragon4.c b/numpy/core/src/multiarray/dragon4.c index 3f2749d82..07e250b10 100644 --- a/numpy/core/src/multiarray/dragon4.c +++ b/numpy/core/src/multiarray/dragon4.c @@ -1509,10 +1509,15 @@ npy_uint64 GetMantissa_F64(FloatUnion64 *v) { return v->integer & 0xFFFFFFFFFFFF * intbit 1 bit, first u64 * mantissa: 63 bits, first u64 */ -typedef union FloatUnion128 { - npy_float128 floatingPoint; + +/* + * Since systems have different types of long doubles, and may not necessarily + * have a 128-byte format we can use to pass values around, here we create + * our own 128-bit storage type for convenience. + */ +typedef struct FloatVal128 { npy_uint64 integer[2]; -} FloatUnion128; +} FloatVal128; npy_bool IsNegative_F128(FloatVal128 *v) { return ((v->integer[1] >> 15) & 0x1) != 0; } @@ -1522,6 +1527,44 @@ npy_uint64 GetMantissa_F128(FloatVal128 *v) { } /* + * then for each different definition of long double, we create a union to + * unpack the float data safely. We can then copy these integers to a + * FloatVal128. + */ +#ifdef NPY_FLOAT128 +typedef union FloatUnion128 +{ + npy_float128 floatingPoint; + struct { + npy_uint64 a; + npy_uint16 b; + } integer; +} FloatUnion128; +#endif + +#ifdef NPY_FLOAT96 +typedef union FloatUnion96 +{ + npy_float96 floatingPoint; + struct { + npy_uint64 a; + npy_uint32 b; + } integer; +} FloatUnion96; +#endif + +#ifdef NPY_FLOAT80 +typedef union FloatUnion80 +{ + npy_float80 floatingPoint; + struct { + npy_uint64 a; + npy_uint16 b; + } integer; +} FloatUnion80; +#endif + +/* * The main changes above this point, relative to Ryan Juckett's code, are: * 1. fixed overflow problems when mantissa was 64 bits (in float128 types), * by replacing multiplication by 2 or 4 by BigInt_ShiftLeft calls. @@ -1532,7 +1575,7 @@ npy_uint64 GetMantissa_F128(FloatVal128 *v) { * Below this point, the FormatPositional and FormatScientific functions have * been more significantly rewritten. The Dragon4_PrintFloat16 and * Dragon4_PrintFloat128 functions are new, and were adapted from the 64 and 32 - * bit versions. + * bit versions. The python interfacing functions (in the header) are new. */ @@ -2102,7 +2145,7 @@ PrintInfNan(char *buffer, npy_uint32 bufferSize, npy_uint64 mantissa, * exponent with 0s until there are this many digits. If * negative, only use sufficient digits. */ -npy_uint32 +static npy_uint32 Dragon4_PrintFloat16(char *buffer, npy_uint32 bufferSize, npy_uint16 value, npy_bool scientific, npy_bool unique, npy_int32 precision, npy_bool sign, TrimMode trim_mode, npy_int32 digits_left, @@ -2198,7 +2241,7 @@ Dragon4_PrintFloat16(char *buffer, npy_uint32 bufferSize, npy_uint16 value, } } -npy_uint32 +static npy_uint32 Dragon4_PrintFloat32(char *buffer, npy_uint32 bufferSize, npy_float32 value, npy_bool scientific, npy_bool unique, npy_int32 precision, npy_bool sign, TrimMode trim_mode, npy_int32 digits_left, @@ -2294,7 +2337,7 @@ Dragon4_PrintFloat32(char *buffer, npy_uint32 bufferSize, npy_float32 value, } } -npy_uint32 +static npy_uint32 Dragon4_PrintFloat64(char *buffer, npy_uint32 bufferSize, npy_float64 value, npy_bool scientific, npy_bool unique, npy_int32 precision, npy_bool sign, TrimMode trim_mode, npy_int32 digits_left, @@ -2391,7 +2434,7 @@ Dragon4_PrintFloat64(char *buffer, npy_uint32 bufferSize, npy_float64 value, } } -npy_uint32 +static npy_uint32 Dragon4_PrintFloat128(char *buffer, npy_uint32 bufferSize, FloatVal128 value, npy_bool scientific, npy_bool unique, npy_int32 precision, npy_bool sign, TrimMode trim_mode, npy_int32 digits_left, @@ -2485,4 +2528,209 @@ Dragon4_PrintFloat128(char *buffer, npy_uint32 bufferSize, FloatVal128 value, digits_left, digits_right); } } + +PyObject * +Dragon4_Positional_AnySize(void *val, size_t size, npy_bool unique, + int precision, int sign, TrimMode trim, + int pad_left, int pad_right) +{ + /* + * Use a very large buffer in case anyone tries to output a large numberG. + * 16384 should be enough to uniquely print any float128, which goes up + * to about 10^4932 */ + static char repr[16384]; + FloatVal128 val128; +#ifdef NPY_FLOAT80 + FloatUnion80 buf80;; +#endif +#ifdef NPY_FLOAT96 + FloatUnion96 buf96; +#endif +#ifdef NPY_FLOAT128 + FloatUnion128 buf128; +#endif + + switch (size) { + case 2: + Dragon4_PrintFloat16(repr, sizeof(repr), *(npy_float16*)val, + 0, unique, precision, sign, trim, pad_left, pad_right, -1); + break; + case 4: + Dragon4_PrintFloat32(repr, sizeof(repr), *(npy_float32*)val, + 0, unique, precision, sign, trim, pad_left, pad_right, -1); + break; + case 8: + Dragon4_PrintFloat64(repr, sizeof(repr), *(npy_float64*)val, + 0, unique, precision, sign, trim, pad_left, pad_right, -1); + break; +#ifdef NPY_FLOAT80 + case 10: + buf80.floatingPoint = *(npy_float80*)val; + val128.integer[0] = buf80.integer.a; + val128.integer[1] = buf80.integer.b; + Dragon4_PrintFloat128(repr, sizeof(repr), val128, + 0, unique, precision, sign, trim, pad_left, pad_right, -1); + break; +#endif +#ifdef NPY_FLOAT96 + case 12: + buf96.floatingPoint = *(npy_float96*)val; + val128.integer[0] = buf96.integer.a; + val128.integer[1] = buf96.integer.b; + Dragon4_PrintFloat128(repr, sizeof(repr), val128, + 0, unique, precision, sign, trim, pad_left, pad_right, -1); + break; +#endif +#ifdef NPY_FLOAT128 + case 16: + buf128.floatingPoint = *(npy_float128*)val; + val128.integer[0] = buf128.integer.a; + val128.integer[1] = buf128.integer.b; + Dragon4_PrintFloat128(repr, sizeof(repr), val128, + 0, unique, precision, sign, trim, pad_left, pad_right, -1); + break; +#endif + default: + PyErr_Format(PyExc_ValueError, "unexpected itemsize %zu", size); + return NULL; + } + + return PyUString_FromString(repr); +} + +PyObject * +Dragon4_Positional(PyObject *obj, npy_bool unique, int precision, int sign, + TrimMode trim, int pad_left, int pad_right) +{ + double val; + + if (PyArray_IsScalar(obj, Half)) { + npy_half x = ((PyHalfScalarObject *)obj)->obval; + return Dragon4_Positional_AnySize(&x, sizeof(npy_half), unique, + precision, sign, trim, pad_left, pad_right); + } + else if (PyArray_IsScalar(obj, Float)) { + npy_float x = ((PyFloatScalarObject *)obj)->obval; + return Dragon4_Positional_AnySize(&x, sizeof(npy_float), unique, + precision, sign, trim, pad_left, pad_right); + } + else if (PyArray_IsScalar(obj, Double)) { + npy_double x = ((PyDoubleScalarObject *)obj)->obval; + return Dragon4_Positional_AnySize(&x, sizeof(npy_double), unique, + precision, sign, trim, pad_left, pad_right); + } + else if (PyArray_IsScalar(obj, LongDouble)) { + npy_longdouble x = ((PyLongDoubleScalarObject *)obj)->obval; + return Dragon4_Positional_AnySize(&x, sizeof(npy_longdouble), unique, + precision, sign, trim, pad_left, pad_right); + } + + val = PyFloat_AsDouble(obj); + if (PyErr_Occurred()) { + return NULL; + } + return Dragon4_Positional_AnySize(&val, sizeof(double), unique, + precision, sign, trim, pad_left, pad_right); +} + +PyObject * +Dragon4_Scientific_AnySize(void *val, size_t size, npy_bool unique, + int precision, int sign, TrimMode trim, + int pad_left, int exp_digits) +{ + /* use a very large buffer in case anyone tries to output a large precision */ + static char repr[4096]; + FloatVal128 val128; +#ifdef NPY_FLOAT80 + FloatUnion80 buf80;; +#endif +#ifdef NPY_FLOAT96 + FloatUnion96 buf96; +#endif +#ifdef NPY_FLOAT128 + FloatUnion128 buf128; #endif + + + switch (size) { + case 2: + Dragon4_PrintFloat16(repr, sizeof(repr), *(npy_float16*)val, + 1, unique, precision, sign, trim, pad_left, -1, exp_digits); + break; + case 4: + Dragon4_PrintFloat32(repr, sizeof(repr), *(npy_float32*)val, + 1, unique, precision, sign, trim, pad_left, -1, exp_digits); + break; + case 8: + Dragon4_PrintFloat64(repr, sizeof(repr), *(npy_float64*)val, + 1, unique, precision, sign, trim, pad_left, -1, exp_digits); + break; +#ifdef NPY_FLOAT80 + case 10: + buf80.floatingPoint = *(npy_float80*)val; + val128.integer[0] = buf80.integer.a; + val128.integer[1] = buf80.integer.b; + Dragon4_PrintFloat128(repr, sizeof(repr), val128, + 1, unique, precision, sign, trim, pad_left, -1, exp_digits); + break; +#endif +#ifdef NPY_FLOAT96 + case 12: + buf96.floatingPoint = *(npy_float96*)val; + val128.integer[0] = buf96.integer.a; + val128.integer[1] = buf96.integer.b; + Dragon4_PrintFloat128(repr, sizeof(repr), val128, + 1, unique, precision, sign, trim, pad_left, -1, exp_digits); + break; +#endif +#ifdef NPY_FLOAT128 + case 16: + buf128.floatingPoint = *(npy_float128*)val; + val128.integer[0] = buf128.integer.a; + val128.integer[1] = buf128.integer.b; + Dragon4_PrintFloat128(repr, sizeof(repr), val128, + 1, unique, precision, sign, trim, pad_left, -1, exp_digits); + break; +#endif + default: + PyErr_Format(PyExc_ValueError, "unexpected itemsize %zu", size); + return NULL; + } + + return PyUString_FromString(repr); +} + +PyObject * +Dragon4_Scientific(PyObject *obj, npy_bool unique, int precision, int sign, + TrimMode trim, int pad_left, int exp_digits) +{ + double val; + + if (PyArray_IsScalar(obj, Half)) { + npy_half x = ((PyHalfScalarObject *)obj)->obval; + return Dragon4_Scientific_AnySize(&x, sizeof(npy_half), unique, + precision, sign, trim, pad_left, exp_digits); + } + else if (PyArray_IsScalar(obj, Float)) { + npy_float x = ((PyFloatScalarObject *)obj)->obval; + return Dragon4_Scientific_AnySize(&x, sizeof(npy_float), unique, + precision, sign, trim, pad_left, exp_digits); + } + else if (PyArray_IsScalar(obj, Double)) { + npy_double x = ((PyDoubleScalarObject *)obj)->obval; + return Dragon4_Scientific_AnySize(&x, sizeof(npy_double), unique, + precision, sign, trim, pad_left, exp_digits); + } + else if (PyArray_IsScalar(obj, LongDouble)) { + npy_longdouble x = ((PyLongDoubleScalarObject *)obj)->obval; + return Dragon4_Scientific_AnySize(&x, sizeof(npy_longdouble), unique, + precision, sign, trim, pad_left, exp_digits); + } + + val = PyFloat_AsDouble(obj); + if (PyErr_Occurred()) { + return NULL; + } + return Dragon4_Scientific_AnySize(&val, sizeof(double), unique, + precision, sign, trim, pad_left, exp_digits); +} diff --git a/numpy/core/src/multiarray/dragon4.h b/numpy/core/src/multiarray/dragon4.h index 9ace4fd58..814c84a2f 100644 --- a/numpy/core/src/multiarray/dragon4.h +++ b/numpy/core/src/multiarray/dragon4.h @@ -30,7 +30,15 @@ #ifndef _NPY_DRAGON4_H_ #define _NPY_DRAGON4_H_ -#include "numpy/npy_common.h" + +#include "Python.h" +#include "structmember.h" +#define NPY_NO_DEPRECATED_API NPY_API_VERSION +#define _MULTIARRAYMODULE +#include "numpy/arrayobject.h" +#include "npy_config.h" +#include "npy_pycompat.h" +#include "numpy/arrayscalars.h" typedef enum TrimMode { @@ -40,53 +48,23 @@ typedef enum TrimMode TrimMode_DptZeros, /* trim trailing zeros & trailing decimal point */ } TrimMode; +PyObject * +Dragon4_Positional_AnySize(void *val, size_t size, npy_bool unique, + int precision, int sign, TrimMode trim, + int pad_left, int pad_right); -//****************************************************************************** -// These functions frint a floating-point number as a decimal string. -// The output string is always NUL terminated and the string length (not -// including the NUL) is returned. -//****************************************************************************** -// -// Arguments are: -// * buffer - buffer to output into -// * bufferSize - maximum characters that can be printed to buffer -// * value - value significand -// * scientific - boolean controlling whether scientific notation is used -// * precision - If positive, specifies the number of decimals to show after -// decimal point. If negative, sufficient digits to uniquely -// specify the float will be output. -// * trim_mode - how to treat trailing zeros and decimal point. See TrimMode. -// * digits_right - pad the result with '' on the right past the decimal point -// * digits_left - pad the result with '' on the right past the decimal point -// * exp_digits - Only affects scientific output. If positive, pads the -// exponent with 0s until there are this many digits. If -// negative, only use sufficient digits. - -npy_uint32 -Dragon4_PrintFloat16(char *buffer, npy_uint32 bufferSize, npy_uint16 value, - npy_bool scientific, npy_bool unique, npy_int32 precision, - npy_bool sign, TrimMode trim_mode, npy_int32 digits_left, - npy_int32 digits_right, npy_int32 exp_digits); +PyObject * +Dragon4_Scientific_AnySize(void *val, size_t size, npy_bool unique, + int precision, int sign, TrimMode trim, + int pad_left, int exp_digits); -npy_uint32 -Dragon4_PrintFloat32(char *buffer, npy_uint32 bufferSize, npy_float32 value, - npy_bool scientific, npy_bool unique, npy_int32 precision, - npy_bool sign, TrimMode trim_mode, npy_int32 digits_left, - npy_int32 digits_right, npy_int32 exp_digits); +PyObject * +Dragon4_Positional(PyObject *obj, npy_bool unique, int precision, int sign, + TrimMode trim, int pad_left, int pad_right); -npy_uint32 -Dragon4_PrintFloat64(char *buffer, npy_uint32 bufferSize, npy_float64 value, - npy_bool scientific, npy_bool unique, npy_int32 precision, - npy_bool sign, TrimMode trim_mode, npy_int32 digits_left, - npy_int32 digits_right, npy_int32 exp_digits); - -#ifdef NPY_FLOAT128 -npy_uint32 -Dragon4_PrintFloat128(char *buffer, npy_uint32 bufferSize, FloatVal128 value, - npy_bool scientific, npy_bool unique, npy_int32 precision, - npy_bool sign, TrimMode trim_mode, npy_int32 digits_left, - npy_int32 digits_right, npy_int32 exp_digits); -#endif +PyObject * +Dragon4_Scientific(PyObject *obj, npy_bool unique, int precision, int sign, + TrimMode trim, int pad_left, int exp_digits); #endif diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c index 499ec343c..9ee53362e 100644 --- a/numpy/core/src/multiarray/multiarraymodule.c +++ b/numpy/core/src/multiarray/multiarraymodule.c @@ -37,6 +37,7 @@ NPY_NO_EXPORT int NPY_NUMUSERTYPES = 0; #include "arrayobject.h" #include "hashdescr.h" #include "descriptor.h" +#include "dragon4.h" #include "calculation.h" #include "number.h" #include "scalartypes.h" @@ -3583,14 +3584,156 @@ as_buffer(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *kwds) #undef _test_code + +/* + * Prints floating-point scalars usign the Dragon4 algorithm, scientific mode. + * Arguments: + * x - a numpy scalar of Floating type + * precision - number of fractional digits to show. In unique mode, can be + * ommited and the unique repr will be returned, otherwise the + * unique value will be truncated to this number of digits + * (breaking the uniqueness guarantee). In fixed mode, is + * required, and specifies the number of fractional digits to + * print. + * unique - whether to use unique (default) or fixed mode. + * sign - whether to show the sign for positive values. Default False + * trim - one of 'k', '.', '0', '-' to control trailing digits, as follows: + * k : don't trim zeros, always leave a decimal point + * . : trim all but the zero before the decimal point + * 0 : trim all trailing zeros, leave decimal point + * - : trim trailing zeros and a trailing decimal point + * Default is k. + * pad_left - pads left side of string with whitespace until at least + * this many characters are to the left of the decimal point. If + * -1, don't add any padding. Default -1. + * exp_digits - exponent will contain at least this many digits, padding + * with 0 if necessary. -1 means pad to 2. Maximum of 5. + */ +static PyObject * +dragon4_scientific(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *kwds) +{ + PyObject *obj; + static char *kwlist[] = {"x", "precision", "unique", "sign", "trim", + "pad_left", "exp_digits", NULL}; + int precision=-1, pad_left=-1, exp_digits=-1; + char *trimstr=NULL; + TrimMode trim = TrimMode_None; + int sign=0, unique=1; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|iiisii", kwlist, + &obj, &precision, &unique, &sign, &trimstr, &pad_left, + &exp_digits)) { + return NULL; + } + + if (trimstr != NULL) { + if (strcmp(trimstr, "k") == 0) { + trim = TrimMode_None; + } + else if (strcmp(trimstr, ".") == 0) { + trim = TrimMode_Zeros; + } + else if (strcmp(trimstr, "0") == 0) { + trim = TrimMode_LeaveOneZero; + } + else if (strcmp(trimstr, "-") == 0) { + trim = TrimMode_DptZeros; + } + else { + PyErr_SetString(PyExc_TypeError, + "if supplied, trim must be 'k', '.', '0' or '-'"); + return NULL; + } + } + + if (unique == 0 && precision < 0) { + PyErr_SetString(PyExc_TypeError, + "in non-unique mode `precision` must be supplied"); + return NULL; + } + + return Dragon4_Scientific(obj, unique, precision, sign, + trim, pad_left, exp_digits); +} + +/* + * Prints floating-point scalars usign the Dragon4 algorithm, positional mode. + * Arguments: + * x - a numpy scalar of Floating type + * precision - number of fractional digits to show. In unique mode, can be + * ommited and the unique repr will be returned, otherwise the + * unique value will be truncated to this number of digits + * (breaking the uniqueness guarantee). In fixed mode, is + * required, and specifies the number of fractional digits to + * print. + * unique - whether to use unique (default) or fixed mode. + * sign - whether to show the sign for positive values. Default False + * trim - one of 'k', '.', '0', '-' to control trailing digits, as follows: + * k : don't trim zeros, always leave a decimal point + * . : trim all but the zero before the decimal point + * 0 : trim all trailing zeros, leave decimal point + * - : trim trailing zeros and a trailing decimal point + * Default is k. + * pad_left - pads left side of string with whitespace until at least + * this many characters are to the left of the decimal point. If + * -1, don't add any padding. Default -1. + * pad_right - pads right side of string with whitespace until at least + * this many characters are to the right of the decimal point. If + * -1, don't add any padding. Default -1. + */ +static PyObject * +dragon4_positional(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *kwds) +{ + PyObject *obj; + static char *kwlist[] = {"x", "precision", "unique", "sign", "trim", + "pad_left", "pad_right", NULL}; + int precision=-1, pad_left=-1, pad_right=-1; + char *trimstr=NULL; + TrimMode trim = TrimMode_None; + int sign=0, unique=1; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|iiisii", kwlist, + &obj, &precision, &unique, &sign, &trimstr, &pad_left, + &pad_right)) { + return NULL; + } + + if (trimstr != NULL) { + if (strcmp(trimstr, "k") == 0) { + trim = TrimMode_None; + } + else if (strcmp(trimstr, ".") == 0) { + trim = TrimMode_Zeros; + } + else if (strcmp(trimstr, "0") == 0) { + trim = TrimMode_LeaveOneZero; + } + else if (strcmp(trimstr, "-") == 0) { + trim = TrimMode_DptZeros; + } + else { + PyErr_SetString(PyExc_TypeError, + "if supplied, trim must be 'k', '.', '0' or '-'"); + return NULL; + } + } + + if (unique == 0 && precision < 0) { + PyErr_SetString(PyExc_TypeError, + "in non-unique mode `precision` must be supplied"); + return NULL; + } + + return Dragon4_Positional(obj, unique, precision, sign, + trim, pad_left, pad_right); +} + static PyObject * format_longfloat(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *kwds) { PyObject *obj; unsigned int precision; - npy_longdouble x; static char *kwlist[] = {"x", "precision", NULL}; - static char repr[100]; if (!PyArg_ParseTupleAndKeywords(args, kwds, "OI:format_longfloat", kwlist, &obj, &precision)) { @@ -3601,12 +3744,8 @@ format_longfloat(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *kwds) "not a longfloat"); return NULL; } - x = ((PyLongDoubleScalarObject *)obj)->obval; - if (precision > 70) { - precision = 70; - } - format_longdouble(repr, 100, x, precision); - return PyUString_FromString(repr); + return Dragon4_Scientific(obj, precision, 0, 1, TrimMode_LeaveOneZero, + -1, -1); } static PyObject * @@ -4289,6 +4428,12 @@ static struct PyMethodDef array_module_methods[] = { {"format_longfloat", (PyCFunction)format_longfloat, METH_VARARGS | METH_KEYWORDS, NULL}, + {"dragon4_positional", + (PyCFunction)dragon4_positional, + METH_VARARGS | METH_KEYWORDS, NULL}, + {"dragon4_scientific", + (PyCFunction)dragon4_scientific, + METH_VARARGS | METH_KEYWORDS, NULL}, {"compare_chararrays", (PyCFunction)compare_chararrays, METH_VARARGS | METH_KEYWORDS, NULL}, |
