Index: Doc/library/decimal.rst =================================================================== --- Doc/library/decimal.rst (revision 81464) +++ Doc/library/decimal.rst (working copy) @@ -122,7 +122,7 @@ >>> from decimal import * >>> getcontext() Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, - capitals=1, flags=[], traps=[Overflow, DivisionByZero, + capitals=1, clamp=0, flags=[], traps=[Overflow, DivisionByZero, InvalidOperation]) >>> getcontext().prec = 7 # Set a new precision @@ -244,7 +244,7 @@ >>> ExtendedContext Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, - capitals=1, flags=[], traps=[]) + capitals=1, clamp=0, flags=[], traps=[]) >>> setcontext(ExtendedContext) >>> Decimal(1) / Decimal(7) Decimal('0.142857143') @@ -269,7 +269,7 @@ Decimal('3.14159292') >>> getcontext() Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, - capitals=1, flags=[Inexact, Rounded], traps=[]) + capitals=1, clamp=0, flags=[Inexact, Rounded], traps=[]) The *flags* entry shows that the rational approximation to :const:`Pi` was rounded (digits beyond the context precision were thrown away) and that the @@ -891,7 +891,7 @@ :class:`Context` constructor. -.. class:: Context(prec=None, rounding=None, traps=None, flags=None, Emin=None, Emax=None, capitals=1) +.. class:: Context(prec=None, rounding=None, traps=None, flags=None, Emin=None, Emax=None, capitals=None, clamp=None) Creates a new context. If a field is not specified or is :const:`None`, the default values are copied from the :const:`DefaultContext`. If the *flags* @@ -922,6 +922,14 @@ :const:`1`, exponents are printed with a capital :const:`E`; otherwise, a lowercase :const:`e` is used: :const:`Decimal('6.02e+23')`. + The *clamp* field is either :const:`0` (the default) or :const:`1`. + If set to :const:`1`, a :class:`Decimal` exponent is limited to the + range ``Emin - prec + 1 <= exponent <= Emax - prec + 1``. Values + with exponent larger than ``Emax - prec + 1`` but smaller than + ``10**(Emax + 1)`` will have their exponent reduced into the + required range by adding additional zeros to their representation, + a process known as 'clamping'. The decimal interchange formats + specified in IEEE 754 are subject to clamping this manner. The :class:`Context` class defines several general purpose methods as well as a large number of methods for doing arithmetic directly in a given context. Index: Lib/decimal.py =================================================================== --- Lib/decimal.py (revision 81464) +++ Lib/decimal.py (working copy) @@ -1611,9 +1611,9 @@ """Decapitate the payload of a NaN to fit the context""" payload = self._int - # maximum length of payload is precision if _clamp=0, - # precision-1 if _clamp=1. - max_payload_len = context.prec - context._clamp + # maximum length of payload is precision if clamp=0, + # precision-1 if clamp=1. + max_payload_len = context.prec - context.clamp if len(payload) > max_payload_len: payload = payload[len(payload)-max_payload_len:].lstrip('0') return _dec_from_triple(self._sign, payload, self._exp, True) @@ -1638,11 +1638,11 @@ return Decimal(self) # if self is zero then exponent should be between Etiny and - # Emax if _clamp==0, and between Etiny and Etop if _clamp==1. + # Emax if clamp==0, and between Etiny and Etop if clamp==1. Etiny = context.Etiny() Etop = context.Etop() if not self: - exp_max = [context.Emax, Etop][context._clamp] + exp_max = [context.Emax, Etop][context.clamp] new_exp = min(max(self._exp, Etiny), exp_max) if new_exp != self._exp: context._raise_error(Clamped) @@ -1702,8 +1702,8 @@ if self_is_subnormal: context._raise_error(Subnormal) - # fold down if _clamp == 1 and self has too few digits - if context._clamp == 1 and self._exp > Etop: + # fold down if clamp == 1 and self has too few digits + if context.clamp == 1 and self._exp > Etop: context._raise_error(Clamped) self_padded = self._int + '0'*(self._exp - Etop) return _dec_from_triple(self._sign, self_padded, Etop) @@ -2451,7 +2451,7 @@ if not dup: return _dec_from_triple(dup._sign, '0', 0) - exp_max = [context.Emax, context.Etop()][context._clamp] + exp_max = [context.Emax, context.Etop()][context.clamp] end = len(dup._int) exp = dup._exp while dup._int[end-1] == '0' and exp < exp_max: @@ -3828,13 +3828,13 @@ Emax - Maximum exponent capitals - If 1, 1*10^1 is printed as 1E+1. If 0, printed as 1e1 - _clamp - If 1, change exponents if too high (Default 0) + clamp - If 1, change exponents if too high (Default 0) """ def __init__(self, prec=None, rounding=None, traps=None, flags=None, Emin=None, Emax=None, - capitals=None, _clamp=0, + capitals=None, clamp=None, _ignored_flags=None): if flags is None: flags = [] @@ -3855,7 +3855,8 @@ """Show the current context.""" s = [] s.append('Context(prec=%(prec)d, rounding=%(rounding)s, ' - 'Emin=%(Emin)d, Emax=%(Emax)d, capitals=%(capitals)d' + 'Emin=%(Emin)d, Emax=%(Emax)d, capitals=%(capitals)d, ' + 'clamp=%(clamp)d' % vars(self)) names = [f.__name__ for f, v in self.flags.items() if v] s.append('flags=[' + ', '.join(names) + ']') @@ -3872,17 +3873,39 @@ """Returns a shallow copy from self.""" nc = Context(self.prec, self.rounding, self.traps, self.flags, self.Emin, self.Emax, - self.capitals, self._clamp, self._ignored_flags) + self.capitals, self.clamp, self._ignored_flags) return nc def copy(self): """Returns a deep copy from self.""" nc = Context(self.prec, self.rounding, self.traps.copy(), self.flags.copy(), self.Emin, self.Emax, - self.capitals, self._clamp, self._ignored_flags) + self.capitals, self.clamp, self._ignored_flags) return nc __copy__ = copy + # _clamp is provided for backwards compatibility with third-party + # code. May be removed in Python >= 3.3. + def _get_clamp(self): + "_clamp mirrors the clamp attribute. Its use is deprecated." + import warnings + warnings.warn('Use of the _clamp attribute is deprecated. ' + 'Please use clamp instead.', + DeprecationWarning) + return self.clamp + + def _set_clamp(self, clamp): + "_clamp mirrors the clamp attribute. Its use is deprecated." + import warnings + warnings.warn('Use of the _clamp attribute is deprecated. ' + 'Please use clamp instead.', + DeprecationWarning) + self.clamp = clamp + + # don't bother with _del_clamp; no sane 3rd party code should + # be deleting the _clamp attribute + _clamp = property(_get_clamp, _set_clamp) + def _raise_error(self, condition, explanation = None, *args): """Handles an error @@ -3965,7 +3988,7 @@ "permitted.") d = Decimal(num, context=self) - if d._isnan() and len(d._int) > self.prec - self._clamp: + if d._isnan() and len(d._int) > self.prec - self.clamp: return self._raise_error(ConversionSyntax, "diagnostic info too long in NaN") return d._fix(self) @@ -5875,7 +5898,8 @@ flags=[], Emax=999999999, Emin=-999999999, - capitals=1 + capitals=1, + clamp=0 ) # Pre-made alternate contexts offered by the specification Index: Lib/test/test_decimal.py =================================================================== --- Lib/test/test_decimal.py (revision 81464) +++ Lib/test/test_decimal.py (working copy) @@ -27,11 +27,13 @@ import math import os, sys import operator +import warnings import pickle, copy import unittest from decimal import * import numbers from test.support import run_unittest, run_doctest, is_resource_enabled +from test.support import check_warnings import random try: import threading @@ -412,7 +414,7 @@ def change_max_exponent(self, exp): self.context.Emax = exp def change_clamp(self, clamp): - self.context._clamp = clamp + self.context.clamp = clamp @@ -1815,6 +1817,26 @@ self.assertNotEqual(id(c.flags), id(d.flags)) self.assertNotEqual(id(c.traps), id(d.traps)) + def test__clamp(self): + # In Python 3.2, the private attribute `_clamp` was made + # public (issue 8540), with the old `_clamp` becoming a + # property wrapping `clamp`. For the duration of Python 3.2 + # only, the attribute should be gettable/settable via both + # `clamp` and `_clamp`; in Python 3.3, `_clamp` should be + # removed. + c = Context(clamp = 0) + self.assertEqual(c.clamp, 0) + + with check_warnings(("", DeprecationWarning)): + c._clamp = 1 + self.assertEqual(c.clamp, 1) + with check_warnings(("", DeprecationWarning)): + self.assertEqual(c._clamp, 1) + c.clamp = 0 + self.assertEqual(c.clamp, 0) + with check_warnings(("", DeprecationWarning)): + self.assertEqual(c._clamp, 0) + def test_abs(self): c = Context() d = c.abs(Decimal(-1))