diff -r 588fcf36c975 Doc/library/struct.rst
--- a/Doc/library/struct.rst Sun May 05 22:16:03 2013 -0500
+++ b/Doc/library/struct.rst Mon May 06 12:59:41 2013 +0200
@@ -204,6 +204,8 @@
+--------+--------------------------+--------------------+----------------+------------+
| ``N`` | :c:type:`size_t` | integer | | \(4) |
+--------+--------------------------+--------------------+----------------+------------+
+| ``e`` | \(7) | float | 2 | \(5) |
++--------+--------------------------+--------------------+----------------+------------+
| ``f`` | :c:type:`float` | float | 4 | \(5) |
+--------+--------------------------+--------------------+----------------+------------+
| ``d`` | :c:type:`double` | float | 8 | \(5) |
@@ -245,9 +247,10 @@
fits your application.
(5)
- 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.
(6)
The ``'P'`` format character is only available for the native byte ordering
@@ -256,6 +259,11 @@
on the host system. The struct module does not interpret this as native
ordering, so the ``'P'`` format is not available.
+(7)
+ 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 for math
+ operations.
+
A format character may be preceded by an integral repeat count. For example,
the format string ``'4h'`` means exactly the same as ``'hhhh'``.
@@ -416,4 +424,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 588fcf36c975 Include/floatobject.h
--- a/Include/floatobject.h Sun May 05 22:16:03 2013 -0500
+++ b/Include/floatobject.h Mon May 06 12:59:41 2013 +0200
@@ -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 588fcf36c975 Lib/test/test_struct.py
--- a/Lib/test/test_struct.py Sun May 05 22:16:03 2013 -0500
+++ b/Lib/test/test_struct.py Mon May 06 12:59:41 2013 +0200
@@ -1,5 +1,6 @@
from collections import abc
import array
+import math
import operator
import unittest
import struct
@@ -367,8 +368,6 @@
# SF bug 705836. "f" had a severe rounding bug, where a carry
# from the low-order discarded bits could propagate into the exponent
# field, causing the result to be wrong by a factor of 2.
- import math
-
for base in range(1, 33):
# smaller <- largest representable float less than base.
delta = 0.5
@@ -665,6 +664,96 @@
self.assertRaises(StopIteration, next, it)
+ def test_half_float(self):
+ # 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():
support.run_unittest(__name__)
diff -r 588fcf36c975 Modules/_struct.c
--- a/Modules/_struct.c Sun May 05 22:16:03 2013 -0500
+++ b/Modules/_struct.c Mon May 06 12:59:41 2013 +0200
@@ -266,6 +266,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 */
@@ -471,6 +483,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;
@@ -681,6 +700,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);
@@ -743,6 +776,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},
@@ -826,6 +860,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);
@@ -922,6 +962,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);
@@ -972,6 +1024,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}
@@ -1054,6 +1107,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);
@@ -1142,6 +1201,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);
@@ -1182,6 +1253,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}
@@ -2222,7 +2294,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 cases (only available in native format):\n\
diff -r 588fcf36c975 Objects/floatobject.c
--- a/Objects/floatobject.c Sun May 05 22:16:03 2013 -0500
+++ b/Objects/floatobject.c Mon May 06 12:59:41 2013 +0200
@@ -1944,8 +1944,115 @@
/*----------------------------------------------------------------------------
- * _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 (x < 0.0 || (x == 0.0 && copysign(1.0, x) == -1.0)) {
+ sign = 1;
+ x = -x;
+ }
+ else {
+ sign = 0;
+ }
+
+ if (x == 0.0) {
+ e = 0;
+ bits = 0;
+ }
+ else if (Py_IS_INFINITY(x)) {
+ e = 0x1f;
+ bits = 0;
+ }
+ else if (Py_IS_NAN(x)) {
+ e = 0x1f;
+ bits = 1;
+ }
+ else {
+ f = frexp(x, &e);
+ if (f < 0.5 || f >= 1.0) {
+ PyErr_SetString(PyExc_SystemError,
+ "frexp() result out of range");
+ return -1;
+ }
+
+ /* Normalize f to be in the range [1.0, 2.0) */
+ f *= 2.0;
+ e--;
+
+ if (e >= 16) {
+ goto Overflow;
+ }
+ else if (e < -25) {
+ /* |x| < 2**-25. Underflow to zero. */
+ f = 0.0;
+ e = 0;
+ }
+ else if (e < -14) {
+ /* |x| < 2**-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 == 1))) {
+ 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);
+
+ /* Write out result. */
+ if (le) {
+ p += 1;
+ incr = -1;
+ }
+
+ /* 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)
{
@@ -2181,6 +2288,76 @@
}
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) {
+#ifdef PY_NO_SHORT_FLOAT_REPR
+ if (f == 0) {
+ /* Infinity */
+ return sign ? -Py_HUGE_VAL : Py_HUGE_VAL;
+ }
+ else {
+ /* NaN */
+#ifdef Py_NAN
+ return sign ? -Py_NAN : Py_NAN;
+#else
+ PyErr_SetString(
+ PyExc_ValueError,
+ "can't unpack IEEE 754 NaN "
+ "on platform that does not support NaNs");
+ return -1;
+#endif /* #ifdef Py_NAN */
+ }
+#else
+ if (f == 0) {
+ /* Infinity */
+ return _Py_dg_infinity(sign);
+ }
+ else {
+ /* NaN */
+ return _Py_dg_stdnan(sign);
+ }
+#endif /* #ifdef PY_NO_SHORT_FLOAT_REPR */
+ }
+
+ 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) {