Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f16baeb

Browse files
committed
Merged revisions 61038,61042-61045,61047,61050,61053,61055-61056,61061-61062,61066,61068,61070,61083,61085,61092-61097,61103-61104,61110-61112,61114-61115,61117-61125 via svnmerge from
svn+ssh://[email protected]/python/trunk ........ r61118 | raymond.hettinger | 2008-02-28 23:30:42 +0100 (Thu, 28 Feb 2008) | 1 line Have itertools.chain() consume its inputs lazily instead of building a tuple of iterators at the outset. ........ r61119 | raymond.hettinger | 2008-02-28 23:46:41 +0100 (Thu, 28 Feb 2008) | 1 line Add alternate constructor for itertools.chain(). ........ r61123 | mark.dickinson | 2008-02-29 03:16:37 +0100 (Fri, 29 Feb 2008) | 2 lines Add __format__ method to Decimal, to support PEP 3101 ........ r61124 | raymond.hettinger | 2008-02-29 03:21:48 +0100 (Fri, 29 Feb 2008) | 1 line Handle the repeat keyword argument for itertools.product(). ........ r61125 | mark.dickinson | 2008-02-29 04:29:17 +0100 (Fri, 29 Feb 2008) | 2 lines Fix docstring typo. ........
1 parent 5524089 commit f16baeb

4 files changed

Lines changed: 447 additions & 58 deletions

File tree

Lib/decimal.py

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2381,6 +2381,29 @@ def _rescale(self, exp, rounding):
23812381
coeff = str(int(coeff)+1)
23822382
return _dec_from_triple(self._sign, coeff, exp)
23832383

2384+
def _round(self, places, rounding):
2385+
"""Round a nonzero, nonspecial Decimal to a fixed number of
2386+
significant figures, using the given rounding mode.
2387+
2388+
Infinities, NaNs and zeros are returned unaltered.
2389+
2390+
This operation is quiet: it raises no flags, and uses no
2391+
information from the context.
2392+
2393+
"""
2394+
if places <= 0:
2395+
raise ValueError("argument should be at least 1 in _round")
2396+
if self._is_special or not self:
2397+
return Decimal(self)
2398+
ans = self._rescale(self.adjusted()+1-places, rounding)
2399+
# it can happen that the rescale alters the adjusted exponent;
2400+
# for example when rounding 99.97 to 3 significant figures.
2401+
# When this happens we end up with an extra 0 at the end of
2402+
# the number; a second rescale fixes this.
2403+
if ans.adjusted() != self.adjusted():
2404+
ans = ans._rescale(ans.adjusted()+1-places, rounding)
2405+
return ans
2406+
23842407
def to_integral_exact(self, rounding=None, context=None):
23852408
"""Rounds to a nearby integer.
23862409
@@ -3432,6 +3455,95 @@ def __deepcopy__(self, memo):
34323455
return self # My components are also immutable
34333456
return self.__class__(str(self))
34343457

3458+
# PEP 3101 support. See also _parse_format_specifier and _format_align
3459+
def __format__(self, specifier, context=None):
3460+
"""Format a Decimal instance according to the given specifier.
3461+
3462+
The specifier should be a standard format specifier, with the
3463+
form described in PEP 3101. Formatting types 'e', 'E', 'f',
3464+
'F', 'g', 'G', and '%' are supported. If the formatting type
3465+
is omitted it defaults to 'g' or 'G', depending on the value
3466+
of context.capitals.
3467+
3468+
At this time the 'n' format specifier type (which is supposed
3469+
to use the current locale) is not supported.
3470+
"""
3471+
3472+
# Note: PEP 3101 says that if the type is not present then
3473+
# there should be at least one digit after the decimal point.
3474+
# We take the liberty of ignoring this requirement for
3475+
# Decimal---it's presumably there to make sure that
3476+
# format(float, '') behaves similarly to str(float).
3477+
if context is None:
3478+
context = getcontext()
3479+
3480+
spec = _parse_format_specifier(specifier)
3481+
3482+
# special values don't care about the type or precision...
3483+
if self._is_special:
3484+
return _format_align(str(self), spec)
3485+
3486+
# a type of None defaults to 'g' or 'G', depending on context
3487+
# if type is '%', adjust exponent of self accordingly
3488+
if spec['type'] is None:
3489+
spec['type'] = ['g', 'G'][context.capitals]
3490+
elif spec['type'] == '%':
3491+
self = _dec_from_triple(self._sign, self._int, self._exp+2)
3492+
3493+
# round if necessary, taking rounding mode from the context
3494+
rounding = context.rounding
3495+
precision = spec['precision']
3496+
if precision is not None:
3497+
if spec['type'] in 'eE':
3498+
self = self._round(precision+1, rounding)
3499+
elif spec['type'] in 'gG':
3500+
if len(self._int) > precision:
3501+
self = self._round(precision, rounding)
3502+
elif spec['type'] in 'fF%':
3503+
self = self._rescale(-precision, rounding)
3504+
# special case: zeros with a positive exponent can't be
3505+
# represented in fixed point; rescale them to 0e0.
3506+
elif not self and self._exp > 0 and spec['type'] in 'fF%':
3507+
self = self._rescale(0, rounding)
3508+
3509+
# figure out placement of the decimal point
3510+
leftdigits = self._exp + len(self._int)
3511+
if spec['type'] in 'fF%':
3512+
dotplace = leftdigits
3513+
elif spec['type'] in 'eE':
3514+
if not self and precision is not None:
3515+
dotplace = 1 - precision
3516+
else:
3517+
dotplace = 1
3518+
elif spec['type'] in 'gG':
3519+
if self._exp <= 0 and leftdigits > -6:
3520+
dotplace = leftdigits
3521+
else:
3522+
dotplace = 1
3523+
3524+
# figure out main part of numeric string...
3525+
if dotplace <= 0:
3526+
num = '0.' + '0'*(-dotplace) + self._int
3527+
elif dotplace >= len(self._int):
3528+
# make sure we're not padding a '0' with extra zeros on the right
3529+
assert dotplace==len(self._int) or self._int != '0'
3530+
num = self._int + '0'*(dotplace-len(self._int))
3531+
else:
3532+
num = self._int[:dotplace] + '.' + self._int[dotplace:]
3533+
3534+
# ...then the trailing exponent, or trailing '%'
3535+
if leftdigits != dotplace or spec['type'] in 'eE':
3536+
echar = {'E': 'E', 'e': 'e', 'G': 'E', 'g': 'e'}[spec['type']]
3537+
num = num + "{0}{1:+}".format(echar, leftdigits-dotplace)
3538+
elif spec['type'] == '%':
3539+
num = num + '%'
3540+
3541+
# add sign
3542+
if self._sign == 1:
3543+
num = '-' + num
3544+
return _format_align(num, spec)
3545+
3546+
34353547
def _dec_from_triple(sign, coefficient, exponent, special=False):
34363548
"""Create a decimal instance directly, without any validation,
34373549
normalization (e.g. removal of leading zeros) or argument
@@ -5249,8 +5361,136 @@ def _convert_other(other, raiseit=False):
52495361

52505362
_all_zeros = re.compile('0*$').match
52515363
_exact_half = re.compile('50*$').match
5364+
5365+
##### PEP3101 support functions ##############################################
5366+
# The functions parse_format_specifier and format_align have little to do
5367+
# with the Decimal class, and could potentially be reused for other pure
5368+
# Python numeric classes that want to implement __format__
5369+
#
5370+
# A format specifier for Decimal looks like:
5371+
#
5372+
# [[fill]align][sign][0][minimumwidth][.precision][type]
5373+
#
5374+
5375+
_parse_format_specifier_regex = re.compile(r"""\A
5376+
(?:
5377+
(?P<fill>.)?
5378+
(?P<align>[<>=^])
5379+
)?
5380+
(?P<sign>[-+ ])?
5381+
(?P<zeropad>0)?
5382+
(?P<minimumwidth>(?!0)\d+)?
5383+
(?:\.(?P<precision>0|(?!0)\d+))?
5384+
(?P<type>[eEfFgG%])?
5385+
\Z
5386+
""", re.VERBOSE)
5387+
52525388
del re
52535389

5390+
def _parse_format_specifier(format_spec):
5391+
"""Parse and validate a format specifier.
5392+
5393+
Turns a standard numeric format specifier into a dict, with the
5394+
following entries:
5395+
5396+
fill: fill character to pad field to minimum width
5397+
align: alignment type, either '<', '>', '=' or '^'
5398+
sign: either '+', '-' or ' '
5399+
minimumwidth: nonnegative integer giving minimum width
5400+
precision: nonnegative integer giving precision, or None
5401+
type: one of the characters 'eEfFgG%', or None
5402+
unicode: either True or False (always True for Python 3.x)
5403+
5404+
"""
5405+
m = _parse_format_specifier_regex.match(format_spec)
5406+
if m is None:
5407+
raise ValueError("Invalid format specifier: " + format_spec)
5408+
5409+
# get the dictionary
5410+
format_dict = m.groupdict()
5411+
5412+
# defaults for fill and alignment
5413+
fill = format_dict['fill']
5414+
align = format_dict['align']
5415+
if format_dict.pop('zeropad') is not None:
5416+
# in the face of conflict, refuse the temptation to guess
5417+
if fill is not None and fill != '0':
5418+
raise ValueError("Fill character conflicts with '0'"
5419+
" in format specifier: " + format_spec)
5420+
if align is not None and align != '=':
5421+
raise ValueError("Alignment conflicts with '0' in "
5422+
"format specifier: " + format_spec)
5423+
fill = '0'
5424+
align = '='
5425+
format_dict['fill'] = fill or ' '
5426+
format_dict['align'] = align or '<'
5427+
5428+
if format_dict['sign'] is None:
5429+
format_dict['sign'] = '-'
5430+
5431+
# turn minimumwidth and precision entries into integers.
5432+
# minimumwidth defaults to 0; precision remains None if not given
5433+
format_dict['minimumwidth'] = int(format_dict['minimumwidth'] or '0')
5434+
if format_dict['precision'] is not None:
5435+
format_dict['precision'] = int(format_dict['precision'])
5436+
5437+
# if format type is 'g' or 'G' then a precision of 0 makes little
5438+
# sense; convert it to 1. Same if format type is unspecified.
5439+
if format_dict['precision'] == 0:
5440+
if format_dict['type'] in 'gG' or format_dict['type'] is None:
5441+
format_dict['precision'] = 1
5442+
5443+
# record whether return type should be str or unicode
5444+
format_dict['unicode'] = isinstance(format_spec, unicode)
5445+
5446+
return format_dict
5447+
5448+
def _format_align(body, spec_dict):
5449+
"""Given an unpadded, non-aligned numeric string, add padding and
5450+
aligment to conform with the given format specifier dictionary (as
5451+
output from parse_format_specifier).
5452+
5453+
It's assumed that if body is negative then it starts with '-'.
5454+
Any leading sign ('-' or '+') is stripped from the body before
5455+
applying the alignment and padding rules, and replaced in the
5456+
appropriate position.
5457+
5458+
"""
5459+
# figure out the sign; we only examine the first character, so if
5460+
# body has leading whitespace the results may be surprising.
5461+
if len(body) > 0 and body[0] in '-+':
5462+
sign = body[0]
5463+
body = body[1:]
5464+
else:
5465+
sign = ''
5466+
5467+
if sign != '-':
5468+
if spec_dict['sign'] in ' +':
5469+
sign = spec_dict['sign']
5470+
else:
5471+
sign = ''
5472+
5473+
# how much extra space do we have to play with?
5474+
minimumwidth = spec_dict['minimumwidth']
5475+
fill = spec_dict['fill']
5476+
padding = fill*(max(minimumwidth - (len(sign+body)), 0))
5477+
5478+
align = spec_dict['align']
5479+
if align == '<':
5480+
result = padding + sign + body
5481+
elif align == '>':
5482+
result = sign + body + padding
5483+
elif align == '=':
5484+
result = sign + padding + body
5485+
else: #align == '^'
5486+
half = len(padding)//2
5487+
result = padding[:half] + sign + body + padding[half:]
5488+
5489+
# make sure that result is unicode if necessary
5490+
if spec_dict['unicode']:
5491+
result = unicode(result)
5492+
5493+
return result
52545494

52555495
##### Useful Constants (internal use only) ################################
52565496

Lib/test/test_decimal.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,98 @@ def __ne__(self, other):
610610
self.assertEqual(eval('Decimal(10)' + sym + 'E()'),
611611
'10' + rop + 'str')
612612

613+
class DecimalFormatTest(unittest.TestCase):
614+
'''Unit tests for the format function.'''
615+
def test_formatting(self):
616+
# triples giving a format, a Decimal, and the expected result
617+
test_values = [
618+
('e', '0E-15', '0e-15'),
619+
('e', '2.3E-15', '2.3e-15'),
620+
('e', '2.30E+2', '2.30e+2'), # preserve significant zeros
621+
('e', '2.30000E-15', '2.30000e-15'),
622+
('e', '1.23456789123456789e40', '1.23456789123456789e+40'),
623+
('e', '1.5', '1.5e+0'),
624+
('e', '0.15', '1.5e-1'),
625+
('e', '0.015', '1.5e-2'),
626+
('e', '0.0000000000015', '1.5e-12'),
627+
('e', '15.0', '1.50e+1'),
628+
('e', '-15', '-1.5e+1'),
629+
('e', '0', '0e+0'),
630+
('e', '0E1', '0e+1'),
631+
('e', '0.0', '0e-1'),
632+
('e', '0.00', '0e-2'),
633+
('.6e', '0E-15', '0.000000e-9'),
634+
('.6e', '0', '0.000000e+6'),
635+
('.6e', '9.999999', '9.999999e+0'),
636+
('.6e', '9.9999999', '1.000000e+1'),
637+
('.6e', '-1.23e5', '-1.230000e+5'),
638+
('.6e', '1.23456789e-3', '1.234568e-3'),
639+
('f', '0', '0'),
640+
('f', '0.0', '0.0'),
641+
('f', '0E-2', '0.00'),
642+
('f', '0.00E-8', '0.0000000000'),
643+
('f', '0E1', '0'), # loses exponent information
644+
('f', '3.2E1', '32'),
645+
('f', '3.2E2', '320'),
646+
('f', '3.20E2', '320'),
647+
('f', '3.200E2', '320.0'),
648+
('f', '3.2E-6', '0.0000032'),
649+
('.6f', '0E-15', '0.000000'), # all zeros treated equally
650+
('.6f', '0E1', '0.000000'),
651+
('.6f', '0', '0.000000'),
652+
('.0f', '0', '0'), # no decimal point
653+
('.0f', '0e-2', '0'),
654+
('.0f', '3.14159265', '3'),
655+
('.1f', '3.14159265', '3.1'),
656+
('.4f', '3.14159265', '3.1416'),
657+
('.6f', '3.14159265', '3.141593'),
658+
('.7f', '3.14159265', '3.1415926'), # round-half-even!
659+
('.8f', '3.14159265', '3.14159265'),
660+
('.9f', '3.14159265', '3.141592650'),
661+
662+
('g', '0', '0'),
663+
('g', '0.0', '0.0'),
664+
('g', '0E1', '0e+1'),
665+
('G', '0E1', '0E+1'),
666+
('g', '0E-5', '0.00000'),
667+
('g', '0E-6', '0.000000'),
668+
('g', '0E-7', '0e-7'),
669+
('g', '-0E2', '-0e+2'),
670+
('.0g', '3.14159265', '3'), # 0 sig fig -> 1 sig fig
671+
('.1g', '3.14159265', '3'),
672+
('.2g', '3.14159265', '3.1'),
673+
('.5g', '3.14159265', '3.1416'),
674+
('.7g', '3.14159265', '3.141593'),
675+
('.8g', '3.14159265', '3.1415926'), # round-half-even!
676+
('.9g', '3.14159265', '3.14159265'),
677+
('.10g', '3.14159265', '3.14159265'), # don't pad
678+
679+
('%', '0E1', '0%'),
680+
('%', '0E0', '0%'),
681+
('%', '0E-1', '0%'),
682+
('%', '0E-2', '0%'),
683+
('%', '0E-3', '0.0%'),
684+
('%', '0E-4', '0.00%'),
685+
686+
('.3%', '0', '0.000%'), # all zeros treated equally
687+
('.3%', '0E10', '0.000%'),
688+
('.3%', '0E-10', '0.000%'),
689+
('.3%', '2.34', '234.000%'),
690+
('.3%', '1.234567', '123.457%'),
691+
('.0%', '1.23', '123%'),
692+
693+
('e', 'NaN', 'NaN'),
694+
('f', '-NaN123', '-NaN123'),
695+
('+g', 'NaN456', '+NaN456'),
696+
('.3e', 'Inf', 'Infinity'),
697+
('.16f', '-Inf', '-Infinity'),
698+
('.0g', '-sNaN', '-sNaN'),
699+
700+
('', '1.00', '1.00'),
701+
]
702+
for fmt, d, result in test_values:
703+
self.assertEqual(format(Decimal(d), fmt), result)
704+
613705
class DecimalArithmeticOperatorsTest(unittest.TestCase):
614706
'''Unit tests for all arithmetic operators, binary and unary.'''
615707

@@ -1351,6 +1443,7 @@ def test_main(arith=False, verbose=None, todo_tests=None, debug=None):
13511443
DecimalExplicitConstructionTest,
13521444
DecimalImplicitConstructionTest,
13531445
DecimalArithmeticOperatorsTest,
1446+
DecimalFormatTest,
13541447
DecimalUseOfContextTest,
13551448
DecimalUsabilityTest,
13561449
DecimalPythonAPItests,

0 commit comments

Comments
 (0)