Skip to content

Commit ba283e2

Browse files
author
Michael W. Hudson
committed
This is my patch:
[ 1181301 ] make float packing copy bytes when they can which hasn't been reviewed, despite numerous threats to check it in anyway if noone reviews it. Please read the diff on the checkin list, at least! The basic idea is to examine the bytes of some 'probe values' to see if the current platform is a IEEE 754-ish platform, and if so _PyFloat_{Pack,Unpack}{4,8} just copy bytes around. The rest is hair for testing, and tests.
1 parent ff52286 commit ba283e2

File tree

5 files changed

+639
-251
lines changed

5 files changed

+639
-251
lines changed

Include/floatobject.h

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,18 @@ PyAPI_FUNC(void) PyFloat_AsString(char*, PyFloatObject *v);
5555
* routines produce a C double from such a string. The suffix (4 or 8)
5656
* specifies the number of bytes in the string.
5757
*
58-
* Excepting NaNs and infinities (which aren't handled correctly), the 4-
59-
* byte format is identical to the IEEE-754 single precision format, and
60-
* the 8-byte format to the IEEE-754 double precision format. On non-
61-
* IEEE platforms with more precision, or larger dynamic range, than
62-
* 754 supports, not all values can be packed; on non-IEEE platforms with
63-
* less precision, or smaller dynamic range, not all values can be
64-
* unpacked. What happens in such cases is partly accidental (alas).
58+
* On platforms that appear to use (see _PyFloat_Init()) IEEE-754 formats
59+
* these functions work by copying bits. On other platforms, the formats the
60+
* 4- byte format is identical to the IEEE-754 single precision format, and
61+
* the 8-byte format to the IEEE-754 double precision format, although the
62+
* packing of INFs and NaNs (if such things exist on the platform) isn't
63+
* handled correctly, and attempting to unpack a string containing an IEEE
64+
* INF or NaN will raise an exception.
65+
*
66+
* On non-IEEE platforms with more precision, or larger dynamic range, than
67+
* 754 supports, not all values can be packed; on non-IEEE platforms with less
68+
* precision, or smaller dynamic range, not all values can be unpacked. What
69+
* happens in such cases is partly accidental (alas).
6570
*/
6671

6772
/* The pack routines write 4 or 8 bytes, starting at p. le is a bool
@@ -70,8 +75,9 @@ PyAPI_FUNC(void) PyFloat_AsString(char*, PyFloatObject *v);
7075
* first, at p).
7176
* Return value: 0 if all is OK, -1 if error (and an exception is
7277
* set, most likely OverflowError).
73-
* Bug: What this does is undefined if x is a NaN or infinity.
74-
* Bug: -0.0 and +0.0 produce the same string.
78+
* There are two problems on non-IEEE platforms:
79+
* 1): What this does is undefined if x is a NaN or infinity.
80+
* 2): -0.0 and +0.0 produce the same string.
7581
*/
7682
PyAPI_FUNC(int) _PyFloat_Pack4(double x, unsigned char *p, int le);
7783
PyAPI_FUNC(int) _PyFloat_Pack8(double x, unsigned char *p, int le);
@@ -81,9 +87,8 @@ PyAPI_FUNC(int) _PyFloat_Pack8(double x, unsigned char *p, int le);
8187
* last, at p+3 or p+7), false if big-endian (exponent first, at p).
8288
* Return value: The unpacked double. On error, this is -1.0 and
8389
* PyErr_Occurred() is true (and an exception is set, most likely
84-
* OverflowError).
85-
* Bug: What this does is undefined if the string represents a NaN or
86-
* infinity.
90+
* OverflowError). Note that on a non-IEEE platform this will refuse
91+
* to unpack a string that represents a NaN or infinity.
8792
*/
8893
PyAPI_FUNC(double) _PyFloat_Unpack4(const unsigned char *p, int le);
8994
PyAPI_FUNC(double) _PyFloat_Unpack8(const unsigned char *p, int le);

Include/pythonrun.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ PyAPI_FUNC(void) _PyExc_Init(void);
105105
PyAPI_FUNC(void) _PyImportHooks_Init(void);
106106
PyAPI_FUNC(int) _PyFrame_Init(void);
107107
PyAPI_FUNC(int) _PyInt_Init(void);
108+
PyAPI_FUNC(void) _PyFloat_Init(void);
108109

109110
/* Various internal finalizers */
110111
PyAPI_FUNC(void) _PyExc_Fini(void);

Lib/test/test_float.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
2+
import unittest, struct
3+
from test import test_support
4+
5+
class FormatFunctionsTestCase(unittest.TestCase):
6+
7+
def setUp(self):
8+
self.save_formats = {'double':float.__getformat__('double'),
9+
'float':float.__getformat__('float')}
10+
11+
def tearDown(self):
12+
float.__setformat__('double', self.save_formats['double'])
13+
float.__setformat__('float', self.save_formats['float'])
14+
15+
def test_getformat(self):
16+
self.assert_(float.__getformat__('double') in
17+
['unknown', 'IEEE, big-endian', 'IEEE, little-endian'])
18+
self.assert_(float.__getformat__('float') in
19+
['unknown', 'IEEE, big-endian', 'IEEE, little-endian'])
20+
self.assertRaises(ValueError, float.__getformat__, 'chicken')
21+
self.assertRaises(TypeError, float.__getformat__, 1)
22+
23+
def test_setformat(self):
24+
for t in 'double', 'float':
25+
float.__setformat__(t, 'unknown')
26+
if self.save_formats[t] == 'IEEE, big-endian':
27+
self.assertRaises(ValueError, float.__setformat__,
28+
t, 'IEEE, little-endian')
29+
elif self.save_formats[t] == 'IEEE, little-endian':
30+
self.assertRaises(ValueError, float.__setformat__,
31+
t, 'IEEE, big-endian')
32+
else:
33+
self.assertRaises(ValueError, float.__setformat__,
34+
t, 'IEEE, big-endian')
35+
self.assertRaises(ValueError, float.__setformat__,
36+
t, 'IEEE, little-endian')
37+
self.assertRaises(ValueError, float.__setformat__,
38+
t, 'chicken')
39+
self.assertRaises(ValueError, float.__setformat__,
40+
'chicken', 'unknown')
41+
42+
BE_DOUBLE_INF = '\x7f\xf0\x00\x00\x00\x00\x00\x00'
43+
LE_DOUBLE_INF = ''.join(reversed(BE_DOUBLE_INF))
44+
BE_DOUBLE_NAN = '\x7f\xf8\x00\x00\x00\x00\x00\x00'
45+
LE_DOUBLE_NAN = ''.join(reversed(BE_DOUBLE_NAN))
46+
47+
BE_FLOAT_INF = '\x7f\x80\x00\x00'
48+
LE_FLOAT_INF = ''.join(reversed(BE_FLOAT_INF))
49+
BE_FLOAT_NAN = '\x7f\xc0\x00\x00'
50+
LE_FLOAT_NAN = ''.join(reversed(BE_FLOAT_NAN))
51+
52+
# on non-IEEE platforms, attempting to unpack a bit pattern
53+
# representing an infinity or a NaN should raise an exception.
54+
55+
class UnknownFormatTestCase(unittest.TestCase):
56+
def setUp(self):
57+
self.save_formats = {'double':float.__getformat__('double'),
58+
'float':float.__getformat__('float')}
59+
float.__setformat__('double', 'unknown')
60+
float.__setformat__('float', 'unknown')
61+
62+
def tearDown(self):
63+
float.__setformat__('double', self.save_formats['double'])
64+
float.__setformat__('float', self.save_formats['float'])
65+
66+
def test_double_specials_dont_unpack(self):
67+
for fmt, data in [('>d', BE_DOUBLE_INF),
68+
('>d', BE_DOUBLE_NAN),
69+
('<d', LE_DOUBLE_INF),
70+
('<d', LE_DOUBLE_NAN)]:
71+
self.assertRaises(ValueError, struct.unpack, fmt, data)
72+
73+
def test_float_specials_dont_unpack(self):
74+
for fmt, data in [('>f', BE_FLOAT_INF),
75+
('>f', BE_FLOAT_NAN),
76+
('<f', LE_FLOAT_INF),
77+
('<f', LE_FLOAT_NAN)]:
78+
self.assertRaises(ValueError, struct.unpack, fmt, data)
79+
80+
81+
# on an IEEE platform, all we guarantee is that bit patterns
82+
# representing infinities or NaNs do not raise an exception; all else
83+
# is accident (today).
84+
85+
class IEEEFormatTestCase(unittest.TestCase):
86+
if float.__getformat__("double").startswith("IEEE"):
87+
def test_double_specials_do_unpack(self):
88+
for fmt, data in [('>d', BE_DOUBLE_INF),
89+
('>d', BE_DOUBLE_NAN),
90+
('<d', LE_DOUBLE_INF),
91+
('<d', LE_DOUBLE_NAN)]:
92+
struct.unpack(fmt, data)
93+
94+
if float.__getformat__("float").startswith("IEEE"):
95+
def test_float_specials_do_unpack(self):
96+
for fmt, data in [('>f', BE_FLOAT_INF),
97+
('>f', BE_FLOAT_NAN),
98+
('<f', LE_FLOAT_INF),
99+
('<f', LE_FLOAT_NAN)]:
100+
struct.unpack(fmt, data)
101+
102+
103+
def test_main():
104+
test_support.run_unittest(
105+
FormatFunctionsTestCase,
106+
UnknownFormatTestCase,
107+
IEEEFormatTestCase)
108+
109+
if __name__ == '__main__':
110+
test_main()

0 commit comments

Comments
 (0)