[Python-checkins] r54893 - python/branches/decimal-branch/Lib/decimal.py
facundo.batista
python-checkins at python.org
Fri Apr 20 23:14:30 CEST 2007
Author: facundo.batista
Date: Fri Apr 20 23:14:29 2007
New Revision: 54893
Modified:
python/branches/decimal-branch/Lib/decimal.py
Log:
All the operations that already existed pass ok the new tests (except
power). For this, I fixed some bugs, reordered some code (without
changing functionality), and made only one important change: Added
a P value to the valid exponents, because we needed a different
diagnostic information.
So far, for diagnostic information we used the Decimal._int digits,
because that was all that was needed. I think it's more clear to
leave there the payload digits, and not make that structure more
complex, and create the new exponent.
P is from Phantom signal, because for everybody asking
self._isnan(), it will return 1 (quiet NaN). But if you want to
look at it in more detail, you will now that in a past life, it was
signaled.
Modified: python/branches/decimal-branch/Lib/decimal.py
==============================================================================
--- python/branches/decimal-branch/Lib/decimal.py (original)
+++ python/branches/decimal-branch/Lib/decimal.py Fri Apr 20 23:14:29 2007
@@ -204,13 +204,21 @@
x ** (non-integer)
x ** (+-)INF
An operand is invalid
+
+ The result of the operation after these is a quiet positive NaN,
+ except when the cause is a signaling NaN, in which case the result is
+ also a quiet NaN, but with the original sign, and an optional
+ diagnostic information.
"""
def handle(self, context, *args):
if args:
if args[0] == 1: # sNaN, must drop 's' but keep diagnostics
return Decimal( (args[1]._sign, args[1]._int, 'n') )
+ elif args[0] == 2:
+ return Decimal( (args[1], args[2], 'P') )
return NaN
+
class ConversionSyntax(InvalidOperation):
"""Trying to convert badly formed string.
@@ -218,9 +226,8 @@
converted to a number and it does not conform to the numeric string
syntax. The result is [0,qNaN].
"""
-
def handle(self, context, *args):
- return (0, (0,), 'n') # Passed to something which uses a tuple.
+ return NaN
class DivisionByZero(DecimalException, ZeroDivisionError):
"""Division by 0.
@@ -562,11 +569,11 @@
raise ValueError('Invalid sign')
for digit in value[1]:
if not isinstance(digit, (int,long)) or digit < 0:
- raise ValueError("The second value in the tuple must be"
+ raise ValueError("The second value in the tuple must be "
"composed of non negative integer elements.")
self._sign = value[0]
self._int = tuple(value[1])
- if value[2] in ('F','n','N'):
+ if value[2] in ('F','n','N', 'P'):
self._exp = value[2]
self._is_special = True
else:
@@ -597,9 +604,18 @@
sig, sign, diag = _isnan(value)
self._is_special = True
if len(diag) > context.prec: # Diagnostic info too long
- self._sign, self._int, self._exp = \
- context._raise_error(ConversionSyntax)
- return self
+ # sig=1, qNaN -> ConversionSyntax
+ # sig=2, sNaN -> InvalidOperation
+ digits = tuple(int(x) for x in diag[-context.prec:])
+ if sig == 1:
+ self._exp = 'P' # qNaN
+ self._sign = sign
+ self._int = digits
+ return self
+
+ return context._raise_error(InvalidOperation,
+ 'diagnostic info too long',
+ 2, sign, digits)
if sig == 1:
self._exp = 'n' # qNaN
else: # sig == 2
@@ -611,8 +627,8 @@
self._sign, self._int, self._exp = _string2exact(value)
except ValueError:
self._is_special = True
- self._sign, self._int, self._exp = \
- context._raise_error(ConversionSyntax)
+ return context._raise_error(ConversionSyntax,
+ "non parseable string")
return self
raise TypeError("Cannot convert %r to Decimal" % value)
@@ -621,12 +637,12 @@
"""Returns whether the number is not actually one.
0 if a number
- 1 if NaN
+ 1 if NaN (it could be a normal quiet NaN or a phantom one)
2 if sNaN
"""
if self._is_special:
exp = self._exp
- if exp == 'n':
+ if exp == 'n' or exp == 'P':
return 1
elif exp == 'N':
return 2
@@ -645,7 +661,7 @@
return 1
return 0
- def _check_nans(self, other = None, context=None):
+ def _check_nans(self, other=None, context=None):
"""Returns whether the number is not actually one.
if self, other are sNaN, signal
@@ -999,6 +1015,9 @@
sign = min(self._sign, other._sign)
if negativezero:
sign = 1
+ if exp < context.Etiny():
+ exp = context.Etiny()
+ context._raise_error(Clamped)
return Decimal( (sign, (0,), exp))
if not self:
exp = max(exp, other._exp - context.prec-1)
@@ -1217,10 +1236,11 @@
if self._isinfinity() and other._isinfinity():
if divmod:
- return (context._raise_error(InvalidOperation,
+ reloco = (context._raise_error(InvalidOperation,
'(+-)INF // (+-)INF'),
context._raise_error(InvalidOperation,
'(+-)INF % (+-)INF'))
+ return reloco
return context._raise_error(InvalidOperation, '(+-)INF/(+-)INF')
if self._isinfinity():
@@ -1554,6 +1574,8 @@
ans = ans._rescale(Etiny, context=context)
# It isn't zero, and exp < Emin => subnormal
context._raise_error(Subnormal)
+ if not ans:
+ context._raise_error(Clamped)
if context.flags[Inexact]:
context._raise_error(Underflow)
else:
@@ -1579,7 +1601,7 @@
return c
return ans
- def _round(self, prec=None, rounding=None, context=None):
+ def _round(self, prec=None, rounding=None, context=None, forceExp=None, fromQuantize=False):
"""Returns a rounded version of self.
You can specify the precision or rounding method. Otherwise, the
@@ -1625,12 +1647,19 @@
temp = Decimal(self)
numdigits = len(temp._int)
- if prec == numdigits:
- return temp
- # See if we need to extend precision
+# # See if we need to extend precision
expdiff = prec - numdigits
- if expdiff > 0:
+
+ # not allowing subnormal for quantize
+ if fromQuantize and (forceExp - context.Emax) > context.prec:
+ context._raise_error(InvalidOperation, "Quantize doesn't allow subnormal")
+ return NaN
+
+ if expdiff >= 0:
+ if fromQuantize and len(temp._int)+expdiff > context.prec:
+ context._raise_error(InvalidOperation, 'Beyond guarded precision')
+ return NaN
tmp = list(temp._int)
tmp.extend([0] * expdiff)
ans = Decimal( (temp._sign, tmp, temp._exp - expdiff))
@@ -1644,18 +1673,49 @@
context._raise_error(Rounded)
return ans
- # Okay, let's round and lose data
-
+ # Okay, let's round and lose data, let's get the correct rounding function
this_function = getattr(temp, self._pick_rounding_function[rounding])
- # Now we've got the rounding function
+ # Now we've got the rounding function
+ origprec = context.prec
if prec != context.prec:
context = context._shallow_copy()
context.prec = prec
ans = this_function(prec, expdiff, context)
+
+ if forceExp is not None:
+ exp = forceExp
+ if fromQuantize and not (context.Emin <= exp <= context.Emax):
+ if (context.Emin <= ans._exp) and ans._int == (0,):
+ context._raise_error(InvalidOperation)
+ return NaN
+
+ if context.Emin < exp < context.Emax:
+ newdiff = ans._exp - exp
+ if newdiff >= 0:
+ ans._int = ans._int + tuple([0]*newdiff)
+ else:
+ ans._int = (0,)
+ ans._exp = exp
+ else:
+ if not context.flags[Underflow]:
+ newdiff = ans._exp - exp
+ if newdiff >= 0:
+ ans._int = ans._int + tuple([0]*newdiff)
+ else:
+ ans._int = (0,)
+
+ ans._exp = exp
+ context._raise_error(Rounded)
+ context._raise_error(Inexact, 'Changed in rounding')
+ return ans
+
+ if len(ans._int) > origprec:
+ context._raise_error(InvalidOperation, 'Beyond guarded precision')
+ return NaN
+
context._raise_error(Rounded)
context._raise_error(Inexact, 'Changed in rounding')
-
return ans
_pick_rounding_function = {}
@@ -1868,7 +1928,7 @@
context = getcontext()
return context._raise_error(InvalidOperation,
'quantize with one INF')
- return self._rescale(exp._exp, rounding, context, watchexp)
+ return self._rescale(exp._exp, rounding, context, watchexp=0, fromQuantize=True)
def same_quantum(self, other):
"""Test whether self and other have the same exponent.
@@ -1882,7 +1942,7 @@
return self._isinfinity() and other._isinfinity() and True
return self._exp == other._exp
- def _rescale(self, exp, rounding=None, context=None, watchexp=1):
+ def _rescale(self, exp, rounding=None, context=None, watchexp=1, fromQuantize=False):
"""Rescales so that the exponent is exp.
exp = exp to scale to (an integer)
@@ -1901,8 +1961,10 @@
if ans:
return ans
- if watchexp and (context.Emax < exp or context.Etiny() > exp):
+ if fromQuantize and (context.Emax < exp or context.Etiny() > exp):
return context._raise_error(InvalidOperation, 'rescale(a, INF)')
+ if fromQuantize and exp < context.Etiny():
+ return context._raise_error(InvalidOperation, '"rhs" must be no less than Etiny')
if not self:
ans = Decimal(self)
@@ -1917,18 +1979,12 @@
return context._raise_error(InvalidOperation, 'Rescale > prec')
tmp = Decimal(self)
- tmp._int = (0,) + tmp._int
- digits += 1
if digits < 0:
tmp._exp = -digits + tmp._exp
tmp._int = (0,1)
digits = 1
- tmp = tmp._round(digits, rounding, context=context)
-
- if tmp._int[0] == 0 and len(tmp._int) > 1:
- tmp._int = tmp._int[1:]
- tmp._exp = exp
+ tmp = tmp._round(digits, rounding, context=context, forceExp=exp, fromQuantize=fromQuantize)
tmp_adjusted = tmp.adjusted()
if tmp and tmp_adjusted < context.Emin:
@@ -2076,7 +2132,7 @@
def max(self, other, context=None):
"""Returns the larger value.
- like max(self, other) except if one is not a number, returns
+ Like max(self, other) except if one is not a number, returns
NaN (and signals if one is sNaN). Also rounds.
"""
other = _convert_other(other)
@@ -2086,6 +2142,8 @@
if self._is_special or other._is_special:
# If one operand is a quiet NaN and the other is number, then the
# number is always returned
+ if other._exp == 'P':
+ return other
sn = self._isnan()
on = other._isnan()
if sn or on:
@@ -2844,6 +2902,8 @@
The operation is not affected by the context.
"""
+ if a._exp == 'P':
+ a = self._raise_error(ConversionSyntax)
return a.__str__(context=self)
def to_integral(self, a):
@@ -3071,7 +3131,6 @@
NaN = Decimal('NaN')
-
##### crud for parsing strings #############################################
import re
More information about the Python-checkins
mailing list