diff options
| author | Stefan Krah <skrah@bytereef.org> | 2015-12-28 22:02:02 (GMT) |
|---|---|---|
| committer | Stefan Krah <skrah@bytereef.org> | 2015-12-28 22:02:02 (GMT) |
| commit | 53f2e0ad45e41c007f714e077ccf11643651ef28 (patch) | |
| tree | c00ffd5dd955b9685de5c1a12fd014745b22f361 /Lib | |
| parent | ac1e7f6983ef4f0feef6389d458544bf924f6965 (diff) | |
| download | cpython-53f2e0ad45e41c007f714e077ccf11643651ef28.zip cpython-53f2e0ad45e41c007f714e077ccf11643651ef28.tar.gz cpython-53f2e0ad45e41c007f714e077ccf11643651ef28.tar.bz2 | |
Issue #25928: Add Decimal.as_integer_ratio(). Python parts and docs by
Mark Dickinson.
Diffstat (limited to 'Lib')
| -rw-r--r-- | Lib/_pydecimal.py | 52 | ||||
| -rw-r--r-- | Lib/test/test_decimal.py | 33 |
2 files changed, 85 insertions, 0 deletions
diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py index 05ba4ee..eb7bba8 100644 --- a/Lib/_pydecimal.py +++ b/Lib/_pydecimal.py @@ -1010,6 +1010,58 @@ class Decimal(object): """ return DecimalTuple(self._sign, tuple(map(int, self._int)), self._exp) + def as_integer_ratio(self): + """Express a finite Decimal instance in the form n / d. + + Returns a pair (n, d) of integers. When called on an infinity + or NaN, raises OverflowError or ValueError respectively. + + >>> Decimal('3.14').as_integer_ratio() + (157, 50) + >>> Decimal('-123e5').as_integer_ratio() + (-12300000, 1) + >>> Decimal('0.00').as_integer_ratio() + (0, 1) + + """ + if self._is_special: + if self.is_nan(): + raise ValueError("Cannot pass NaN " + "to decimal.as_integer_ratio.") + else: + raise OverflowError("Cannot pass infinity " + "to decimal.as_integer_ratio.") + + if not self: + return 0, 1 + + # Find n, d in lowest terms such that abs(self) == n / d; + # we'll deal with the sign later. + n = int(self._int) + if self._exp >= 0: + # self is an integer. + n, d = n * 10**self._exp, 1 + else: + # Find d2, d5 such that abs(self) = n / (2**d2 * 5**d5). + d5 = -self._exp + while d5 > 0 and n % 5 == 0: + n //= 5 + d5 -= 1 + + # (n & -n).bit_length() - 1 counts trailing zeros in binary + # representation of n (provided n is nonzero). + d2 = -self._exp + shift2 = min((n & -n).bit_length() - 1, d2) + if shift2: + n >>= shift2 + d2 -= shift2 + + d = 5**d5 << d2 + + if self._sign: + n = -n + return n, d + def __repr__(self): """Represents the number as an instance of Decimal.""" # Invariant: eval(repr(d)) == d diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 137aaa5..f6d58ff 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -2047,6 +2047,39 @@ class UsabilityTest(unittest.TestCase): d = Decimal( (1, (0, 2, 7, 1), 'F') ) self.assertEqual(d.as_tuple(), (1, (0,), 'F')) + def test_as_integer_ratio(self): + Decimal = self.decimal.Decimal + + # exceptional cases + self.assertRaises(OverflowError, + Decimal.as_integer_ratio, Decimal('inf')) + self.assertRaises(OverflowError, + Decimal.as_integer_ratio, Decimal('-inf')) + self.assertRaises(ValueError, + Decimal.as_integer_ratio, Decimal('-nan')) + self.assertRaises(ValueError, + Decimal.as_integer_ratio, Decimal('snan123')) + + for exp in range(-4, 2): + for coeff in range(1000): + for sign in '+', '-': + d = Decimal('%s%dE%d' % (sign, coeff, exp)) + pq = d.as_integer_ratio() + p, q = pq + + # check return type + self.assertIsInstance(pq, tuple) + self.assertIsInstance(p, int) + self.assertIsInstance(q, int) + + # check normalization: q should be positive; + # p should be relatively prime to q. + self.assertGreater(q, 0) + self.assertEqual(math.gcd(p, q), 1) + + # check that p/q actually gives the correct value + self.assertEqual(Decimal(p) / Decimal(q), d) + def test_subclassing(self): # Different behaviours when subclassing Decimal Decimal = self.decimal.Decimal |
