Made the various is_* operations return booleans. This was discussed

with Cawlishaw by mail, and he basically confirmed that to these is_*
operations, there's no need to return Decimal(0) and Decimal(1) if
the language supports the False and True booleans.

Also added a few tests for the these functions in extra.decTest, since
they are mostly untested (apart from the doctests).

Thanks Mark Dickinson
This commit is contained in:
Facundo Batista 2007-10-02 17:01:24 +00:00
parent 31ba8480d8
commit 1a191df14d
3 changed files with 2240 additions and 125 deletions

View File

@ -679,14 +679,11 @@ class Decimal(object):
return 0 return 0
def __nonzero__(self): def __nonzero__(self):
"""Is the number non-zero? """Return True if self is nonzero; otherwise return False.
0 if self == 0 NaNs and infinities are considered nonzero.
1 if self != 0
""" """
if self._is_special: return self._is_special or self._int[0] != 0
return True
return sum(self._int) != 0
def __cmp__(self, other): def __cmp__(self, other):
other = _convert_other(other) other = _convert_other(other)
@ -2239,15 +2236,18 @@ class Decimal(object):
return ans return ans
def same_quantum(self, other): def same_quantum(self, other):
"""Test whether self and other have the same exponent. """Return True if self and other have the same exponent; otherwise
return False.
same as self._exp == other._exp, except NaN == sNaN If either operand is a special value, the following rules are used:
* return True if both operands are infinities
* return True if both operands are NaNs
* otherwise, return False.
""" """
other = _convert_other(other, raiseit=True)
if self._is_special or other._is_special: if self._is_special or other._is_special:
if self._isnan() or other._isnan(): return (self.is_nan() and other.is_nan() or
return self._isnan() and other._isnan() and True self.is_infinite() and other.is_infinite())
if self._isinfinity() or other._isinfinity():
return self._isinfinity() and other._isinfinity() and True
return self._exp == other._exp return self._exp == other._exp
def _rescale(self, exp, rounding): def _rescale(self, exp, rounding):
@ -2730,84 +2730,60 @@ class Decimal(object):
return ans return ans
def is_canonical(self): def is_canonical(self):
"""Returns 1 if self is canonical; otherwise returns 0.""" """Return True if self is canonical; otherwise return False.
return Dec_p1
Currently, the encoding of a Decimal instance is always
canonical, so this method returns True for any Decimal.
"""
return True
def is_finite(self): def is_finite(self):
"""Returns 1 if self is finite, otherwise returns 0. """Return True if self is finite; otherwise return False.
For it to be finite, it must be neither infinite nor a NaN. A Decimal instance is considered finite if it is neither
infinite nor a NaN.
""" """
if self._is_special: return not self._is_special
return Dec_0
else:
return Dec_p1
def is_infinite(self): def is_infinite(self):
"""Returns 1 if self is an Infinite, otherwise returns 0.""" """Return True if self is infinite; otherwise return False."""
if self._isinfinity(): return self._exp == 'F'
return Dec_p1
else:
return Dec_0
def is_nan(self): def is_nan(self):
"""Returns 1 if self is qNaN or sNaN, otherwise returns 0.""" """Return True if self is a qNaN or sNaN; otherwise return False."""
if self._isnan(): return self._exp in ('n', 'N')
return Dec_p1
else:
return Dec_0
def is_normal(self, context=None): def is_normal(self, context=None):
"""Returns 1 if self is a normal number, otherwise returns 0.""" """Return True if self is a normal number; otherwise return False."""
if self._is_special: if self._is_special or not self:
return Dec_0 return False
if not self:
return Dec_0
if context is None: if context is None:
context = getcontext() context = getcontext()
if context.Emin <= self.adjusted() <= context.Emax: return context.Emin <= self.adjusted() <= context.Emax
return Dec_p1
else:
return Dec_0
def is_qnan(self): def is_qnan(self):
"""Returns 1 if self is a quiet NaN, otherwise returns 0.""" """Return True if self is a quiet NaN; otherwise return False."""
if self._isnan() == 1: return self._exp == 'n'
return Dec_p1
else:
return Dec_0
def is_signed(self): def is_signed(self):
"""Returns 1 if self is negative, otherwise returns 0.""" """Return True if self is negative; otherwise return False."""
return Decimal(self._sign) return self._sign == 1
def is_snan(self): def is_snan(self):
"""Returns 1 if self is a signaling NaN, otherwise returns 0.""" """Return True if self is a signaling NaN; otherwise return False."""
if self._isnan() == 2: return self._exp == 'N'
return Dec_p1
else:
return Dec_0
def is_subnormal(self, context=None): def is_subnormal(self, context=None):
"""Returns 1 if self is subnormal, otherwise returns 0.""" """Return True if self is subnormal; otherwise return False."""
if self._is_special: if self._is_special or not self:
return Dec_0 return False
if not self:
return Dec_0
if context is None: if context is None:
context = getcontext() context = getcontext()
return self.adjusted() < context.Emin
r = self._exp + len(self._int)
if r <= context.Emin:
return Dec_p1
return Dec_0
def is_zero(self): def is_zero(self):
"""Returns 1 if self is a zero, otherwise returns 0.""" """Return True if self is a zero; otherwise return False."""
if self: return not self._is_special and self._int[0] == 0
return Dec_0
else:
return Dec_p1
def _ln_exp_bound(self): def _ln_exp_bound(self):
"""Compute a lower bound for the adjusted exponent of self.ln(). """Compute a lower bound for the adjusted exponent of self.ln().
@ -3871,138 +3847,145 @@ class Context(object):
return a.fma(b, c, context=self) return a.fma(b, c, context=self)
def is_canonical(self, a): def is_canonical(self, a):
"""Returns 1 if the operand is canonical; otherwise returns 0. """Return True if the operand is canonical; otherwise return False.
Currently, the encoding of a Decimal instance is always
canonical, so this method returns True for any Decimal.
>>> ExtendedContext.is_canonical(Decimal('2.50')) >>> ExtendedContext.is_canonical(Decimal('2.50'))
Decimal("1") True
""" """
return Dec_p1 return a.is_canonical()
def is_finite(self, a): def is_finite(self, a):
"""Returns 1 if the operand is finite, otherwise returns 0. """Return True if the operand is finite; otherwise return False.
For it to be finite, it must be neither infinite nor a NaN. A Decimal instance is considered finite if it is neither
infinite nor a NaN.
>>> ExtendedContext.is_finite(Decimal('2.50')) >>> ExtendedContext.is_finite(Decimal('2.50'))
Decimal("1") True
>>> ExtendedContext.is_finite(Decimal('-0.3')) >>> ExtendedContext.is_finite(Decimal('-0.3'))
Decimal("1") True
>>> ExtendedContext.is_finite(Decimal('0')) >>> ExtendedContext.is_finite(Decimal('0'))
Decimal("1") True
>>> ExtendedContext.is_finite(Decimal('Inf')) >>> ExtendedContext.is_finite(Decimal('Inf'))
Decimal("0") False
>>> ExtendedContext.is_finite(Decimal('NaN')) >>> ExtendedContext.is_finite(Decimal('NaN'))
Decimal("0") False
""" """
return a.is_finite() return a.is_finite()
def is_infinite(self, a): def is_infinite(self, a):
"""Returns 1 if the operand is an Infinite, otherwise returns 0. """Return True if the operand is infinite; otherwise return False.
>>> ExtendedContext.is_infinite(Decimal('2.50')) >>> ExtendedContext.is_infinite(Decimal('2.50'))
Decimal("0") False
>>> ExtendedContext.is_infinite(Decimal('-Inf')) >>> ExtendedContext.is_infinite(Decimal('-Inf'))
Decimal("1") True
>>> ExtendedContext.is_infinite(Decimal('NaN')) >>> ExtendedContext.is_infinite(Decimal('NaN'))
Decimal("0") False
""" """
return a.is_infinite() return a.is_infinite()
def is_nan(self, a): def is_nan(self, a):
"""Returns 1 if the operand is qNaN or sNaN, otherwise returns 0. """Return True if the operand is a qNaN or sNaN;
otherwise return False.
>>> ExtendedContext.is_nan(Decimal('2.50')) >>> ExtendedContext.is_nan(Decimal('2.50'))
Decimal("0") False
>>> ExtendedContext.is_nan(Decimal('NaN')) >>> ExtendedContext.is_nan(Decimal('NaN'))
Decimal("1") True
>>> ExtendedContext.is_nan(Decimal('-sNaN')) >>> ExtendedContext.is_nan(Decimal('-sNaN'))
Decimal("1") True
""" """
return a.is_nan() return a.is_nan()
def is_normal(self, a): def is_normal(self, a):
"""Returns 1 if the operand is a normal number, otherwise returns 0. """Return True if the operand is a normal number;
otherwise return False.
>>> c = ExtendedContext.copy() >>> c = ExtendedContext.copy()
>>> c.Emin = -999 >>> c.Emin = -999
>>> c.Emax = 999 >>> c.Emax = 999
>>> c.is_normal(Decimal('2.50')) >>> c.is_normal(Decimal('2.50'))
Decimal("1") True
>>> c.is_normal(Decimal('0.1E-999')) >>> c.is_normal(Decimal('0.1E-999'))
Decimal("0") False
>>> c.is_normal(Decimal('0.00')) >>> c.is_normal(Decimal('0.00'))
Decimal("0") False
>>> c.is_normal(Decimal('-Inf')) >>> c.is_normal(Decimal('-Inf'))
Decimal("0") False
>>> c.is_normal(Decimal('NaN')) >>> c.is_normal(Decimal('NaN'))
Decimal("0") False
""" """
return a.is_normal(context=self) return a.is_normal(context=self)
def is_qnan(self, a): def is_qnan(self, a):
"""Returns 1 if the operand is a quiet NaN, otherwise returns 0. """Return True if the operand is a quiet NaN; otherwise return False.
>>> ExtendedContext.is_qnan(Decimal('2.50')) >>> ExtendedContext.is_qnan(Decimal('2.50'))
Decimal("0") False
>>> ExtendedContext.is_qnan(Decimal('NaN')) >>> ExtendedContext.is_qnan(Decimal('NaN'))
Decimal("1") True
>>> ExtendedContext.is_qnan(Decimal('sNaN')) >>> ExtendedContext.is_qnan(Decimal('sNaN'))
Decimal("0") False
""" """
return a.is_qnan() return a.is_qnan()
def is_signed(self, a): def is_signed(self, a):
"""Returns 1 if the operand is negative, otherwise returns 0. """Return True if the operand is negative; otherwise return False.
>>> ExtendedContext.is_signed(Decimal('2.50')) >>> ExtendedContext.is_signed(Decimal('2.50'))
Decimal("0") False
>>> ExtendedContext.is_signed(Decimal('-12')) >>> ExtendedContext.is_signed(Decimal('-12'))
Decimal("1") True
>>> ExtendedContext.is_signed(Decimal('-0')) >>> ExtendedContext.is_signed(Decimal('-0'))
Decimal("1") True
""" """
return a.is_signed() return a.is_signed()
def is_snan(self, a): def is_snan(self, a):
"""Returns 1 if the operand is a signaling NaN, otherwise returns 0. """Return True if the operand is a signaling NaN;
otherwise return False.
>>> ExtendedContext.is_snan(Decimal('2.50')) >>> ExtendedContext.is_snan(Decimal('2.50'))
Decimal("0") False
>>> ExtendedContext.is_snan(Decimal('NaN')) >>> ExtendedContext.is_snan(Decimal('NaN'))
Decimal("0") False
>>> ExtendedContext.is_snan(Decimal('sNaN')) >>> ExtendedContext.is_snan(Decimal('sNaN'))
Decimal("1") True
""" """
return a.is_snan() return a.is_snan()
def is_subnormal(self, a): def is_subnormal(self, a):
"""Returns 1 if the operand is subnormal, otherwise returns 0. """Return True if the operand is subnormal; otherwise return False.
>>> c = ExtendedContext.copy() >>> c = ExtendedContext.copy()
>>> c.Emin = -999 >>> c.Emin = -999
>>> c.Emax = 999 >>> c.Emax = 999
>>> c.is_subnormal(Decimal('2.50')) >>> c.is_subnormal(Decimal('2.50'))
Decimal("0") False
>>> c.is_subnormal(Decimal('0.1E-999')) >>> c.is_subnormal(Decimal('0.1E-999'))
Decimal("1") True
>>> c.is_subnormal(Decimal('0.00')) >>> c.is_subnormal(Decimal('0.00'))
Decimal("0") False
>>> c.is_subnormal(Decimal('-Inf')) >>> c.is_subnormal(Decimal('-Inf'))
Decimal("0") False
>>> c.is_subnormal(Decimal('NaN')) >>> c.is_subnormal(Decimal('NaN'))
Decimal("0") False
""" """
return a.is_subnormal(context=self) return a.is_subnormal(context=self)
def is_zero(self, a): def is_zero(self, a):
"""Returns 1 if the operand is a zero, otherwise returns 0. """Return True if the operand is a zero; otherwise return False.
>>> ExtendedContext.is_zero(Decimal('0')) >>> ExtendedContext.is_zero(Decimal('0'))
Decimal("1") True
>>> ExtendedContext.is_zero(Decimal('2.50')) >>> ExtendedContext.is_zero(Decimal('2.50'))
Decimal("0") False
>>> ExtendedContext.is_zero(Decimal('-0E+2')) >>> ExtendedContext.is_zero(Decimal('-0E+2'))
Decimal("1") True
""" """
return a.is_zero() return a.is_zero()

File diff suppressed because it is too large Load Diff

View File

@ -95,35 +95,61 @@ RoundingDict = {'ceiling' : ROUND_CEILING, #Maps test-case names to roundings.
# Name adapter to be able to change the Decimal and Context # Name adapter to be able to change the Decimal and Context
# interface without changing the test files from Cowlishaw # interface without changing the test files from Cowlishaw
nameAdapter = {'toeng':'to_eng_string', nameAdapter = {'and':'logical_and',
'tosci':'to_sci_string',
'samequantum':'same_quantum',
'tointegral':'to_integral_value',
'tointegralx':'to_integral_exact',
'remaindernear':'remainder_near',
'divideint':'divide_int',
'squareroot':'sqrt',
'apply':'_apply', 'apply':'_apply',
'class':'number_class', 'class':'number_class',
'comparesig':'compare_signal', 'comparesig':'compare_signal',
'comparetotal':'compare_total', 'comparetotal':'compare_total',
'comparetotmag':'compare_total_mag', 'comparetotmag':'compare_total_mag',
'copyabs':'copy_abs',
'copy':'copy_decimal', 'copy':'copy_decimal',
'copyabs':'copy_abs',
'copynegate':'copy_negate', 'copynegate':'copy_negate',
'copysign':'copy_sign', 'copysign':'copy_sign',
'and':'logical_and', 'divideint':'divide_int',
'or':'logical_or',
'xor':'logical_xor',
'invert':'logical_invert', 'invert':'logical_invert',
'iscanonical':'is_canonical',
'isfinite':'is_finite',
'isinfinite':'is_infinite',
'isnan':'is_nan',
'isnormal':'is_normal',
'isqnan':'is_qnan',
'issigned':'is_signed',
'issnan':'is_snan',
'issubnormal':'is_subnormal',
'iszero':'is_zero',
'maxmag':'max_mag', 'maxmag':'max_mag',
'minmag':'min_mag', 'minmag':'min_mag',
'nextminus':'next_minus', 'nextminus':'next_minus',
'nextplus':'next_plus', 'nextplus':'next_plus',
'nexttoward':'next_toward', 'nexttoward':'next_toward',
'or':'logical_or',
'reduce':'normalize', 'reduce':'normalize',
'remaindernear':'remainder_near',
'samequantum':'same_quantum',
'squareroot':'sqrt',
'toeng':'to_eng_string',
'tointegral':'to_integral_value',
'tointegralx':'to_integral_exact',
'tosci':'to_sci_string',
'xor':'logical_xor',
} }
# The following functions return True/False rather than a Decimal instance
LOGICAL_FUNCTIONS = (
'is_canonical',
'is_finite',
'is_infinite',
'is_nan',
'is_normal',
'is_qnan',
'is_signed',
'is_snan',
'is_subnormal',
'is_zero',
'same_quantum',
)
# For some operations (currently exp, ln, log10, power), the decNumber # For some operations (currently exp, ln, log10, power), the decNumber
# reference implementation imposes additional restrictions on the # reference implementation imposes additional restrictions on the
# context and operands. These restrictions are not part of the # context and operands. These restrictions are not part of the
@ -321,7 +347,7 @@ class DecimalTest(unittest.TestCase):
print "--", self.context print "--", self.context
try: try:
result = str(funct(*vals)) result = str(funct(*vals))
if fname == 'same_quantum': if fname in LOGICAL_FUNCTIONS:
result = str(int(eval(result))) # 'True', 'False' -> '1', '0' result = str(int(eval(result))) # 'True', 'False' -> '1', '0'
except Signals, error: except Signals, error:
self.fail("Raised %s in %s" % (error, s)) self.fail("Raised %s in %s" % (error, s))