[Python-checkins] r68208 - in python/trunk: Doc/library/decimal.rst Lib/decimal.py Lib/test/test_decimal.py Misc/NEWS

raymond.hettinger python-checkins at python.org
Sat Jan 3 20:02:24 CET 2009


Author: raymond.hettinger
Date: Sat Jan 3 20:02:23 2009
New Revision: 68208
Log:
Issue 4796: Add from_float methods to the decimal module.
Modified:
 python/trunk/Doc/library/decimal.rst
 python/trunk/Lib/decimal.py
 python/trunk/Lib/test/test_decimal.py
 python/trunk/Misc/NEWS
Modified: python/trunk/Doc/library/decimal.rst
==============================================================================
--- python/trunk/Doc/library/decimal.rst	(original)
+++ python/trunk/Doc/library/decimal.rst	Sat Jan 3 20:02:23 2009
@@ -484,6 +484,29 @@
 
 .. versionadded:: 2.6
 
+ .. method:: from_float(f)
+
+ Classmethod that converts a float to a decimal number, exactly.
+
+ Note `Decimal.from_float(0.1)` is not the same as `Decimal('0.1')`.
+ Since 0.1 is not exactly representable in binary floating point, the
+ value is stored as the nearest representable value which is
+ `0x1.999999999999ap-4`. That equivalent value in decimal is
+ `0.1000000000000000055511151231257827021181583404541015625`.
+
+ .. doctest::
+
+ >>> Decimal.from_float(0.1)
+ Decimal('0.1000000000000000055511151231257827021181583404541015625')
+ >>> Decimal.from_float(float('nan'))
+ Decimal('NaN')
+ >>> Decimal.from_float(float('inf'))
+ Decimal('Infinity')
+ >>> Decimal.from_float(float('-inf'))
+ Decimal('-Infinity')
+
+ .. versionadded:: 2.7
+
 .. method:: fma(other, third[, context])
 
 Fused multiply-add. Return self*other+third with no rounding of the
@@ -1007,6 +1030,26 @@
 If the argument is a string, no leading or trailing whitespace is
 permitted.
 
+.. method:: create_decimal_from_float(f)
+
+ Creates a new Decimal instance from a float *f* but rounding using *self*
+ as the context. Unlike the :method:`Decimal.from_float` class method,
+ the context precision, rounding method, flags, and traps are applied to
+ the conversion.
+
+ .. doctest::
+
+ >>> context = Context(prec=5, rounding=ROUND_DOWN)
+ >>> context.create_decimal_from_float(math.pi)
+ Decimal('3.1415')
+ >>> context = Context(prec=5, traps=[Inexact])
+ >>> context.create_decimal_from_float(math.pi)
+ Traceback (most recent call last):
+ ...
+ Inexact: None
+
+ .. versionadded:: 2.7
+
 .. method:: Etiny()
 
 Returns a value equal to ``Emin - prec + 1`` which is the minimum exponent
Modified: python/trunk/Lib/decimal.py
==============================================================================
--- python/trunk/Lib/decimal.py	(original)
+++ python/trunk/Lib/decimal.py	Sat Jan 3 20:02:23 2009
@@ -135,6 +135,7 @@
 ]
 
 import copy as _copy
+import math as _math
 
 try:
 from collections import namedtuple as _namedtuple
@@ -242,7 +243,7 @@
 """
 
 def handle(self, context, sign, *args):
- return _SignedInfinity[sign]
+ return _Infsign[sign]
 
 class DivisionImpossible(InvalidOperation):
 """Cannot perform the division adequately.
@@ -340,15 +341,15 @@
 def handle(self, context, sign, *args):
 if context.rounding in (ROUND_HALF_UP, ROUND_HALF_EVEN,
 ROUND_HALF_DOWN, ROUND_UP):
- return _SignedInfinity[sign]
+ return _Infsign[sign]
 if sign == 0:
 if context.rounding == ROUND_CEILING:
- return _SignedInfinity[sign]
+ return _Infsign[sign]
 return _dec_from_triple(sign, '9'*context.prec,
 context.Emax-context.prec+1)
 if sign == 1:
 if context.rounding == ROUND_FLOOR:
- return _SignedInfinity[sign]
+ return _Infsign[sign]
 return _dec_from_triple(sign, '9'*context.prec,
 context.Emax-context.prec+1)
 
@@ -653,6 +654,38 @@
 
 raise TypeError("Cannot convert %r to Decimal" % value)
 
+ @classmethod
+ def from_float(cls, f):
+ """Converts a float to a decimal number, exactly.
+
+ Note that Decimal.from_float(0.1) is not the same as Decimal('0.1').
+ Since 0.1 is not exactly representable in binary floating point, the
+ value is stored as the nearest representable value which is
+ 0x1.999999999999ap-4. The exact equivalent of the value in decimal
+ is 0.1000000000000000055511151231257827021181583404541015625.
+
+ >>> Decimal.from_float(0.1)
+ Decimal('0.1000000000000000055511151231257827021181583404541015625')
+ >>> Decimal.from_float(float('nan'))
+ Decimal('NaN')
+ >>> Decimal.from_float(float('inf'))
+ Decimal('Infinity')
+ >>> Decimal.from_float(-float('inf'))
+ Decimal('-Infinity')
+ >>> Decimal.from_float(-0.0)
+ Decimal('-0')
+
+ """
+ if isinstance(f, (int, long)): # handle integer inputs
+ return cls(f)
+ if _math.isinf(f) or _math.isnan(f): # raises TypeError if not a float
+ return cls(repr(f))
+ sign = 0 if _math.copysign(1.0, f) == 1.0 else 1
+ n, d = abs(f).as_integer_ratio()
+ k = d.bit_length() - 1
+ result = _dec_from_triple(sign, str(n*5**k), -k)
+ return result if cls is Decimal else cls(result)
+
 def _isnan(self):
 """Returns whether the number is not actually one.
 
@@ -1171,12 +1204,12 @@
 if self._isinfinity():
 if not other:
 return context._raise_error(InvalidOperation, '(+-)INF * 0')
- return _SignedInfinity[resultsign]
+ return _Infsign[resultsign]
 
 if other._isinfinity():
 if not self:
 return context._raise_error(InvalidOperation, '0 * (+-)INF')
- return _SignedInfinity[resultsign]
+ return _Infsign[resultsign]
 
 resultexp = self._exp + other._exp
 
@@ -1226,7 +1259,7 @@
 return context._raise_error(InvalidOperation, '(+-)INF/(+-)INF')
 
 if self._isinfinity():
- return _SignedInfinity[sign]
+ return _Infsign[sign]
 
 if other._isinfinity():
 context._raise_error(Clamped, 'Division by infinity')
@@ -1329,7 +1362,7 @@
 ans = context._raise_error(InvalidOperation, 'divmod(INF, INF)')
 return ans, ans
 else:
- return (_SignedInfinity[sign],
+ return (_Infsign[sign],
 context._raise_error(InvalidOperation, 'INF % x'))
 
 if not other:
@@ -1477,7 +1510,7 @@
 if other._isinfinity():
 return context._raise_error(InvalidOperation, 'INF // INF')
 else:
- return _SignedInfinity[self._sign ^ other._sign]
+ return _Infsign[self._sign ^ other._sign]
 
 if not other:
 if self:
@@ -1732,12 +1765,12 @@
 if not other:
 return context._raise_error(InvalidOperation,
 'INF * 0 in fma')
- product = _SignedInfinity[self._sign ^ other._sign]
+ product = _Infsign[self._sign ^ other._sign]
 elif other._exp == 'F':
 if not self:
 return context._raise_error(InvalidOperation,
 '0 * INF in fma')
- product = _SignedInfinity[self._sign ^ other._sign]
+ product = _Infsign[self._sign ^ other._sign]
 else:
 product = _dec_from_triple(self._sign ^ other._sign,
 str(int(self._int) * int(other._int)),
@@ -2087,7 +2120,7 @@
 if not self:
 return context._raise_error(InvalidOperation, '0 ** 0')
 else:
- return _One
+ return _Dec_p1
 
 # result has sign 1 iff self._sign is 1 and other is an odd integer
 result_sign = 0
@@ -2109,19 +2142,19 @@
 if other._sign == 0:
 return _dec_from_triple(result_sign, '0', 0)
 else:
- return _SignedInfinity[result_sign]
+ return _Infsign[result_sign]
 
 # Inf**(+ve or Inf) = Inf; Inf**(-ve or -Inf) = 0
 if self._isinfinity():
 if other._sign == 0:
- return _SignedInfinity[result_sign]
+ return _Infsign[result_sign]
 else:
 return _dec_from_triple(result_sign, '0', 0)
 
 # 1**other = 1, but the choice of exponent and the flags
 # depend on the exponent of self, and on whether other is a
 # positive integer, a negative integer, or neither
- if self == _One:
+ if self == _Dec_p1:
 if other._isinteger():
 # exp = max(self._exp*max(int(other), 0),
 # 1-context.prec) but evaluating int(other) directly
@@ -2154,7 +2187,7 @@
 if (other._sign == 0) == (self_adj < 0):
 return _dec_from_triple(result_sign, '0', 0)
 else:
- return _SignedInfinity[result_sign]
+ return _Infsign[result_sign]
 
 # from here on, the result always goes through the call
 # to _fix at the end of this function.
@@ -2674,9 +2707,9 @@
 """
 # if one is negative and the other is positive, it's easy
 if self._sign and not other._sign:
- return _NegativeOne
+ return _Dec_n1
 if not self._sign and other._sign:
- return _One
+ return _Dec_p1
 sign = self._sign
 
 # let's handle both NaN types
@@ -2686,51 +2719,51 @@
 if self_nan == other_nan:
 if self._int < other._int:
 if sign:
- return _One
+ return _Dec_p1
 else:
- return _NegativeOne
+ return _Dec_n1
 if self._int > other._int:
 if sign:
- return _NegativeOne
+ return _Dec_n1
 else:
- return _One
- return _Zero
+ return _Dec_p1
+ return _Dec_0
 
 if sign:
 if self_nan == 1:
- return _NegativeOne
+ return _Dec_n1
 if other_nan == 1:
- return _One
+ return _Dec_p1
 if self_nan == 2:
- return _NegativeOne
+ return _Dec_n1
 if other_nan == 2:
- return _One
+ return _Dec_p1
 else:
 if self_nan == 1:
- return _One
+ return _Dec_p1
 if other_nan == 1:
- return _NegativeOne
+ return _Dec_n1
 if self_nan == 2:
- return _One
+ return _Dec_p1
 if other_nan == 2:
- return _NegativeOne
+ return _Dec_n1
 
 if self < other:
- return _NegativeOne
+ return _Dec_n1
 if self > other:
- return _One
+ return _Dec_p1
 
 if self._exp < other._exp:
 if sign:
- return _One
+ return _Dec_p1
 else:
- return _NegativeOne
+ return _Dec_n1
 if self._exp > other._exp:
 if sign:
- return _NegativeOne
+ return _Dec_n1
 else:
- return _One
- return _Zero
+ return _Dec_p1
+ return _Dec_0
 
 
 def compare_total_mag(self, other):
@@ -2771,11 +2804,11 @@
 
 # exp(-Infinity) = 0
 if self._isinfinity() == -1:
- return _Zero
+ return _Dec_0
 
 # exp(0) = 1
 if not self:
- return _One
+ return _Dec_p1
 
 # exp(Infinity) = Infinity
 if self._isinfinity() == 1:
@@ -2927,15 +2960,15 @@
 
 # ln(0.0) == -Infinity
 if not self:
- return _NegativeInfinity
+ return _negInf
 
 # ln(Infinity) = Infinity
 if self._isinfinity() == 1:
- return _Infinity
+ return _Inf
 
 # ln(1.0) == 0.0
- if self == _One:
- return _Zero
+ if self == _Dec_p1:
+ return _Dec_0
 
 # ln(negative) raises InvalidOperation
 if self._sign == 1:
@@ -3007,11 +3040,11 @@
 
 # log10(0.0) == -Infinity
 if not self:
- return _NegativeInfinity
+ return _negInf
 
 # log10(Infinity) = Infinity
 if self._isinfinity() == 1:
- return _Infinity
+ return _Inf
 
 # log10(negative or -Infinity) raises InvalidOperation
 if self._sign == 1:
@@ -3063,7 +3096,7 @@
 
 # logb(+/-Inf) = +Inf
 if self._isinfinity():
- return _Infinity
+ return _Inf
 
 # logb(0) = -Inf, DivisionByZero
 if not self:
@@ -3220,7 +3253,7 @@
 return ans
 
 if self._isinfinity() == -1:
- return _NegativeInfinity
+ return _negInf
 if self._isinfinity() == 1:
 return _dec_from_triple(0, '9'*context.prec, context.Etop())
 
@@ -3243,7 +3276,7 @@
 return ans
 
 if self._isinfinity() == 1:
- return _Infinity
+ return _Inf
 if self._isinfinity() == -1:
 return _dec_from_triple(1, '9'*context.prec, context.Etop())
 
@@ -3744,6 +3777,23 @@
 "diagnostic info too long in NaN")
 return d._fix(self)
 
+ def create_decimal_from_float(self, f):
+ """Creates a new Decimal instance from a float but rounding using self
+ as the context.
+
+ >>> context = Context(prec=5, rounding=ROUND_DOWN)
+ >>> context.create_decimal_from_float(3.1415926535897932)
+ Decimal('3.1415')
+ >>> context = Context(prec=5, traps=[Inexact])
+ >>> context.create_decimal_from_float(3.1415926535897932)
+ Traceback (most recent call last):
+ ...
+ Inexact: None
+
+ """
+ d = Decimal.from_float(f) # An exact conversion
+ return d._fix(self) # Apply the context rounding
+
 # Methods
 def abs(self, a):
 """Returns the absolute value of the operand.
@@ -5490,15 +5540,15 @@
 ##### Useful Constants (internal use only) ################################
 
 # Reusable defaults
-_Infinity = Decimal('Inf')
-_NegativeInfinity = Decimal('-Inf')
+_Inf = Decimal('Inf')
+_negInf = Decimal('-Inf')
 _NaN = Decimal('NaN')
-_Zero = Decimal(0)
-_One = Decimal(1)
-_NegativeOne = Decimal(-1)
+_Dec_0 = Decimal(0)
+_Dec_p1 = Decimal(1)
+_Dec_n1 = Decimal(-1)
 
-# _SignedInfinity[sign] is infinity w/ that sign
-_SignedInfinity = (_Infinity, _NegativeInfinity)
+# _Infsign[sign] is infinity w/ that sign
+_Infsign = (_Inf, _negInf)
 
 
 
Modified: python/trunk/Lib/test/test_decimal.py
==============================================================================
--- python/trunk/Lib/test/test_decimal.py	(original)
+++ python/trunk/Lib/test/test_decimal.py	Sat Jan 3 20:02:23 2009
@@ -1361,6 +1361,55 @@
 r = d.to_integral(ROUND_DOWN)
 self.assertEqual(Decimal(math.trunc(d)), r)
 
+ def test_from_float(self):
+
+ class MyDecimal(Decimal):
+ pass
+
+ r = MyDecimal.from_float(0.1)
+ self.assertEqual(type(r), MyDecimal)
+ self.assertEqual(str(r),
+ '0.1000000000000000055511151231257827021181583404541015625')
+ bigint = 12345678901234567890123456789
+ self.assertEqual(MyDecimal.from_float(bigint), MyDecimal(bigint))
+ self.assert_(MyDecimal.from_float(float('nan')).is_qnan())
+ self.assert_(MyDecimal.from_float(float('inf')).is_infinite())
+ self.assert_(MyDecimal.from_float(float('-inf')).is_infinite())
+ self.assertEqual(str(MyDecimal.from_float(float('nan'))),
+ str(Decimal('NaN')))
+ self.assertEqual(str(MyDecimal.from_float(float('inf'))),
+ str(Decimal('Infinity')))
+ self.assertEqual(str(MyDecimal.from_float(float('-inf'))),
+ str(Decimal('-Infinity')))
+ self.assertRaises(TypeError, MyDecimal.from_float, 'abc')
+ for i in range(200):
+ x = random.expovariate(0.01) * (random.random() * 2.0 - 1.0)
+ self.assertEqual(x, float(MyDecimal.from_float(x))) # roundtrip
+
+ def test_create_decimal_from_float(self):
+ context = Context(prec=5, rounding=ROUND_DOWN)
+ self.assertEqual(
+ context.create_decimal_from_float(math.pi),
+ Decimal('3.1415')
+ )
+ context = Context(prec=5, rounding=ROUND_UP)
+ self.assertEqual(
+ context.create_decimal_from_float(math.pi),
+ Decimal('3.1416')
+ )
+ context = Context(prec=5, traps=[Inexact])
+ self.assertRaises(
+ Inexact,
+ context.create_decimal_from_float,
+ math.pi
+ )
+ self.assertEqual(repr(context.create_decimal_from_float(-0.0)),
+ "Decimal('-0')")
+ self.assertEqual(repr(context.create_decimal_from_float(1.0)),
+ "Decimal('1')")
+ self.assertEqual(repr(context.create_decimal_from_float(10)),
+ "Decimal('10')")
+
 class ContextAPItests(unittest.TestCase):
 
 def test_pickle(self):
Modified: python/trunk/Misc/NEWS
==============================================================================
--- python/trunk/Misc/NEWS	(original)
+++ python/trunk/Misc/NEWS	Sat Jan 3 20:02:23 2009
@@ -12,8 +12,6 @@
 Core and Builtins
 -----------------
 
-- Issue #4817: Remove unused function PyOS_GetLastModificationTime.
-
 - Issue #4075: Use OutputDebugStringW in Py_FatalError.
 
 - Issue #4797: IOError.filename was not set when _fileio.FileIO failed to open
@@ -110,6 +108,9 @@
 Library
 -------
 
+- Issue #4796: Added Decimal.from_float() and Context.create_decimal_from_float()
+ to the decimal module.
+
 - Issue #4812: add missing underscore prefix to some internal-use-only
 constants in the decimal module. (Dec_0 becomes _Dec_0, etc.)
 


More information about the Python-checkins mailing list

AltStyle によって変換されたページ (->オリジナル) /