[Python-checkins] r68211 - in python/branches/py3k: 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:20:32 CET 2009
Author: raymond.hettinger
Date: Sat Jan 3 20:20:32 2009
New Revision: 68211
Log:
Issue 4796: Add from_float methods to the decimal module.
Modified:
python/branches/py3k/Doc/library/decimal.rst
python/branches/py3k/Lib/decimal.py
python/branches/py3k/Lib/test/test_decimal.py
python/branches/py3k/Misc/NEWS
Modified: python/branches/py3k/Doc/library/decimal.rst
==============================================================================
--- python/branches/py3k/Doc/library/decimal.rst (original)
+++ python/branches/py3k/Doc/library/decimal.rst Sat Jan 3 20:20:32 2009
@@ -453,6 +453,29 @@
>>> Decimal(321).exp()
Decimal('2.561702493119680037517373933E+139')
+ .. 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
@@ -910,6 +933,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):
+ ...
+ decimal.Inexact: None
+
+ .. versionadded:: 2.7
+
.. method:: Etiny()
Returns a value equal to ``Emin - prec + 1`` which is the minimum exponent
Modified: python/branches/py3k/Lib/decimal.py
==============================================================================
--- python/branches/py3k/Lib/decimal.py (original)
+++ python/branches/py3k/Lib/decimal.py Sat Jan 3 20:20:32 2009
@@ -136,6 +136,7 @@
import numbers as _numbers
import copy as _copy
+import math as _math
try:
from collections import namedtuple as _namedtuple
@@ -654,6 +655,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): # 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.
@@ -3830,6 +3863,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):
+ ...
+ decimal.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.
Modified: python/branches/py3k/Lib/test/test_decimal.py
==============================================================================
--- python/branches/py3k/Lib/test/test_decimal.py (original)
+++ python/branches/py3k/Lib/test/test_decimal.py Sat Jan 3 20:20:32 2009
@@ -1421,6 +1421,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/branches/py3k/Misc/NEWS
==============================================================================
--- python/branches/py3k/Misc/NEWS (original)
+++ python/branches/py3k/Misc/NEWS Sat Jan 3 20:20:32 2009
@@ -12,8 +12,6 @@
Core and Builtins
-----------------
-- Issue #4817: Remove unused function PyOS_GetLastModificationTime.
-
- Issue #4580: Fix slicing of memoryviews when the item size is greater than
one byte. Also fixes the meaning of len() so that it returns the number of
items, rather than the size in bytes.
@@ -88,6 +86,9 @@
Python 3.x, in accordance with the `official amendments of the spec
<http://www.wsgi.org/wsgi/Amendments_1.0>`_.
+- 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