Merged revisions 64974 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk ........ r64974 | mark.dickinson | 2008-07-15 20:08:33 +0100 (Tue, 15 Jul 2008) | 3 lines Issue #3008: add instance method float.hex and class method float.fromhex to convert floats to and from hexadecimal strings respectively. ........
This commit is contained in:
parent
0c474d01a1
commit
65fe25e597
|
@ -423,6 +423,71 @@ Notes:
|
|||
|
||||
.. _typeiter:
|
||||
|
||||
|
||||
Additional Methods on Float
|
||||
---------------------------
|
||||
|
||||
The float type has some additional methods to support conversion to
|
||||
and from hexadecimal strings. Since Python's floats are stored
|
||||
internally as binary numbers, converting a float to or from a
|
||||
*decimal* string usually involves a small rounding error. In
|
||||
contrast, hexadecimal strings allow exact representation and
|
||||
specification of floating-point numbers. This can be useful when
|
||||
debugging, and in numerical work.
|
||||
|
||||
|
||||
.. method:: float.hex()
|
||||
|
||||
Return a representation of a floating-point number as a hexadecimal
|
||||
string. For finite floating-point numbers, this representation
|
||||
will always include a leading ``0x`` and a trailing ``p`` and
|
||||
exponent.
|
||||
|
||||
|
||||
.. method:: float.fromhex(s)
|
||||
|
||||
Class method to return the float represented by a hexadecimal
|
||||
string *s*. The string *s* may have leading and trailing
|
||||
whitespace.
|
||||
|
||||
|
||||
Note that :meth:`float.hex` is an instance method, while
|
||||
:meth:`float.fromhex` is a class method.
|
||||
|
||||
A hexadecimal string takes the form::
|
||||
|
||||
[sign] ['0x'] integer ['.' fraction] ['p' exponent]
|
||||
|
||||
where the optional ``sign`` may by either ``+`` or ``-``, ``integer``
|
||||
and ``fraction`` are strings of hexadecimal digits, and ``exponent``
|
||||
is a decimal integer with an optional leading sign. Case is not
|
||||
significant, and there must be at least one hexadecimal digit in
|
||||
either the integer or the fraction. This syntax is similar to the
|
||||
syntax specified in section 6.4.4.2 of the C99 standard, and also to
|
||||
the syntax used in Java 1.5 onwards. In particular, the output of
|
||||
:meth:`float.hex` is usable as a hexadecimal floating-point literal in
|
||||
C or Java code, and hexadecimal strings produced by C's ``%a`` format
|
||||
character or Java's ``Double.toHexString`` are accepted by
|
||||
:meth:`float.fromhex`.
|
||||
|
||||
|
||||
Note that the exponent is written in decimal rather than hexadecimal,
|
||||
and that it gives the power of 2 by which to multiply the coefficient.
|
||||
For example, the hexadecimal string ``0x3.a7p10`` represents the
|
||||
floating-point number ``(3 + 10./16 + 7./16**2) * 2.0**10``, or
|
||||
``3740.0``::
|
||||
|
||||
>>> float.fromhex('0x3.a7p10')
|
||||
3740.0
|
||||
|
||||
|
||||
Applying the reverse conversion to ``3740.0`` gives a different
|
||||
hexadecimal string representing the same number::
|
||||
|
||||
>>> float.hex(3740.0)
|
||||
'0x1.d380000000000p+11'
|
||||
|
||||
|
||||
Iterator Types
|
||||
==============
|
||||
|
||||
|
|
|
@ -1397,6 +1397,11 @@ Here are all of the changes that Python 2.6 makes to the core Python language.
|
|||
:func:`isnan`, return true if their floating-point argument is
|
||||
infinite or Not A Number. (:issue:`1640`)
|
||||
|
||||
The float type has a new instance method :meth:`float.hex` and a
|
||||
corresponding new class method :meth:`float.fromhex` to convert
|
||||
floating-point numbers to and from hexadecimal strings,
|
||||
respectively. (:issue:`3008`)
|
||||
|
||||
* The :mod:`math` module has a number of new functions, and the existing
|
||||
functions have been improved to give more consistent behaviour
|
||||
across platforms, especially with respect to handling of
|
||||
|
|
|
@ -3,7 +3,7 @@ import unittest, struct
|
|||
import os
|
||||
from test import support
|
||||
import math
|
||||
from math import isinf, isnan
|
||||
from math import isinf, isnan, copysign, ldexp
|
||||
import operator
|
||||
|
||||
INF = float("inf")
|
||||
|
@ -358,6 +358,389 @@ class InfNanTest(unittest.TestCase):
|
|||
self.failIf(NAN.is_inf())
|
||||
self.failIf((0.).is_inf())
|
||||
|
||||
fromHex = float.fromhex
|
||||
toHex = float.hex
|
||||
class HexFloatTestCase(unittest.TestCase):
|
||||
MAX = fromHex('0x.fffffffffffff8p+1024') # max normal
|
||||
MIN = fromHex('0x1p-1022') # min normal
|
||||
TINY = fromHex('0x0.0000000000001p-1022') # min subnormal
|
||||
EPS = fromHex('0x0.0000000000001p0') # diff between 1.0 and next float up
|
||||
|
||||
def identical(self, x, y):
|
||||
# check that floats x and y are identical, or that both
|
||||
# are NaNs
|
||||
if isnan(x) or isnan(y):
|
||||
if isnan(x) == isnan(y):
|
||||
return
|
||||
elif x == y and (x != 0.0 or copysign(1.0, x) == copysign(1.0, y)):
|
||||
return
|
||||
self.fail('%r not identical to %r' % (x, y))
|
||||
|
||||
def test_ends(self):
|
||||
self.identical(self.MIN, 2.**-1022)
|
||||
self.identical(self.TINY, 2.**-1074)
|
||||
self.identical(self.EPS, 2.**-52)
|
||||
self.identical(self.MAX, 2.*(2.**1023 - 2.**970))
|
||||
|
||||
def test_invalid_inputs(self):
|
||||
invalid_inputs = [
|
||||
'infi', # misspelt infinities and nans
|
||||
'-Infinit',
|
||||
'++inf',
|
||||
'-+Inf',
|
||||
'--nan',
|
||||
'+-NaN',
|
||||
'snan',
|
||||
'NaNs',
|
||||
'nna',
|
||||
'0xnan',
|
||||
'',
|
||||
' ',
|
||||
'x1.0p0',
|
||||
'0xX1.0p0',
|
||||
'+ 0x1.0p0', # internal whitespace
|
||||
'- 0x1.0p0',
|
||||
'0 x1.0p0',
|
||||
'0x 1.0p0',
|
||||
'0x1 2.0p0',
|
||||
'+0x1 .0p0',
|
||||
'0x1. 0p0',
|
||||
'-0x1.0 1p0',
|
||||
'-0x1.0 p0',
|
||||
'+0x1.0p +0',
|
||||
'0x1.0p -0',
|
||||
'0x1.0p 0',
|
||||
'+0x1.0p+ 0',
|
||||
'-0x1.0p- 0',
|
||||
'++0x1.0p-0', # double signs
|
||||
'--0x1.0p0',
|
||||
'+-0x1.0p+0',
|
||||
'-+0x1.0p0',
|
||||
'0x1.0p++0',
|
||||
'+0x1.0p+-0',
|
||||
'-0x1.0p-+0',
|
||||
'0x1.0p--0',
|
||||
'0x1.0.p0',
|
||||
'0x.p0', # no hex digits before or after point
|
||||
'0x1,p0', # wrong decimal point character
|
||||
'0x1pa',
|
||||
'0x1p\uff10', # fullwidth Unicode digits
|
||||
'\uff10x1p0',
|
||||
'0x\uff11p0',
|
||||
'0x1.\uff10p0',
|
||||
'0x1p0 \n 0x2p0',
|
||||
'0x1p0\0 0x1p0', # embedded null byte is not end of string
|
||||
]
|
||||
for x in invalid_inputs:
|
||||
self.assertRaises(ValueError, fromHex, x)
|
||||
|
||||
|
||||
def test_from_hex(self):
|
||||
MIN = self.MIN;
|
||||
MAX = self.MAX;
|
||||
TINY = self.TINY;
|
||||
EPS = self.EPS;
|
||||
|
||||
# two spellings of infinity, with optional signs; case-insensitive
|
||||
self.identical(fromHex('inf'), INF)
|
||||
self.identical(fromHex('+Inf'), INF)
|
||||
self.identical(fromHex('-INF'), -INF)
|
||||
self.identical(fromHex('iNf'), INF)
|
||||
self.identical(fromHex('Infinity'), INF)
|
||||
self.identical(fromHex('+INFINITY'), INF)
|
||||
self.identical(fromHex('-infinity'), -INF)
|
||||
self.identical(fromHex('-iNFiNitY'), -INF)
|
||||
|
||||
# nans with optional sign; case insensitive
|
||||
self.identical(fromHex('nan'), NAN)
|
||||
self.identical(fromHex('+NaN'), NAN)
|
||||
self.identical(fromHex('-NaN'), NAN)
|
||||
self.identical(fromHex('-nAN'), NAN)
|
||||
|
||||
# variations in input format
|
||||
self.identical(fromHex('1'), 1.0)
|
||||
self.identical(fromHex('+1'), 1.0)
|
||||
self.identical(fromHex('1.'), 1.0)
|
||||
self.identical(fromHex('1.0'), 1.0)
|
||||
self.identical(fromHex('1.0p0'), 1.0)
|
||||
self.identical(fromHex('01'), 1.0)
|
||||
self.identical(fromHex('01.'), 1.0)
|
||||
self.identical(fromHex('0x1'), 1.0)
|
||||
self.identical(fromHex('0x1.'), 1.0)
|
||||
self.identical(fromHex('0x1.0'), 1.0)
|
||||
self.identical(fromHex('+0x1.0'), 1.0)
|
||||
self.identical(fromHex('0x1p0'), 1.0)
|
||||
self.identical(fromHex('0X1p0'), 1.0)
|
||||
self.identical(fromHex('0X1P0'), 1.0)
|
||||
self.identical(fromHex('0x1P0'), 1.0)
|
||||
self.identical(fromHex('0x1.p0'), 1.0)
|
||||
self.identical(fromHex('0x1.0p0'), 1.0)
|
||||
self.identical(fromHex('0x.1p4'), 1.0)
|
||||
self.identical(fromHex('0x.1p04'), 1.0)
|
||||
self.identical(fromHex('0x.1p004'), 1.0)
|
||||
self.identical(fromHex('0x1p+0'), 1.0)
|
||||
self.identical(fromHex('0x1P-0'), 1.0)
|
||||
self.identical(fromHex('+0x1p0'), 1.0)
|
||||
self.identical(fromHex('0x01p0'), 1.0)
|
||||
self.identical(fromHex('0x1p00'), 1.0)
|
||||
self.identical(fromHex(' 0x1p0 '), 1.0)
|
||||
self.identical(fromHex('\n 0x1p0'), 1.0)
|
||||
self.identical(fromHex('0x1p0 \t'), 1.0)
|
||||
self.identical(fromHex('0xap0'), 10.0)
|
||||
self.identical(fromHex('0xAp0'), 10.0)
|
||||
self.identical(fromHex('0xaP0'), 10.0)
|
||||
self.identical(fromHex('0xAP0'), 10.0)
|
||||
self.identical(fromHex('0xbep0'), 190.0)
|
||||
self.identical(fromHex('0xBep0'), 190.0)
|
||||
self.identical(fromHex('0xbEp0'), 190.0)
|
||||
self.identical(fromHex('0XBE0P-4'), 190.0)
|
||||
self.identical(fromHex('0xBEp0'), 190.0)
|
||||
self.identical(fromHex('0xB.Ep4'), 190.0)
|
||||
self.identical(fromHex('0x.BEp8'), 190.0)
|
||||
self.identical(fromHex('0x.0BEp12'), 190.0)
|
||||
|
||||
# moving the point around
|
||||
pi = fromHex('0x1.921fb54442d18p1')
|
||||
self.identical(fromHex('0x.006487ed5110b46p11'), pi)
|
||||
self.identical(fromHex('0x.00c90fdaa22168cp10'), pi)
|
||||
self.identical(fromHex('0x.01921fb54442d18p9'), pi)
|
||||
self.identical(fromHex('0x.03243f6a8885a3p8'), pi)
|
||||
self.identical(fromHex('0x.06487ed5110b46p7'), pi)
|
||||
self.identical(fromHex('0x.0c90fdaa22168cp6'), pi)
|
||||
self.identical(fromHex('0x.1921fb54442d18p5'), pi)
|
||||
self.identical(fromHex('0x.3243f6a8885a3p4'), pi)
|
||||
self.identical(fromHex('0x.6487ed5110b46p3'), pi)
|
||||
self.identical(fromHex('0x.c90fdaa22168cp2'), pi)
|
||||
self.identical(fromHex('0x1.921fb54442d18p1'), pi)
|
||||
self.identical(fromHex('0x3.243f6a8885a3p0'), pi)
|
||||
self.identical(fromHex('0x6.487ed5110b46p-1'), pi)
|
||||
self.identical(fromHex('0xc.90fdaa22168cp-2'), pi)
|
||||
self.identical(fromHex('0x19.21fb54442d18p-3'), pi)
|
||||
self.identical(fromHex('0x32.43f6a8885a3p-4'), pi)
|
||||
self.identical(fromHex('0x64.87ed5110b46p-5'), pi)
|
||||
self.identical(fromHex('0xc9.0fdaa22168cp-6'), pi)
|
||||
self.identical(fromHex('0x192.1fb54442d18p-7'), pi)
|
||||
self.identical(fromHex('0x324.3f6a8885a3p-8'), pi)
|
||||
self.identical(fromHex('0x648.7ed5110b46p-9'), pi)
|
||||
self.identical(fromHex('0xc90.fdaa22168cp-10'), pi)
|
||||
self.identical(fromHex('0x1921.fb54442d18p-11'), pi)
|
||||
# ...
|
||||
self.identical(fromHex('0x1921fb54442d1.8p-47'), pi)
|
||||
self.identical(fromHex('0x3243f6a8885a3p-48'), pi)
|
||||
self.identical(fromHex('0x6487ed5110b46p-49'), pi)
|
||||
self.identical(fromHex('0xc90fdaa22168cp-50'), pi)
|
||||
self.identical(fromHex('0x1921fb54442d18p-51'), pi)
|
||||
self.identical(fromHex('0x3243f6a8885a30p-52'), pi)
|
||||
self.identical(fromHex('0x6487ed5110b460p-53'), pi)
|
||||
self.identical(fromHex('0xc90fdaa22168c0p-54'), pi)
|
||||
self.identical(fromHex('0x1921fb54442d180p-55'), pi)
|
||||
|
||||
|
||||
# results that should overflow...
|
||||
self.assertRaises(OverflowError, fromHex, '-0x1p1024')
|
||||
self.assertRaises(OverflowError, fromHex, '0x1p+1025')
|
||||
self.assertRaises(OverflowError, fromHex, '+0X1p1030')
|
||||
self.assertRaises(OverflowError, fromHex, '-0x1p+1100')
|
||||
self.assertRaises(OverflowError, fromHex, '0X1p123456789123456789')
|
||||
self.assertRaises(OverflowError, fromHex, '+0X.8p+1025')
|
||||
self.assertRaises(OverflowError, fromHex, '+0x0.8p1025')
|
||||
self.assertRaises(OverflowError, fromHex, '-0x0.4p1026')
|
||||
self.assertRaises(OverflowError, fromHex, '0X2p+1023')
|
||||
self.assertRaises(OverflowError, fromHex, '0x2.p1023')
|
||||
self.assertRaises(OverflowError, fromHex, '-0x2.0p+1023')
|
||||
self.assertRaises(OverflowError, fromHex, '+0X4p+1022')
|
||||
self.assertRaises(OverflowError, fromHex, '0x1.ffffffffffffffp+1023')
|
||||
self.assertRaises(OverflowError, fromHex, '-0X1.fffffffffffff9p1023')
|
||||
self.assertRaises(OverflowError, fromHex, '0X1.fffffffffffff8p1023')
|
||||
self.assertRaises(OverflowError, fromHex, '+0x3.fffffffffffffp1022')
|
||||
self.assertRaises(OverflowError, fromHex, '0x3fffffffffffffp+970')
|
||||
self.assertRaises(OverflowError, fromHex, '0x10000000000000000p960')
|
||||
self.assertRaises(OverflowError, fromHex, '-0Xffffffffffffffffp960')
|
||||
|
||||
# ...and those that round to +-max float
|
||||
self.identical(fromHex('+0x1.fffffffffffffp+1023'), MAX)
|
||||
self.identical(fromHex('-0X1.fffffffffffff7p1023'), -MAX)
|
||||
self.identical(fromHex('0X1.fffffffffffff7fffffffffffffp1023'), MAX)
|
||||
|
||||
# zeros
|
||||
self.identical(fromHex('0x0p0'), 0.0)
|
||||
self.identical(fromHex('0x0p1000'), 0.0)
|
||||
self.identical(fromHex('-0x0p1023'), -0.0)
|
||||
self.identical(fromHex('0X0p1024'), 0.0)
|
||||
self.identical(fromHex('-0x0p1025'), -0.0)
|
||||
self.identical(fromHex('0X0p2000'), 0.0)
|
||||
self.identical(fromHex('0x0p123456789123456789'), 0.0)
|
||||
self.identical(fromHex('-0X0p-0'), -0.0)
|
||||
self.identical(fromHex('-0X0p-1000'), -0.0)
|
||||
self.identical(fromHex('0x0p-1023'), 0.0)
|
||||
self.identical(fromHex('-0X0p-1024'), -0.0)
|
||||
self.identical(fromHex('-0x0p-1025'), -0.0)
|
||||
self.identical(fromHex('-0x0p-1072'), -0.0)
|
||||
self.identical(fromHex('0X0p-1073'), 0.0)
|
||||
self.identical(fromHex('-0x0p-1074'), -0.0)
|
||||
self.identical(fromHex('0x0p-1075'), 0.0)
|
||||
self.identical(fromHex('0X0p-1076'), 0.0)
|
||||
self.identical(fromHex('-0X0p-2000'), -0.0)
|
||||
self.identical(fromHex('-0x0p-123456789123456789'), -0.0)
|
||||
|
||||
# values that should underflow to 0
|
||||
self.identical(fromHex('0X1p-1075'), 0.0)
|
||||
self.identical(fromHex('-0X1p-1075'), -0.0)
|
||||
self.identical(fromHex('-0x1p-123456789123456789'), -0.0)
|
||||
self.identical(fromHex('0x1.00000000000000001p-1075'), TINY)
|
||||
self.identical(fromHex('-0x1.1p-1075'), -TINY)
|
||||
self.identical(fromHex('0x1.fffffffffffffffffp-1075'), TINY)
|
||||
|
||||
# check round-half-even is working correctly near 0 ...
|
||||
self.identical(fromHex('0x1p-1076'), 0.0)
|
||||
self.identical(fromHex('0X2p-1076'), 0.0)
|
||||
self.identical(fromHex('0X3p-1076'), TINY)
|
||||
self.identical(fromHex('0x4p-1076'), TINY)
|
||||
self.identical(fromHex('0X5p-1076'), TINY)
|
||||
self.identical(fromHex('0X6p-1076'), 2*TINY)
|
||||
self.identical(fromHex('0x7p-1076'), 2*TINY)
|
||||
self.identical(fromHex('0X8p-1076'), 2*TINY)
|
||||
self.identical(fromHex('0X9p-1076'), 2*TINY)
|
||||
self.identical(fromHex('0xap-1076'), 2*TINY)
|
||||
self.identical(fromHex('0Xbp-1076'), 3*TINY)
|
||||
self.identical(fromHex('0xcp-1076'), 3*TINY)
|
||||
self.identical(fromHex('0Xdp-1076'), 3*TINY)
|
||||
self.identical(fromHex('0Xep-1076'), 4*TINY)
|
||||
self.identical(fromHex('0xfp-1076'), 4*TINY)
|
||||
self.identical(fromHex('0x10p-1076'), 4*TINY)
|
||||
self.identical(fromHex('-0x1p-1076'), -0.0)
|
||||
self.identical(fromHex('-0X2p-1076'), -0.0)
|
||||
self.identical(fromHex('-0x3p-1076'), -TINY)
|
||||
self.identical(fromHex('-0X4p-1076'), -TINY)
|
||||
self.identical(fromHex('-0x5p-1076'), -TINY)
|
||||
self.identical(fromHex('-0x6p-1076'), -2*TINY)
|
||||
self.identical(fromHex('-0X7p-1076'), -2*TINY)
|
||||
self.identical(fromHex('-0X8p-1076'), -2*TINY)
|
||||
self.identical(fromHex('-0X9p-1076'), -2*TINY)
|
||||
self.identical(fromHex('-0Xap-1076'), -2*TINY)
|
||||
self.identical(fromHex('-0xbp-1076'), -3*TINY)
|
||||
self.identical(fromHex('-0xcp-1076'), -3*TINY)
|
||||
self.identical(fromHex('-0Xdp-1076'), -3*TINY)
|
||||
self.identical(fromHex('-0xep-1076'), -4*TINY)
|
||||
self.identical(fromHex('-0Xfp-1076'), -4*TINY)
|
||||
self.identical(fromHex('-0X10p-1076'), -4*TINY)
|
||||
|
||||
# ... and near MIN ...
|
||||
self.identical(fromHex('0x0.ffffffffffffd6p-1022'), MIN-3*TINY)
|
||||
self.identical(fromHex('0x0.ffffffffffffd8p-1022'), MIN-2*TINY)
|
||||
self.identical(fromHex('0x0.ffffffffffffdap-1022'), MIN-2*TINY)
|
||||
self.identical(fromHex('0x0.ffffffffffffdcp-1022'), MIN-2*TINY)
|
||||
self.identical(fromHex('0x0.ffffffffffffdep-1022'), MIN-2*TINY)
|
||||
self.identical(fromHex('0x0.ffffffffffffe0p-1022'), MIN-2*TINY)
|
||||
self.identical(fromHex('0x0.ffffffffffffe2p-1022'), MIN-2*TINY)
|
||||
self.identical(fromHex('0x0.ffffffffffffe4p-1022'), MIN-2*TINY)
|
||||
self.identical(fromHex('0x0.ffffffffffffe6p-1022'), MIN-2*TINY)
|
||||
self.identical(fromHex('0x0.ffffffffffffe8p-1022'), MIN-2*TINY)
|
||||
self.identical(fromHex('0x0.ffffffffffffeap-1022'), MIN-TINY)
|
||||
self.identical(fromHex('0x0.ffffffffffffecp-1022'), MIN-TINY)
|
||||
self.identical(fromHex('0x0.ffffffffffffeep-1022'), MIN-TINY)
|
||||
self.identical(fromHex('0x0.fffffffffffff0p-1022'), MIN-TINY)
|
||||
self.identical(fromHex('0x0.fffffffffffff2p-1022'), MIN-TINY)
|
||||
self.identical(fromHex('0x0.fffffffffffff4p-1022'), MIN-TINY)
|
||||
self.identical(fromHex('0x0.fffffffffffff6p-1022'), MIN-TINY)
|
||||
self.identical(fromHex('0x0.fffffffffffff8p-1022'), MIN)
|
||||
self.identical(fromHex('0x0.fffffffffffffap-1022'), MIN)
|
||||
self.identical(fromHex('0x0.fffffffffffffcp-1022'), MIN)
|
||||
self.identical(fromHex('0x0.fffffffffffffep-1022'), MIN)
|
||||
self.identical(fromHex('0x1.00000000000000p-1022'), MIN)
|
||||
self.identical(fromHex('0x1.00000000000002p-1022'), MIN)
|
||||
self.identical(fromHex('0x1.00000000000004p-1022'), MIN)
|
||||
self.identical(fromHex('0x1.00000000000006p-1022'), MIN)
|
||||
self.identical(fromHex('0x1.00000000000008p-1022'), MIN)
|
||||
self.identical(fromHex('0x1.0000000000000ap-1022'), MIN+TINY)
|
||||
self.identical(fromHex('0x1.0000000000000cp-1022'), MIN+TINY)
|
||||
self.identical(fromHex('0x1.0000000000000ep-1022'), MIN+TINY)
|
||||
self.identical(fromHex('0x1.00000000000010p-1022'), MIN+TINY)
|
||||
self.identical(fromHex('0x1.00000000000012p-1022'), MIN+TINY)
|
||||
self.identical(fromHex('0x1.00000000000014p-1022'), MIN+TINY)
|
||||
self.identical(fromHex('0x1.00000000000016p-1022'), MIN+TINY)
|
||||
self.identical(fromHex('0x1.00000000000018p-1022'), MIN+2*TINY)
|
||||
|
||||
# ... and near 1.0.
|
||||
self.identical(fromHex('0x0.fffffffffffff0p0'), 1.0-EPS)
|
||||
self.identical(fromHex('0x0.fffffffffffff1p0'), 1.0-EPS)
|
||||
self.identical(fromHex('0X0.fffffffffffff2p0'), 1.0-EPS)
|
||||
self.identical(fromHex('0x0.fffffffffffff3p0'), 1.0-EPS)
|
||||
self.identical(fromHex('0X0.fffffffffffff4p0'), 1.0-EPS)
|
||||
self.identical(fromHex('0X0.fffffffffffff5p0'), 1.0-EPS/2)
|
||||
self.identical(fromHex('0X0.fffffffffffff6p0'), 1.0-EPS/2)
|
||||
self.identical(fromHex('0x0.fffffffffffff7p0'), 1.0-EPS/2)
|
||||
self.identical(fromHex('0x0.fffffffffffff8p0'), 1.0-EPS/2)
|
||||
self.identical(fromHex('0X0.fffffffffffff9p0'), 1.0-EPS/2)
|
||||
self.identical(fromHex('0X0.fffffffffffffap0'), 1.0-EPS/2)
|
||||
self.identical(fromHex('0x0.fffffffffffffbp0'), 1.0-EPS/2)
|
||||
self.identical(fromHex('0X0.fffffffffffffcp0'), 1.0)
|
||||
self.identical(fromHex('0x0.fffffffffffffdp0'), 1.0)
|
||||
self.identical(fromHex('0X0.fffffffffffffep0'), 1.0)
|
||||
self.identical(fromHex('0x0.ffffffffffffffp0'), 1.0)
|
||||
self.identical(fromHex('0X1.00000000000000p0'), 1.0)
|
||||
self.identical(fromHex('0X1.00000000000001p0'), 1.0)
|
||||
self.identical(fromHex('0x1.00000000000002p0'), 1.0)
|
||||
self.identical(fromHex('0X1.00000000000003p0'), 1.0)
|
||||
self.identical(fromHex('0x1.00000000000004p0'), 1.0)
|
||||
self.identical(fromHex('0X1.00000000000005p0'), 1.0)
|
||||
self.identical(fromHex('0X1.00000000000006p0'), 1.0)
|
||||
self.identical(fromHex('0X1.00000000000007p0'), 1.0)
|
||||
self.identical(fromHex('0x1.00000000000007ffffffffffffffffffffp0'),
|
||||
1.0)
|
||||
self.identical(fromHex('0x1.00000000000008p0'), 1.0)
|
||||
self.identical(fromHex('0x1.00000000000008000000000000000001p0'),
|
||||
1+EPS)
|
||||
self.identical(fromHex('0X1.00000000000009p0'), 1.0+EPS)
|
||||
self.identical(fromHex('0x1.0000000000000ap0'), 1.0+EPS)
|
||||
self.identical(fromHex('0x1.0000000000000bp0'), 1.0+EPS)
|
||||
self.identical(fromHex('0X1.0000000000000cp0'), 1.0+EPS)
|
||||
self.identical(fromHex('0x1.0000000000000dp0'), 1.0+EPS)
|
||||
self.identical(fromHex('0x1.0000000000000ep0'), 1.0+EPS)
|
||||
self.identical(fromHex('0X1.0000000000000fp0'), 1.0+EPS)
|
||||
self.identical(fromHex('0x1.00000000000010p0'), 1.0+EPS)
|
||||
self.identical(fromHex('0X1.00000000000011p0'), 1.0+EPS)
|
||||
self.identical(fromHex('0x1.00000000000012p0'), 1.0+EPS)
|
||||
self.identical(fromHex('0X1.00000000000013p0'), 1.0+EPS)
|
||||
self.identical(fromHex('0X1.00000000000014p0'), 1.0+EPS)
|
||||
self.identical(fromHex('0x1.00000000000015p0'), 1.0+EPS)
|
||||
self.identical(fromHex('0x1.00000000000016p0'), 1.0+EPS)
|
||||
self.identical(fromHex('0X1.00000000000017p0'), 1.0+EPS)
|
||||
self.identical(fromHex('0x1.00000000000017ffffffffffffffffffffp0'),
|
||||
1.0+EPS)
|
||||
self.identical(fromHex('0x1.00000000000018p0'), 1.0+2*EPS)
|
||||
self.identical(fromHex('0X1.00000000000018000000000000000001p0'),
|
||||
1.0+2*EPS)
|
||||
self.identical(fromHex('0x1.00000000000019p0'), 1.0+2*EPS)
|
||||
self.identical(fromHex('0X1.0000000000001ap0'), 1.0+2*EPS)
|
||||
self.identical(fromHex('0X1.0000000000001bp0'), 1.0+2*EPS)
|
||||
self.identical(fromHex('0x1.0000000000001cp0'), 1.0+2*EPS)
|
||||
self.identical(fromHex('0x1.0000000000001dp0'), 1.0+2*EPS)
|
||||
self.identical(fromHex('0x1.0000000000001ep0'), 1.0+2*EPS)
|
||||
self.identical(fromHex('0X1.0000000000001fp0'), 1.0+2*EPS)
|
||||
self.identical(fromHex('0x1.00000000000020p0'), 1.0+2*EPS)
|
||||
|
||||
def test_roundtrip(self):
|
||||
def roundtrip(x):
|
||||
return fromHex(toHex(x))
|
||||
|
||||
for x in [NAN, INF, self.MAX, self.MIN, self.MIN-self.TINY, self.TINY, 0.0]:
|
||||
self.identical(x, roundtrip(x))
|
||||
self.identical(-x, roundtrip(-x))
|
||||
|
||||
# fromHex(toHex(x)) should exactly recover x, for any non-NaN float x.
|
||||
import random
|
||||
for i in range(10000):
|
||||
e = random.randrange(-1200, 1200)
|
||||
m = random.random()
|
||||
s = random.choice([1.0, -1.0])
|
||||
try:
|
||||
x = s*ldexp(m, e)
|
||||
except OverflowError:
|
||||
pass
|
||||
else:
|
||||
self.identical(x, fromHex(toHex(x)))
|
||||
|
||||
|
||||
def test_main():
|
||||
support.run_unittest(
|
||||
|
@ -367,6 +750,7 @@ def test_main():
|
|||
FormatTestCase,
|
||||
ReprTestCase,
|
||||
InfNanTest,
|
||||
HexFloatTestCase,
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -12,6 +12,10 @@ What's new in Python 3.0b2?
|
|||
Core and Builtins
|
||||
-----------------
|
||||
|
||||
- Issue #3008: the float type has a new instance method 'float.hex'
|
||||
and a new class method 'float.fromhex' to convert floating-point
|
||||
numbers to and from hexadecimal strings, respectively.
|
||||
|
||||
- Issue #3083: Add alternate (#) formatting for bin, oct, hex output
|
||||
for str.format(). This adds the prefix 0b, 0o, or 0x, respectively.
|
||||
|
||||
|
|
|
@ -10,6 +10,11 @@
|
|||
#include <ctype.h>
|
||||
#include <float.h>
|
||||
|
||||
#undef MAX
|
||||
#undef MIN
|
||||
#define MAX(x, y) ((x) < (y) ? (y) : (x))
|
||||
#define MIN(x, y) ((x) < (y) ? (x) : (y))
|
||||
|
||||
#ifdef HAVE_IEEEFP_H
|
||||
#include <ieeefp.h>
|
||||
#endif
|
||||
|
@ -1015,6 +1020,405 @@ float_float(PyObject *v)
|
|||
return v;
|
||||
}
|
||||
|
||||
/* turn ASCII hex characters into integer values and vice versa */
|
||||
|
||||
static char
|
||||
char_from_hex(int x)
|
||||
{
|
||||
assert(0 <= x && x < 16);
|
||||
return "0123456789abcdef"[x];
|
||||
}
|
||||
|
||||
static int
|
||||
hex_from_char(char c) {
|
||||
int x;
|
||||
assert(isxdigit(c));
|
||||
switch(c) {
|
||||
case '0':
|
||||
x = 0;
|
||||
break;
|
||||
case '1':
|
||||
x = 1;
|
||||
break;
|
||||
case '2':
|
||||
x = 2;
|
||||
break;
|
||||
case '3':
|
||||
x = 3;
|
||||
break;
|
||||
case '4':
|
||||
x = 4;
|
||||
break;
|
||||
case '5':
|
||||
x = 5;
|
||||
break;
|
||||
case '6':
|
||||
x = 6;
|
||||
break;
|
||||
case '7':
|
||||
x = 7;
|
||||
break;
|
||||
case '8':
|
||||
x = 8;
|
||||
break;
|
||||
case '9':
|
||||
x = 9;
|
||||
break;
|
||||
case 'a':
|
||||
case 'A':
|
||||
x = 10;
|
||||
break;
|
||||
case 'b':
|
||||
case 'B':
|
||||
x = 11;
|
||||
break;
|
||||
case 'c':
|
||||
case 'C':
|
||||
x = 12;
|
||||
break;
|
||||
case 'd':
|
||||
case 'D':
|
||||
x = 13;
|
||||
break;
|
||||
case 'e':
|
||||
case 'E':
|
||||
x = 14;
|
||||
break;
|
||||
case 'f':
|
||||
case 'F':
|
||||
x = 15;
|
||||
break;
|
||||
default:
|
||||
x = -1;
|
||||
break;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
/* convert a float to a hexadecimal string */
|
||||
|
||||
/* TOHEX_NBITS is DBL_MANT_DIG rounded up to the next integer
|
||||
of the form 4k+1. */
|
||||
#define TOHEX_NBITS DBL_MANT_DIG + 3 - (DBL_MANT_DIG+2)%4
|
||||
|
||||
static PyObject *
|
||||
float_hex(PyObject *v)
|
||||
{
|
||||
double x, m;
|
||||
int e, shift, i, si, esign;
|
||||
/* Space for 1+(TOHEX_NBITS-1)/4 digits, a decimal point, and the
|
||||
trailing NUL byte. */
|
||||
char s[(TOHEX_NBITS-1)/4+3];
|
||||
|
||||
CONVERT_TO_DOUBLE(v, x);
|
||||
|
||||
if (Py_IS_NAN(x) || Py_IS_INFINITY(x))
|
||||
return float_str((PyFloatObject *)v);
|
||||
|
||||
if (x == 0.0) {
|
||||
if(copysign(1.0, x) == -1.0)
|
||||
return PyUnicode_FromString("-0x0.0p+0");
|
||||
else
|
||||
return PyUnicode_FromString("0x0.0p+0");
|
||||
}
|
||||
|
||||
m = frexp(fabs(x), &e);
|
||||
shift = 1 - MAX(DBL_MIN_EXP - e, 0);
|
||||
m = ldexp(m, shift);
|
||||
e -= shift;
|
||||
|
||||
si = 0;
|
||||
s[si] = char_from_hex((int)m);
|
||||
si++;
|
||||
m -= (int)m;
|
||||
s[si] = '.';
|
||||
si++;
|
||||
for (i=0; i < (TOHEX_NBITS-1)/4; i++) {
|
||||
m *= 16.0;
|
||||
s[si] = char_from_hex((int)m);
|
||||
si++;
|
||||
m -= (int)m;
|
||||
}
|
||||
s[si] = '\0';
|
||||
|
||||
if (e < 0) {
|
||||
esign = (int)'-';
|
||||
e = -e;
|
||||
}
|
||||
else
|
||||
esign = (int)'+';
|
||||
|
||||
if (x < 0.0)
|
||||
return PyUnicode_FromFormat("-0x%sp%c%d", s, esign, e);
|
||||
else
|
||||
return PyUnicode_FromFormat("0x%sp%c%d", s, esign, e);
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(float_hex_doc,
|
||||
"float.hex() -> string\n\
|
||||
\n\
|
||||
Return a hexadecimal representation of a floating-point number.\n\
|
||||
>>> (-0.1).hex()\n\
|
||||
'-0x1.999999999999ap-4'\n\
|
||||
>>> 3.14159.hex()\n\
|
||||
'0x1.921f9f01b866ep+1'");
|
||||
|
||||
/* Convert a hexadecimal string to a float. */
|
||||
|
||||
static PyObject *
|
||||
float_fromhex(PyObject *cls, PyObject *arg)
|
||||
{
|
||||
PyObject *result_as_float, *result;
|
||||
double x;
|
||||
long exp, top_exp, lsb, key_digit;
|
||||
char *s, *coeff_start, *s_store, *coeff_end, *exp_start, *s_end;
|
||||
int half_eps, digit, round_up, sign=1;
|
||||
Py_ssize_t length, ndigits, fdigits, i;
|
||||
|
||||
/*
|
||||
* For the sake of simplicity and correctness, we impose an artificial
|
||||
* limit on ndigits, the total number of hex digits in the coefficient
|
||||
* The limit is chosen to ensure that, writing exp for the exponent,
|
||||
*
|
||||
* (1) if exp > LONG_MAX/2 then the value of the hex string is
|
||||
* guaranteed to overflow (provided it's nonzero)
|
||||
*
|
||||
* (2) if exp < LONG_MIN/2 then the value of the hex string is
|
||||
* guaranteed to underflow to 0.
|
||||
*
|
||||
* (3) if LONG_MIN/2 <= exp <= LONG_MAX/2 then there's no danger of
|
||||
* overflow in the calculation of exp and top_exp below.
|
||||
*
|
||||
* More specifically, ndigits is assumed to satisfy the following
|
||||
* inequalities:
|
||||
*
|
||||
* 4*ndigits <= DBL_MIN_EXP - DBL_MANT_DIG - LONG_MIN/2
|
||||
* 4*ndigits <= LONG_MAX/2 + 1 - DBL_MAX_EXP
|
||||
*
|
||||
* If either of these inequalities is not satisfied, a ValueError is
|
||||
* raised. Otherwise, write x for the value of the hex string, and
|
||||
* assume x is nonzero. Then
|
||||
*
|
||||
* 2**(exp-4*ndigits) <= |x| < 2**(exp+4*ndigits).
|
||||
*
|
||||
* Now if exp > LONG_MAX/2 then:
|
||||
*
|
||||
* exp - 4*ndigits >= LONG_MAX/2 + 1 - (LONG_MAX/2 + 1 - DBL_MAX_EXP)
|
||||
* = DBL_MAX_EXP
|
||||
*
|
||||
* so |x| >= 2**DBL_MAX_EXP, which is too large to be stored in C
|
||||
* double, so overflows. If exp < LONG_MIN/2, then
|
||||
*
|
||||
* exp + 4*ndigits <= LONG_MIN/2 - 1 + (
|
||||
* DBL_MIN_EXP - DBL_MANT_DIG - LONG_MIN/2)
|
||||
* = DBL_MIN_EXP - DBL_MANT_DIG - 1
|
||||
*
|
||||
* and so |x| < 2**(DBL_MIN_EXP-DBL_MANT_DIG-1), hence underflows to 0
|
||||
* when converted to a C double.
|
||||
*
|
||||
* It's easy to show that if LONG_MIN/2 <= exp <= LONG_MAX/2 then both
|
||||
* exp+4*ndigits and exp-4*ndigits are within the range of a long.
|
||||
*/
|
||||
|
||||
s = PyUnicode_AsStringAndSize(arg, &length);
|
||||
if (s == NULL)
|
||||
return NULL;
|
||||
s_end = s + length;
|
||||
|
||||
/********************
|
||||
* Parse the string *
|
||||
********************/
|
||||
|
||||
/* leading whitespace and optional sign */
|
||||
while (isspace(*s))
|
||||
s++;
|
||||
if (*s == '-') {
|
||||
s++;
|
||||
sign = -1;
|
||||
}
|
||||
else if (*s == '+')
|
||||
s++;
|
||||
|
||||
/* infinities and nans */
|
||||
if (PyOS_mystrnicmp(s, "nan", 4) == 0) {
|
||||
x = Py_NAN;
|
||||
goto finished;
|
||||
}
|
||||
if (PyOS_mystrnicmp(s, "inf", 4) == 0 ||
|
||||
PyOS_mystrnicmp(s, "infinity", 9) == 0) {
|
||||
x = sign*Py_HUGE_VAL;
|
||||
goto finished;
|
||||
}
|
||||
|
||||
/* [0x] */
|
||||
s_store = s;
|
||||
if (*s == '0') {
|
||||
s++;
|
||||
if (tolower(*s) == (int)'x')
|
||||
s++;
|
||||
else
|
||||
s = s_store;
|
||||
}
|
||||
|
||||
/* coefficient: <integer> [. <fraction>] */
|
||||
coeff_start = s;
|
||||
while (isxdigit(*s))
|
||||
s++;
|
||||
s_store = s;
|
||||
if (*s == '.') {
|
||||
s++;
|
||||
while (isxdigit(*s))
|
||||
s++;
|
||||
coeff_end = s-1;
|
||||
}
|
||||
else
|
||||
coeff_end = s;
|
||||
|
||||
/* ndigits = total # of hex digits; fdigits = # after point */
|
||||
ndigits = coeff_end - coeff_start;
|
||||
fdigits = coeff_end - s_store;
|
||||
if (ndigits == 0)
|
||||
goto parse_error;
|
||||
if (ndigits > MIN(DBL_MIN_EXP - DBL_MANT_DIG - LONG_MIN/2,
|
||||
LONG_MAX/2 + 1 - DBL_MAX_EXP)/4)
|
||||
goto insane_length_error;
|
||||
|
||||
/* [p <exponent>] */
|
||||
if (tolower(*s) == (int)'p') {
|
||||
s++;
|
||||
exp_start = s;
|
||||
if (*s == '-' || *s == '+')
|
||||
s++;
|
||||
if (!isdigit(*s))
|
||||
goto parse_error;
|
||||
s++;
|
||||
while (isdigit(*s))
|
||||
s++;
|
||||
exp = strtol(exp_start, NULL, 10);
|
||||
}
|
||||
else
|
||||
exp = 0;
|
||||
|
||||
/* optional trailing whitespace leading to the end of the string */
|
||||
while (isspace(*s))
|
||||
s++;
|
||||
if (s != s_end)
|
||||
goto parse_error;
|
||||
|
||||
/* for 0 <= j < ndigits, HEX_DIGIT(j) gives the jth most significant digit */
|
||||
#define HEX_DIGIT(j) hex_from_char(*((j) < fdigits ? \
|
||||
coeff_end-(j) : \
|
||||
coeff_end-1-(j)))
|
||||
|
||||
/*******************************************
|
||||
* Compute rounded value of the hex string *
|
||||
*******************************************/
|
||||
|
||||
/* Discard leading zeros, and catch extreme overflow and underflow */
|
||||
while (ndigits > 0 && HEX_DIGIT(ndigits-1) == 0)
|
||||
ndigits--;
|
||||
if (ndigits == 0 || exp < LONG_MIN/2) {
|
||||
x = sign * 0.0;
|
||||
goto finished;
|
||||
}
|
||||
if (exp > LONG_MAX/2)
|
||||
goto overflow_error;
|
||||
|
||||
/* Adjust exponent for fractional part. */
|
||||
exp = exp - 4*((long)fdigits);
|
||||
|
||||
/* top_exp = 1 more than exponent of most sig. bit of coefficient */
|
||||
top_exp = exp + 4*((long)ndigits - 1);
|
||||
for (digit = HEX_DIGIT(ndigits-1); digit != 0; digit /= 2)
|
||||
top_exp++;
|
||||
|
||||
/* catch almost all nonextreme cases of overflow and underflow here */
|
||||
if (top_exp < DBL_MIN_EXP - DBL_MANT_DIG) {
|
||||
x = sign * 0.0;
|
||||
goto finished;
|
||||
}
|
||||
if (top_exp > DBL_MAX_EXP)
|
||||
goto overflow_error;
|
||||
|
||||
/* lsb = exponent of least significant bit of the *rounded* value.
|
||||
This is top_exp - DBL_MANT_DIG unless result is subnormal. */
|
||||
lsb = MAX(top_exp, (long)DBL_MIN_EXP) - DBL_MANT_DIG;
|
||||
|
||||
x = 0.0;
|
||||
if (exp >= lsb) {
|
||||
/* no rounding required */
|
||||
for (i = ndigits-1; i >= 0; i--)
|
||||
x = 16.0*x + HEX_DIGIT(i);
|
||||
x = sign * ldexp(x, (int)(exp));
|
||||
goto finished;
|
||||
}
|
||||
/* rounding required. key_digit is the index of the hex digit
|
||||
containing the first bit to be rounded away. */
|
||||
half_eps = 1 << (int)((lsb - exp - 1) % 4);
|
||||
key_digit = (lsb - exp - 1) / 4;
|
||||
for (i = ndigits-1; i > key_digit; i--)
|
||||
x = 16.0*x + HEX_DIGIT(i);
|
||||
digit = HEX_DIGIT(key_digit);
|
||||
x = 16.0*x + (double)(digit & (16-2*half_eps));
|
||||
|
||||
/* round-half-even: round up if bit lsb-1 is 1 and at least one of
|
||||
bits lsb, lsb-2, lsb-3, lsb-4, ... is 1. */
|
||||
if ((digit & half_eps) != 0) {
|
||||
round_up = 0;
|
||||
if ((digit & (3*half_eps-1)) != 0 ||
|
||||
(half_eps == 8 && (HEX_DIGIT(key_digit+1) & 1) != 0))
|
||||
round_up = 1;
|
||||
else
|
||||
for (i = key_digit-1; i >= 0; i--)
|
||||
if (HEX_DIGIT(i) != 0) {
|
||||
round_up = 1;
|
||||
break;
|
||||
}
|
||||
if (round_up == 1) {
|
||||
x += 2*half_eps;
|
||||
if (top_exp == DBL_MAX_EXP &&
|
||||
x == ldexp((double)(2*half_eps), DBL_MANT_DIG))
|
||||
/* overflow corner case: pre-rounded value <
|
||||
2**DBL_MAX_EXP; rounded=2**DBL_MAX_EXP. */
|
||||
goto overflow_error;
|
||||
}
|
||||
}
|
||||
x = sign * ldexp(x, (int)(exp+4*key_digit));
|
||||
|
||||
finished:
|
||||
result_as_float = Py_BuildValue("(d)", x);
|
||||
if (result_as_float == NULL)
|
||||
return NULL;
|
||||
result = PyObject_CallObject(cls, result_as_float);
|
||||
Py_DECREF(result_as_float);
|
||||
return result;
|
||||
|
||||
overflow_error:
|
||||
PyErr_SetString(PyExc_OverflowError,
|
||||
"hexadecimal value too large to represent as a float");
|
||||
return NULL;
|
||||
|
||||
parse_error:
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"invalid hexadecimal floating-point string");
|
||||
return NULL;
|
||||
|
||||
insane_length_error:
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"hexadecimal string too long to convert");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(float_fromhex_doc,
|
||||
"float.fromhex(string) -> float\n\
|
||||
\n\
|
||||
Create a floating-point number from a hexadecimal string.\n\
|
||||
>>> float.fromhex('0x1.ffffp10')\n\
|
||||
2047.984375\n\
|
||||
>>> float.fromhex('-0x1p-1074')\n\
|
||||
-4.9406564584124654e-324");
|
||||
|
||||
|
||||
static PyObject *
|
||||
float_as_integer_ratio(PyObject *v, PyObject *unused)
|
||||
{
|
||||
|
@ -1326,6 +1730,10 @@ static PyMethodDef float_methods[] = {
|
|||
"When an argument is passed, works like built-in round(x, ndigits)."},
|
||||
{"as_integer_ratio", (PyCFunction)float_as_integer_ratio, METH_NOARGS,
|
||||
float_as_integer_ratio_doc},
|
||||
{"fromhex", (PyCFunction)float_fromhex,
|
||||
METH_O|METH_CLASS, float_fromhex_doc},
|
||||
{"hex", (PyCFunction)float_hex,
|
||||
METH_NOARGS, float_hex_doc},
|
||||
{"is_integer", (PyCFunction)float_is_integer, METH_NOARGS,
|
||||
"Returns True if the float is an integer."},
|
||||
#if 0
|
||||
|
|
Loading…
Reference in New Issue