diff -r 25a2aeecb34b Doc/library/struct.rst --- a/Doc/library/struct.rst Thu Mar 31 02:05:54 2011 +0200 +++ b/Doc/library/struct.rst Tue Apr 05 11:04:14 2011 -0700 @@ -191,11 +191,13 @@ +--------+--------------------------+--------------------+----------------+------------+ | ``d`` | :c:type:`double` | float | 8 | \(4) | +--------+--------------------------+--------------------+----------------+------------+ +| ``e`` | \(5) | float | 2 | \(4) | ++--------+--------------------------+--------------------+----------------+------------+ | ``s`` | :c:type:`char[]` | bytes | | | +--------+--------------------------+--------------------+----------------+------------+ | ``p`` | :c:type:`char[]` | bytes | | | +--------+--------------------------+--------------------+----------------+------------+ -| ``P`` | :c:type:`void \*` | integer | | \(5) | +| ``P`` | :c:type:`void \*` | integer | | \(6) | +--------+--------------------------+--------------------+----------------+------------+ Notes: @@ -219,11 +221,17 @@ Use of the :meth:`__index__` method for non-integers is new in 3.2. (4) - For the ``'f'`` and ``'d'`` conversion codes, the packed representation uses - the IEEE 754 binary32 (for ``'f'``) or binary64 (for ``'d'``) format, - regardless of the floating-point format used by the platform. + For the ``'f'``, ``'d'`` and ``'e'`` conversion codes, the packed + representation uses the IEEE 754 binary32, binary64 or binary16 format (for + ``'f'``, ``'d'`` or ``'e'`` respectively), regardless of the floating-point + format used by the platform. (5) + The IEEE 754-2008 16-bit "half float" type is not widely supported by C + compilers. An unsigned short can be used for storage, but not math + operations. + +(6) The ``'P'`` format character is only available for the native byte ordering (selected as the default or with the ``'@'`` byte order character). The byte order character ``'='`` chooses to use little- or big-endian ordering based @@ -382,4 +390,3 @@ The calculated size of the struct (and hence of the bytes object produced by the :meth:`pack` method) corresponding to :attr:`format`. - diff -r 25a2aeecb34b Include/floatobject.h --- a/Include/floatobject.h Thu Mar 31 02:05:54 2011 +0200 +++ b/Include/floatobject.h Tue Apr 05 11:04:14 2011 -0700 @@ -74,9 +74,9 @@ * happens in such cases is partly accidental (alas). */ -/* The pack routines write 4 or 8 bytes, starting at p. le is a bool +/* The pack routines write 2, 4 or 8 bytes, starting at p. le is a bool * argument, true if you want the string in little-endian format (exponent - * last, at p+3 or p+7), false if you want big-endian format (exponent + * last, at p+1, p+3 or p+7), false if you want big-endian format (exponent * first, at p). * Return value: 0 if all is OK, -1 if error (and an exception is * set, most likely OverflowError). @@ -84,6 +84,7 @@ * 1): What this does is undefined if x is a NaN or infinity. * 2): -0.0 and +0.0 produce the same string. */ +PyAPI_FUNC(int) _PyFloat_Pack2(double x, unsigned char *p, int le); PyAPI_FUNC(int) _PyFloat_Pack4(double x, unsigned char *p, int le); PyAPI_FUNC(int) _PyFloat_Pack8(double x, unsigned char *p, int le); @@ -96,14 +97,15 @@ PyAPI_FUNC(int) _PyFloat_Digits(char *buf, double v, int *signum); PyAPI_FUNC(void) _PyFloat_DigitsInit(void); -/* The unpack routines read 4 or 8 bytes, starting at p. le is a bool +/* The unpack routines read 2, 4 or 8 bytes, starting at p. le is a bool * argument, true if the string is in little-endian format (exponent - * last, at p+3 or p+7), false if big-endian (exponent first, at p). + * last, at p+1, p+3 or p+7), false if big-endian (exponent first, at p). * Return value: The unpacked double. On error, this is -1.0 and * PyErr_Occurred() is true (and an exception is set, most likely * OverflowError). Note that on a non-IEEE platform this will refuse * to unpack a string that represents a NaN or infinity. */ +PyAPI_FUNC(double) _PyFloat_Unpack2(const unsigned char *p, int le); PyAPI_FUNC(double) _PyFloat_Unpack4(const unsigned char *p, int le); PyAPI_FUNC(double) _PyFloat_Unpack8(const unsigned char *p, int le); diff -r 25a2aeecb34b Lib/test/test_struct.py --- a/Lib/test/test_struct.py Thu Mar 31 02:05:54 2011 +0200 +++ b/Lib/test/test_struct.py Tue Apr 05 11:04:14 2011 -0700 @@ -556,6 +556,98 @@ s = struct.Struct('i') s.__init__('ii') + def test_half_float(self): + import math + + # Examples from: + # http://en.wikipedia.org/wiki/Half_precision_floating-point_format + format_bits_float__cleanRoundtrip_list = [ + ('>e', b'\x3c\x00', 1.0), + ('>e', b'\xc0\x00', -2.0), + ('>e', b'\x7b\xff', 6.5504 * 10**4), # (max half precision) + ('>e', b'\x04\x00', 2**-14), # ~= 6.10352 * 10**-5 (minimum positive normal) + ('>e', b'\x00\x01', 2**-24), # ~= 5.96046 * 10**-8 (minimum strictly positive subnormal) + ('>e', b'\x00\x00', 0.0), + ('>e', b'\x80\x00', -0.0), + ('>e', b'\x7c\x00', float('+inf')), + ('>e', b'\xfc\x00', float('-inf')), + ('>e', b'\x35\x55', 0.333251953125), # ~= 1/3 + + ('e', b'\xfc\x01'), + ] + + for formatcode, bits in format_bits__nan_list: + f, = struct.unpack(formatcode, memoryview(bits)) + self.assertTrue(math.isnan(f)) + + + # Checks for round-to-even behavior + format_bits_float__rounding_list = [ + ('>e', b'\x00\x01', 2.0**-25 + 2.0**-35), # Rounds to minimum subnormal + ('>e', b'\x00\x00', 2.0**-25), # Underflows to zero (nearest even mode) + ('>e', b'\x00\x00', 2.0**-26), # Underflows to zero + ('>e', b'\x3c\x01', 1.0+2.0**-11 + 2.0**-16), # rounds to 1.0+2**(-10) + ('>e', b'\x3c\x00', 1.0+2.0**-11), # rounds to 1.0 (nearest even mode) + ('>e', b'\x3c\x00', 1.0+2.0**-12), # rounds to 1.0 + + ('>e', b'\x7b\xff', 65519), # rounds to 65504 + #('>e', b'\x7c\x00', 65520), # rounds to inf + + + #('>e', b'\x80\x01', -2.0**-25 + 2.0**-35), # Rounds to minimum subnormal + ('>e', b'\x80\x00', -2.0**-25), # Underflows to zero (nearest even mode) + ('>e', b'\x80\x00', -2.0**-26), # Underflows to zero + ('>e', b'\xbc\x01', -1.0-2.0**-11 - 2.0**-16), # rounds to 1.0+2**(-10) + ('>e', b'\xbc\x00', -1.0-2.0**-11), # rounds to 1.0 (nearest even mode) + ('>e', b'\xbc\x00', -1.0-2.0**-12), # rounds to 1.0 + + ('>e', b'\xfb\xff', -65519), # rounds to 65504 + ] + + for formatcode, bits, f in format_bits_float__rounding_list: + #print('rounding to even', formatcode, [hex(x) for x in bits], f) + self.assertEqual(bits, struct.pack(formatcode, f)) + + + # This overflows, and so raises an error + format_bits_float__roundingError_list = [ + ('>e', b'\x7c\x00', 65520), # rounds to inf + ] + + for formatcode, bits, f in format_bits_float__roundingError_list: + #print('rounding error', formatcode, [hex(x) for x in bits], f) + self.assertRaises(struct.error, struct.pack, formatcode, f) + + # Double rounding + format_bits_float__doubleRoundingError_list = [ + ('>e', b'\x67\xff', 0x1ffdffffff * 2**-26), # should be 2047, if double-rounded 64>32>16, becomes 2048 + ] + + for formatcode, bits, f in format_bits_float__doubleRoundingError_list: + #print('double rounding error', formatcode, [hex(x) for x in bits], f) + self.assertEqual(bits, struct.pack(formatcode, f)) + + def test_main(): run_unittest(StructTest) diff -r 25a2aeecb34b Modules/_struct.c --- a/Modules/_struct.c Thu Mar 31 02:05:54 2011 +0200 +++ b/Modules/_struct.c Tue Apr 05 11:04:14 2011 -0700 @@ -218,6 +218,18 @@ /* Floating point helpers */ +static PyObject * +unpack_halffloat(const char *p, /* start of 4-byte string */ + int le) /* true for little-endian, false for big-endian */ +{ + double x; + + x = _PyFloat_Unpack2((unsigned char *)p, le); + if (x == -1.0 && PyErr_Occurred()) + return NULL; + return PyFloat_FromDouble(x); +} + static PyObject * unpack_float(const char *p, /* start of 4-byte string */ @@ -406,6 +418,13 @@ static PyObject * +nu_halffloat(const char *p, const formatdef *f) +{ + int le = 1; + return PyFloat_FromDouble(_PyFloat_Unpack2(p, (int)*(char*)&le)); +} + +static PyObject * nu_float(const char *p, const formatdef *f) { float x; @@ -596,6 +615,20 @@ } static int +np_halffloat(char *p, PyObject *v, const formatdef *f) +{ + double x = PyFloat_AsDouble(v); + if (x == -1 && PyErr_Occurred()) { + PyErr_SetString(StructError, + "required argument is not a float"); + return -1; + } + int le = 1; + _PyFloat_Pack2(x, p, (int)*(char*)&le); + return 0; +} + +static int np_float(char *p, PyObject *v, const formatdef *f) { float x = (float)PyFloat_AsDouble(v); @@ -656,6 +689,7 @@ {'Q', sizeof(PY_LONG_LONG), LONG_LONG_ALIGN, nu_ulonglong,np_ulonglong}, #endif {'?', sizeof(BOOL_TYPE), BOOL_ALIGN, nu_bool, np_bool}, + {'e', sizeof(short), SHORT_ALIGN, nu_halffloat, np_halffloat}, {'f', sizeof(float), FLOAT_ALIGN, nu_float, np_float}, {'d', sizeof(double), DOUBLE_ALIGN, nu_double, np_double}, {'P', sizeof(void *), VOID_P_ALIGN, nu_void_p, np_void_p}, @@ -739,6 +773,12 @@ } static PyObject * +bu_halffloat(const char *p, const formatdef *f) +{ + return unpack_halffloat(p, 0); +} + +static PyObject * bu_float(const char *p, const formatdef *f) { return unpack_float(p, 0); @@ -835,6 +875,18 @@ } static int +bp_halffloat(char *p, PyObject *v, const formatdef *f) +{ + double x = PyFloat_AsDouble(v); + if (x == -1 && PyErr_Occurred()) { + PyErr_SetString(StructError, + "required argument is not a float"); + return -1; + } + return _PyFloat_Pack2(x, (unsigned char *)p, 0); +} + +static int bp_float(char *p, PyObject *v, const formatdef *f) { double x = PyFloat_AsDouble(v); @@ -885,6 +937,7 @@ {'q', 8, 0, bu_longlong, bp_longlong}, {'Q', 8, 0, bu_ulonglong, bp_ulonglong}, {'?', 1, 0, bu_bool, bp_bool}, + {'e', 2, 0, bu_halffloat, bp_halffloat}, {'f', 4, 0, bu_float, bp_float}, {'d', 8, 0, bu_double, bp_double}, {0} @@ -967,6 +1020,12 @@ } static PyObject * +lu_halffloat(const char *p, const formatdef *f) +{ + return unpack_halffloat(p, 1); +} + +static PyObject * lu_float(const char *p, const formatdef *f) { return unpack_float(p, 1); @@ -1055,6 +1114,18 @@ } static int +lp_halffloat(char *p, PyObject *v, const formatdef *f) +{ + double x = PyFloat_AsDouble(v); + if (x == -1 && PyErr_Occurred()) { + PyErr_SetString(StructError, + "required argument is not a float"); + return -1; + } + return _PyFloat_Pack2(x, (unsigned char *)p, 1); +} + +static int lp_float(char *p, PyObject *v, const formatdef *f) { double x = PyFloat_AsDouble(v); @@ -1095,6 +1166,7 @@ {'Q', 8, 0, lu_ulonglong, lp_ulonglong}, {'?', 1, 0, bu_bool, bp_bool}, /* Std rep not endian dep, but potentially different from native rep -- reuse bx_bool funcs. */ + {'e', 2, 0, lu_halffloat, lp_halffloat}, {'f', 4, 0, lu_float, lp_float}, {'d', 8, 0, lu_double, lp_double}, {0} @@ -1948,7 +2020,7 @@ x: pad byte (no data); c:char; b:signed byte; B:unsigned byte;\n\ ?: _Bool (requires C99; if not available, char is used instead)\n\ h:short; H:unsigned short; i:int; I:unsigned int;\n\ - l:long; L:unsigned long; f:float; d:double.\n\ + l:long; L:unsigned long; f:float; d:double; e:half-float.\n\ Special cases (preceding decimal count indicates length):\n\ s:string (array of char); p: pascal string (with count byte).\n\ Special case (only available in native format):\n\ diff -r 25a2aeecb34b Objects/floatobject.c --- a/Objects/floatobject.c Thu Mar 31 02:05:54 2011 +0200 +++ b/Objects/floatobject.c Tue Apr 05 11:04:14 2011 -0700 @@ -2032,8 +2032,111 @@ } /*---------------------------------------------------------------------------- - * _PyFloat_{Pack,Unpack}{4,8}. See floatobject.h. + * _PyFloat_{Pack,Unpack}{2,4,8}. See floatobject.h. + * To match the NPY_HALF_ROUND_TIES_TO_EVEN behavior in: + * https://github.com/numpy/numpy/blob/master/numpy/core/src/npymath/halffloat.c + * We use: + * bits = (unsigned short)f; Note the truncation + * if ((f - bits > 0.5) || (f - bits == 0.5 && bits % 2)) { + * bits++; + * } */ + +int +_PyFloat_Pack2(double x, unsigned char *p, int le) +{ + unsigned char sign; + int e; + double f; + unsigned short bits; + int incr = 1; + + if (le) { + p += 1; + incr = -1; + } + + if (x < 0 || signbit(x)) { + sign = 1; + x = -x; + } + else + sign = 0; + + if (x == 0) { + e = 0; + bits = 0; + } + else if (isinf(x)) { + e = 0x1f; + bits = 0; + } + else if (isnan(x)) { + e = 0x1f; + bits = 1; + } + else { + f = frexp(x, &e); + + /* Normalize f to be in the range [1.0, 2.0) */ + if (0.5 <= f && f < 1.0) { + f *= 2.0; + e--; + } + else if (f == 0.0) + e = 0; + else { + PyErr_SetString(PyExc_SystemError, + "frexp() result out of range"); + return -1; + } + + if (e >= 16) + goto Overflow; + else if (e < -14) { + /* Gradual underflow */ + f = ldexp(f, 14 + e); + e = 0; + } + else /* if (!(e == 0 && f == 0.0)) */ { + e += 15; + f -= 1.0; /* Get rid of leading 1 */ + } + + f *= 1024.0; /* 2**10 */ + /* Round to even */ + bits = (unsigned short)f; /* Note the truncation */ + if ((f - bits > 0.5) || (f - bits == 0.5 && bits % 2)) { + bits++; + } + + assert(bits <= 1024); + if (bits >> 10) { + /* The carry propagated out of a string of 10 1 bits. */ + bits = 0; + ++e; + if (e >= 31) + goto Overflow; + } + } + + bits |= (e << 10) | (sign << 15); + + /* First byte */ + *p = (unsigned char)((bits >> 8) & 0xff); + p += incr; + + /* Second byte */ + *p = (unsigned char)(bits & 0xFF); + + return 0; + + Overflow: + PyErr_SetString(PyExc_OverflowError, + "float too large to pack with e format"); + return -1; +} + int _PyFloat_Pack4(double x, unsigned char *p, int le) { @@ -2269,6 +2372,64 @@ } double +_PyFloat_Unpack2(const unsigned char *p, int le) +{ + unsigned char sign; + int e; + unsigned int f; + double x; + int incr = 1; + + if (le) { + p += 1; + incr = -1; + } + + /* First byte */ + sign = (*p >> 7) & 1; + e = (*p & 0x7c) >> 2; + f = (*p & 0x03) << 8; + p += incr; + + /* Second byte */ + f |= *p; + + if (e == 0x1f) { + if (double_format == unknown_format) { + PyErr_SetString( + PyExc_ValueError, + "can't unpack IEEE 754 special value " + "on non-IEEE platform"); + return -1; + } + else if (f) { + return Py_NAN; + } + else if (sign) { + return -Py_HUGE_VAL; + } + else { + return Py_HUGE_VAL; + } + } + + x = (double)f / 1024.0; + + if (e == 0) + e = -14; + else { + x += 1.0; + e -= 15; + } + x = ldexp(x, e); + + if (sign) + x = -x; + + return x; +} + +double _PyFloat_Unpack4(const unsigned char *p, int le) { if (float_format == unknown_format) {