]> granicus.if.org Git - python/commitdiff
#Issue 8540: Make Context._clamp attribute public in decimal module.
authorMark Dickinson <dickinsm@gmail.com>
Sat, 22 May 2010 18:35:36 +0000 (18:35 +0000)
committerMark Dickinson <dickinsm@gmail.com>
Sat, 22 May 2010 18:35:36 +0000 (18:35 +0000)
Doc/library/decimal.rst
Lib/decimal.py
Lib/test/test_decimal.py
Misc/NEWS

index 7b23f6a3b2cbc234ac9788b473cd36e627706aa0..ff9bd91e8ce49039c92f8117628515ab017f1487 100644 (file)
@@ -122,7 +122,7 @@ precision, rounding, or enabled traps::
    >>> 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 @@ enabled:
 
    >>> 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 @@ using the :meth:`clear_flags` method. ::
    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 @@ In addition to the three supplied contexts, new contexts can be created with the
 :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,23 @@ In addition to the three supplied contexts, new contexts can be created with the
    :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`, the exponent ``e`` of a :class:`Decimal`
+   instance representable in this context is strictly limited to the
+   range ``Emin - prec + 1 <= e <= Emax - prec + 1``.  If *clamp* is
+   :const:`0` then a weaker condition holds: the adjusted exponent of
+   the :class:`Decimal` instance is at most ``Emax``.  When *clamp* is
+   :const:`1`, a large normal number will, where possible, have its
+   exponent reduced and a corresponding number of zeros added to its
+   coefficient, in order to fit the exponent constraints; this
+   preserves the value of the number but loses information about
+   significant trailing zeros.  For example::
+
+      >>> Context(prec=6, Emax=999, clamp=1).create_decimal('1.23e999')
+      Decimal('1.23000E+999')
+
+   A *clamp* value of :const:`1` allows compatibility with the
+   fixed-width decimal interchange formats specified in IEEE 754.
 
    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 c9edf9c86a738ea0cb7c160bb0427efb590b630c..cc71cd834032e358594ebed9fb8c0e9c864d776a 100644 (file)
@@ -1611,9 +1611,9 @@ class Decimal(object):
         """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 @@ class Decimal(object):
                 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 @@ class Decimal(object):
         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 @@ class Decimal(object):
 
         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 @@ class Context(object):
     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 @@ class Context(object):
         """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 @@ class Context(object):
         """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 @@ class Context(object):
                                      "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 @@ DefaultContext = Context(
         flags=[],
         Emax=999999999,
         Emin=-999999999,
-        capitals=1
+        capitals=1,
+        clamp=0
 )
 
 # Pre-made alternate contexts offered by the specification
index 21590880b08dd2a547b06e1489b67754da8faddc..50b660b3413ef31098019cdf5128ec9f57210ad7 100644 (file)
@@ -27,11 +27,13 @@ with the corresponding argument.
 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 @@ class DecimalTest(unittest.TestCase):
     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 @@ class ContextAPItests(unittest.TestCase):
         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))
index 1799e73b31c822b044a7d7133bb17fbd83905341..22df937a1362f53a5c332a4099ee1a9dbc39820a 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -393,6 +393,11 @@ C-API
 Library
 -------
 
+- Issue #8540: Decimal module: rename the Context._clamp attribute to
+  Context.clamp and make it public.  This is useful in creating
+  contexts that correspond to the decimal interchange formats
+  specified in IEEE 754.
+
 - Issue #6268: Fix seek() method of codecs.open(), don't read or write the BOM
   twice after seek(0). Fix also reset() method of codecs, UTF-16, UTF-32 and
   StreamWriter classes.