Commit b1d8e322 authored by Mark Dickinson's avatar Mark Dickinson

#Issue 8540: Make Context._clamp attribute public in decimal module.

parent a92ad7ee
...@@ -122,7 +122,7 @@ precision, rounding, or enabled traps:: ...@@ -122,7 +122,7 @@ precision, rounding, or enabled traps::
>>> from decimal import * >>> from decimal import *
>>> getcontext() >>> getcontext()
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, 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]) InvalidOperation])
>>> getcontext().prec = 7 # Set a new precision >>> getcontext().prec = 7 # Set a new precision
...@@ -244,7 +244,7 @@ enabled: ...@@ -244,7 +244,7 @@ enabled:
>>> ExtendedContext >>> ExtendedContext
Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999,
capitals=1, flags=[], traps=[]) capitals=1, clamp=0, flags=[], traps=[])
>>> setcontext(ExtendedContext) >>> setcontext(ExtendedContext)
>>> Decimal(1) / Decimal(7) >>> Decimal(1) / Decimal(7)
Decimal('0.142857143') Decimal('0.142857143')
...@@ -269,7 +269,7 @@ using the :meth:`clear_flags` method. :: ...@@ -269,7 +269,7 @@ using the :meth:`clear_flags` method. ::
Decimal('3.14159292') Decimal('3.14159292')
>>> getcontext() >>> getcontext()
Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, 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 The *flags* entry shows that the rational approximation to :const:`Pi` was
rounded (digits beyond the context precision were thrown away) and that the 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 ...@@ -891,7 +891,7 @@ In addition to the three supplied contexts, new contexts can be created with the
:class:`Context` constructor. :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 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* 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 ...@@ -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 :const:`1`, exponents are printed with a capital :const:`E`; otherwise, a
lowercase :const:`e` is used: :const:`Decimal('6.02e+23')`. 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 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. a large number of methods for doing arithmetic directly in a given context.
......
...@@ -1611,9 +1611,9 @@ class Decimal(object): ...@@ -1611,9 +1611,9 @@ class Decimal(object):
"""Decapitate the payload of a NaN to fit the context""" """Decapitate the payload of a NaN to fit the context"""
payload = self._int payload = self._int
# maximum length of payload is precision if _clamp=0, # maximum length of payload is precision if clamp=0,
# precision-1 if _clamp=1. # precision-1 if clamp=1.
max_payload_len = context.prec - context._clamp max_payload_len = context.prec - context.clamp
if len(payload) > max_payload_len: if len(payload) > max_payload_len:
payload = payload[len(payload)-max_payload_len:].lstrip('0') payload = payload[len(payload)-max_payload_len:].lstrip('0')
return _dec_from_triple(self._sign, payload, self._exp, True) return _dec_from_triple(self._sign, payload, self._exp, True)
...@@ -1638,11 +1638,11 @@ class Decimal(object): ...@@ -1638,11 +1638,11 @@ class Decimal(object):
return Decimal(self) return Decimal(self)
# if self is zero then exponent should be between Etiny and # 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() Etiny = context.Etiny()
Etop = context.Etop() Etop = context.Etop()
if not self: 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) new_exp = min(max(self._exp, Etiny), exp_max)
if new_exp != self._exp: if new_exp != self._exp:
context._raise_error(Clamped) context._raise_error(Clamped)
...@@ -1702,8 +1702,8 @@ class Decimal(object): ...@@ -1702,8 +1702,8 @@ class Decimal(object):
if self_is_subnormal: if self_is_subnormal:
context._raise_error(Subnormal) context._raise_error(Subnormal)
# fold down if _clamp == 1 and self has too few digits # fold down if clamp == 1 and self has too few digits
if context._clamp == 1 and self._exp > Etop: if context.clamp == 1 and self._exp > Etop:
context._raise_error(Clamped) context._raise_error(Clamped)
self_padded = self._int + '0'*(self._exp - Etop) self_padded = self._int + '0'*(self._exp - Etop)
return _dec_from_triple(self._sign, self_padded, Etop) return _dec_from_triple(self._sign, self_padded, Etop)
...@@ -2451,7 +2451,7 @@ class Decimal(object): ...@@ -2451,7 +2451,7 @@ class Decimal(object):
if not dup: if not dup:
return _dec_from_triple(dup._sign, '0', 0) 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) end = len(dup._int)
exp = dup._exp exp = dup._exp
while dup._int[end-1] == '0' and exp < exp_max: while dup._int[end-1] == '0' and exp < exp_max:
...@@ -3828,13 +3828,13 @@ class Context(object): ...@@ -3828,13 +3828,13 @@ class Context(object):
Emax - Maximum exponent Emax - Maximum exponent
capitals - If 1, 1*10^1 is printed as 1E+1. capitals - If 1, 1*10^1 is printed as 1E+1.
If 0, printed as 1e1 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, def __init__(self, prec=None, rounding=None,
traps=None, flags=None, traps=None, flags=None,
Emin=None, Emax=None, Emin=None, Emax=None,
capitals=None, _clamp=0, capitals=None, clamp=None,
_ignored_flags=None): _ignored_flags=None):
if flags is None: if flags is None:
flags = [] flags = []
...@@ -3855,7 +3855,8 @@ class Context(object): ...@@ -3855,7 +3855,8 @@ class Context(object):
"""Show the current context.""" """Show the current context."""
s = [] s = []
s.append('Context(prec=%(prec)d, rounding=%(rounding)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)) % vars(self))
names = [f.__name__ for f, v in self.flags.items() if v] names = [f.__name__ for f, v in self.flags.items() if v]
s.append('flags=[' + ', '.join(names) + ']') s.append('flags=[' + ', '.join(names) + ']')
...@@ -3872,17 +3873,39 @@ class Context(object): ...@@ -3872,17 +3873,39 @@ class Context(object):
"""Returns a shallow copy from self.""" """Returns a shallow copy from self."""
nc = Context(self.prec, self.rounding, self.traps, nc = Context(self.prec, self.rounding, self.traps,
self.flags, self.Emin, self.Emax, self.flags, self.Emin, self.Emax,
self.capitals, self._clamp, self._ignored_flags) self.capitals, self.clamp, self._ignored_flags)
return nc return nc
def copy(self): def copy(self):
"""Returns a deep copy from self.""" """Returns a deep copy from self."""
nc = Context(self.prec, self.rounding, self.traps.copy(), nc = Context(self.prec, self.rounding, self.traps.copy(),
self.flags.copy(), self.Emin, self.Emax, self.flags.copy(), self.Emin, self.Emax,
self.capitals, self._clamp, self._ignored_flags) self.capitals, self.clamp, self._ignored_flags)
return nc return nc
__copy__ = copy __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): def _raise_error(self, condition, explanation = None, *args):
"""Handles an error """Handles an error
...@@ -3965,7 +3988,7 @@ class Context(object): ...@@ -3965,7 +3988,7 @@ class Context(object):
"permitted.") "permitted.")
d = Decimal(num, context=self) 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, return self._raise_error(ConversionSyntax,
"diagnostic info too long in NaN") "diagnostic info too long in NaN")
return d._fix(self) return d._fix(self)
...@@ -5875,7 +5898,8 @@ DefaultContext = Context( ...@@ -5875,7 +5898,8 @@ DefaultContext = Context(
flags=[], flags=[],
Emax=999999999, Emax=999999999,
Emin=-999999999, Emin=-999999999,
capitals=1 capitals=1,
clamp=0
) )
# Pre-made alternate contexts offered by the specification # Pre-made alternate contexts offered by the specification
......
...@@ -27,11 +27,13 @@ with the corresponding argument. ...@@ -27,11 +27,13 @@ with the corresponding argument.
import math import math
import os, sys import os, sys
import operator import operator
import warnings
import pickle, copy import pickle, copy
import unittest import unittest
from decimal import * from decimal import *
import numbers import numbers
from test.support import run_unittest, run_doctest, is_resource_enabled from test.support import run_unittest, run_doctest, is_resource_enabled
from test.support import check_warnings
import random import random
try: try:
import threading import threading
...@@ -412,7 +414,7 @@ class DecimalTest(unittest.TestCase): ...@@ -412,7 +414,7 @@ class DecimalTest(unittest.TestCase):
def change_max_exponent(self, exp): def change_max_exponent(self, exp):
self.context.Emax = exp self.context.Emax = exp
def change_clamp(self, clamp): def change_clamp(self, clamp):
self.context._clamp = clamp self.context.clamp = clamp
...@@ -1815,6 +1817,26 @@ class ContextAPItests(unittest.TestCase): ...@@ -1815,6 +1817,26 @@ class ContextAPItests(unittest.TestCase):
self.assertNotEqual(id(c.flags), id(d.flags)) self.assertNotEqual(id(c.flags), id(d.flags))
self.assertNotEqual(id(c.traps), id(d.traps)) 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): def test_abs(self):
c = Context() c = Context()
d = c.abs(Decimal(-1)) d = c.abs(Decimal(-1))
......
...@@ -393,6 +393,11 @@ C-API ...@@ -393,6 +393,11 @@ C-API
Library 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 - 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 twice after seek(0). Fix also reset() method of codecs, UTF-16, UTF-32 and
StreamWriter classes. StreamWriter classes.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment