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:
Mark Dickinson 2008-07-16 11:30:51 +00:00
parent 0c474d01a1
commit 65fe25e597
5 changed files with 867 additions and 1 deletions

View File

@ -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
==============

View File

@ -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

View File

@ -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__':

View File

@ -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.

View File

@ -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