Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 771ed76

Browse files
committed
Issue 4796: Add from_float methods to the decimal module.
1 parent 5f81741 commit 771ed76

4 files changed

Lines changed: 145 additions & 2 deletions

File tree

Doc/library/decimal.rst

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,29 @@ Decimal objects
453453
>>> Decimal(321).exp()
454454
Decimal('2.561702493119680037517373933E+139')
455455

456+
.. method:: from_float(f)
457+
458+
Classmethod that converts a float to a decimal number, exactly.
459+
460+
Note `Decimal.from_float(0.1)` is not the same as `Decimal('0.1')`.
461+
Since 0.1 is not exactly representable in binary floating point, the
462+
value is stored as the nearest representable value which is
463+
`0x1.999999999999ap-4`. That equivalent value in decimal is
464+
`0.1000000000000000055511151231257827021181583404541015625`.
465+
466+
.. doctest::
467+
468+
>>> Decimal.from_float(0.1)
469+
Decimal('0.1000000000000000055511151231257827021181583404541015625')
470+
>>> Decimal.from_float(float('nan'))
471+
Decimal('NaN')
472+
>>> Decimal.from_float(float('inf'))
473+
Decimal('Infinity')
474+
>>> Decimal.from_float(float('-inf'))
475+
Decimal('-Infinity')
476+
477+
.. versionadded:: 2.7
478+
456479
.. method:: fma(other, third[, context])
457480

458481
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
910933
If the argument is a string, no leading or trailing whitespace is
911934
permitted.
912935

936+
.. method:: create_decimal_from_float(f)
937+
938+
Creates a new Decimal instance from a float *f* but rounding using *self*
939+
as the context. Unlike the :method:`Decimal.from_float` class method,
940+
the context precision, rounding method, flags, and traps are applied to
941+
the conversion.
942+
943+
.. doctest::
944+
945+
>>> context = Context(prec=5, rounding=ROUND_DOWN)
946+
>>> context.create_decimal_from_float(math.pi)
947+
Decimal('3.1415')
948+
>>> context = Context(prec=5, traps=[Inexact])
949+
>>> context.create_decimal_from_float(math.pi)
950+
Traceback (most recent call last):
951+
...
952+
decimal.Inexact: None
953+
954+
.. versionadded:: 2.7
955+
913956
.. method:: Etiny()
914957

915958
Returns a value equal to ``Emin - prec + 1`` which is the minimum exponent

Lib/decimal.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136

137137
import numbers as _numbers
138138
import copy as _copy
139+
import math as _math
139140

140141
try:
141142
from collections import namedtuple as _namedtuple
@@ -654,6 +655,38 @@ def __new__(cls, value="0", context=None):
654655

655656
raise TypeError("Cannot convert %r to Decimal" % value)
656657

658+
@classmethod
659+
def from_float(cls, f):
660+
"""Converts a float to a decimal number, exactly.
661+
662+
Note that Decimal.from_float(0.1) is not the same as Decimal('0.1').
663+
Since 0.1 is not exactly representable in binary floating point, the
664+
value is stored as the nearest representable value which is
665+
0x1.999999999999ap-4. The exact equivalent of the value in decimal
666+
is 0.1000000000000000055511151231257827021181583404541015625.
667+
668+
>>> Decimal.from_float(0.1)
669+
Decimal('0.1000000000000000055511151231257827021181583404541015625')
670+
>>> Decimal.from_float(float('nan'))
671+
Decimal('NaN')
672+
>>> Decimal.from_float(float('inf'))
673+
Decimal('Infinity')
674+
>>> Decimal.from_float(-float('inf'))
675+
Decimal('-Infinity')
676+
>>> Decimal.from_float(-0.0)
677+
Decimal('-0')
678+
679+
"""
680+
if isinstance(f, int): # handle integer inputs
681+
return cls(f)
682+
if _math.isinf(f) or _math.isnan(f): # raises TypeError if not a float
683+
return cls(repr(f))
684+
sign = 0 if _math.copysign(1.0, f) == 1.0 else 1
685+
n, d = abs(f).as_integer_ratio()
686+
k = d.bit_length() - 1
687+
result = _dec_from_triple(sign, str(n*5**k), -k)
688+
return result if cls is Decimal else cls(result)
689+
657690
def _isnan(self):
658691
"""Returns whether the number is not actually one.
659692
@@ -3830,6 +3863,23 @@ def create_decimal(self, num='0'):
38303863
"diagnostic info too long in NaN")
38313864
return d._fix(self)
38323865

3866+
def create_decimal_from_float(self, f):
3867+
"""Creates a new Decimal instance from a float but rounding using self
3868+
as the context.
3869+
3870+
>>> context = Context(prec=5, rounding=ROUND_DOWN)
3871+
>>> context.create_decimal_from_float(3.1415926535897932)
3872+
Decimal('3.1415')
3873+
>>> context = Context(prec=5, traps=[Inexact])
3874+
>>> context.create_decimal_from_float(3.1415926535897932)
3875+
Traceback (most recent call last):
3876+
...
3877+
decimal.Inexact: None
3878+
3879+
"""
3880+
d = Decimal.from_float(f) # An exact conversion
3881+
return d._fix(self) # Apply the context rounding
3882+
38333883
# Methods
38343884
def abs(self, a):
38353885
"""Returns the absolute value of the operand.

Lib/test/test_decimal.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,6 +1421,55 @@ def test_trunc(self):
14211421
r = d.to_integral(ROUND_DOWN)
14221422
self.assertEqual(Decimal(math.trunc(d)), r)
14231423

1424+
def test_from_float(self):
1425+
1426+
class MyDecimal(Decimal):
1427+
pass
1428+
1429+
r = MyDecimal.from_float(0.1)
1430+
self.assertEqual(type(r), MyDecimal)
1431+
self.assertEqual(str(r),
1432+
'0.1000000000000000055511151231257827021181583404541015625')
1433+
bigint = 12345678901234567890123456789
1434+
self.assertEqual(MyDecimal.from_float(bigint), MyDecimal(bigint))
1435+
self.assert_(MyDecimal.from_float(float('nan')).is_qnan())
1436+
self.assert_(MyDecimal.from_float(float('inf')).is_infinite())
1437+
self.assert_(MyDecimal.from_float(float('-inf')).is_infinite())
1438+
self.assertEqual(str(MyDecimal.from_float(float('nan'))),
1439+
str(Decimal('NaN')))
1440+
self.assertEqual(str(MyDecimal.from_float(float('inf'))),
1441+
str(Decimal('Infinity')))
1442+
self.assertEqual(str(MyDecimal.from_float(float('-inf'))),
1443+
str(Decimal('-Infinity')))
1444+
self.assertRaises(TypeError, MyDecimal.from_float, 'abc')
1445+
for i in range(200):
1446+
x = random.expovariate(0.01) * (random.random() * 2.0 - 1.0)
1447+
self.assertEqual(x, float(MyDecimal.from_float(x))) # roundtrip
1448+
1449+
def test_create_decimal_from_float(self):
1450+
context = Context(prec=5, rounding=ROUND_DOWN)
1451+
self.assertEqual(
1452+
context.create_decimal_from_float(math.pi),
1453+
Decimal('3.1415')
1454+
)
1455+
context = Context(prec=5, rounding=ROUND_UP)
1456+
self.assertEqual(
1457+
context.create_decimal_from_float(math.pi),
1458+
Decimal('3.1416')
1459+
)
1460+
context = Context(prec=5, traps=[Inexact])
1461+
self.assertRaises(
1462+
Inexact,
1463+
context.create_decimal_from_float,
1464+
math.pi
1465+
)
1466+
self.assertEqual(repr(context.create_decimal_from_float(-0.0)),
1467+
"Decimal('-0')")
1468+
self.assertEqual(repr(context.create_decimal_from_float(1.0)),
1469+
"Decimal('1')")
1470+
self.assertEqual(repr(context.create_decimal_from_float(10)),
1471+
"Decimal('10')")
1472+
14241473
class ContextAPItests(unittest.TestCase):
14251474

14261475
def test_pickle(self):

Misc/NEWS

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ What's New in Python 3.1 alpha 0
1212
Core and Builtins
1313
-----------------
1414

15-
- Issue #4817: Remove unused function PyOS_GetLastModificationTime.
16-
1715
- Issue #4580: Fix slicing of memoryviews when the item size is greater than
1816
one byte. Also fixes the meaning of len() so that it returns the number of
1917
items, rather than the size in bytes.
@@ -88,6 +86,9 @@ Library
8886
Python 3.x, in accordance with the `official amendments of the spec
8987
<http://www.wsgi.org/wsgi/Amendments_1.0>`_.
9088

89+
- Issue #4796: Added Decimal.from_float() and Context.create_decimal_from_float()
90+
to the decimal module.
91+
9192
- Issue #4812: add missing underscore prefix to some internal-use-only
9293
constants in the decimal module. (Dec_0 becomes _Dec_0, etc.)
9394

0 commit comments

Comments
 (0)