Issue 4796: Add from_float methods to the decimal module.
This commit is contained in:
parent
5f81741106
commit
771ed76102
|
@ -453,6 +453,29 @@ Decimal objects
|
|||
>>> 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 @@ In addition to the three supplied contexts, new contexts can be created with the
|
|||
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
|
||||
|
|
|
@ -136,6 +136,7 @@ __all__ = [
|
|||
|
||||
import numbers as _numbers
|
||||
import copy as _copy
|
||||
import math as _math
|
||||
|
||||
try:
|
||||
from collections import namedtuple as _namedtuple
|
||||
|
@ -654,6 +655,38 @@ class Decimal(_numbers.Real):
|
|||
|
||||
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 @@ class Context(object):
|
|||
"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.
|
||||
|
|
|
@ -1421,6 +1421,55 @@ class DecimalPythonAPItests(unittest.TestCase):
|
|||
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):
|
||||
|
|
|
@ -12,8 +12,6 @@ What's New in Python 3.1 alpha 0
|
|||
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 @@ Library
|
|||
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.)
|
||||
|
||||
|
|
Loading…
Reference in New Issue