bpo-41324 Add a minimal decimal capsule API (#21519)
diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py
index 5d0992a..113b37d 100644
--- a/Lib/test/test_decimal.py
+++ b/Lib/test/test_decimal.py
@@ -43,6 +43,13 @@
import inspect
import threading
+from _testcapi import decimal_is_special
+from _testcapi import decimal_is_nan
+from _testcapi import decimal_is_infinite
+from _testcapi import decimal_get_digits
+from _testcapi import decimal_as_triple
+from _testcapi import decimal_from_triple
+
C = import_fresh_module('decimal', fresh=['_decimal'])
P = import_fresh_module('decimal', blocked=['_decimal'])
@@ -4751,6 +4758,175 @@
self.assertEqual(C.DecTraps,
C.DecErrors|C.DecOverflow|C.DecUnderflow)
+ def test_decimal_api_predicates(self):
+ # Capsule API
+
+ d = C.Decimal("0")
+ self.assertFalse(decimal_is_special(d))
+ self.assertFalse(decimal_is_nan(d))
+ self.assertFalse(decimal_is_infinite(d))
+
+ d = C.Decimal("NaN")
+ self.assertTrue(decimal_is_special(d))
+ self.assertTrue(decimal_is_nan(d))
+ self.assertFalse(decimal_is_infinite(d))
+
+ d = C.Decimal("sNaN")
+ self.assertTrue(decimal_is_special(d))
+ self.assertTrue(decimal_is_nan(d))
+ self.assertFalse(decimal_is_infinite(d))
+
+ d = C.Decimal("inf")
+ self.assertTrue(decimal_is_special(d))
+ self.assertFalse(decimal_is_nan(d))
+ self.assertTrue(decimal_is_infinite(d))
+
+ def test_decimal_api_get_digits(self):
+ # Capsule API
+
+ d = C.Decimal("0")
+ self.assertEqual(decimal_get_digits(d), 1)
+
+ d = C.Decimal("1234567890")
+ self.assertEqual(decimal_get_digits(d), 10)
+
+ d = C.Decimal("inf")
+ self.assertEqual(decimal_get_digits(d), 0)
+
+ d = C.Decimal("NaN")
+ self.assertEqual(decimal_get_digits(d), 0)
+
+ d = C.Decimal("sNaN")
+ self.assertEqual(decimal_get_digits(d), 0)
+
+ d = C.Decimal("NaN1234567890")
+ self.assertEqual(decimal_get_digits(d), 10)
+
+ d = C.Decimal("sNaN1234567890")
+ self.assertEqual(decimal_get_digits(d), 10)
+
+ def test_decimal_api_triple(self):
+ # Capsule API
+
+ def as_triple(d):
+ """Convert a decimal to a decimal triple with a split uint128_t
+ coefficient:
+
+ (sign, hi, lo, exp)
+
+ It is called 'triple' because (hi, lo) are regarded as a single
+ uint128_t that is split because not all compilers support uint128_t.
+ """
+ sign, digits, exp = d.as_tuple()
+
+ s = "".join(str(d) for d in digits)
+ coeff = int(s) if s else 0
+
+ if coeff < 0 or coeff >= 2**128:
+ raise ValueError("value out of bounds for a uint128 triple");
+
+ hi, lo = divmod(coeff, 2**64)
+ return (sign, hi, lo, exp)
+
+ def from_triple(triple):
+ """Convert a decimal triple with a split uint128_t coefficient to a string.
+ """
+ sign, hi, lo, exp = triple
+ coeff = hi * 2**64 + lo
+
+ if coeff < 0 or coeff >= 2**128:
+ raise ValueError("value out of bounds for a uint128 triple");
+
+ digits = tuple(int(c) for c in str(coeff))
+
+ return P.Decimal((sign, digits, exp))
+
+ signs = ["", "-"]
+
+ coefficients = [
+ "000000000000000000000000000000000000000",
+
+ "299999999999999999999999999999999999999",
+ "299999999999999999990000000000000000000",
+ "200000000000000000009999999999999999999",
+ "000000000000000000009999999999999999999",
+
+ "299999999999999999999999999999000000000",
+ "299999999999999999999000000000999999999",
+ "299999999999000000000999999999999999999",
+ "299000000000999999999999999999999999999",
+ "000999999999999999999999999999999999999",
+
+ "300000000000000000000000000000000000000",
+ "310000000000000000001000000000000000000",
+ "310000000000000000000000000000000000000",
+ "300000000000000000001000000000000000000",
+
+ "340100000000100000000100000000100000000",
+ "340100000000100000000100000000000000000",
+ "340100000000100000000000000000100000000",
+ "340100000000000000000100000000100000000",
+ "340000000000100000000100000000100000000",
+
+ "340282366920938463463374607431768211455",
+ ]
+
+ exponents = [
+ "E+0", "E+1", "E-1",
+ "E+%s" % str(C.MAX_EMAX-38),
+ "E-%s" % str(C.MIN_ETINY+38),
+ ]
+
+ for sign in signs:
+ for coeff in coefficients:
+ for exp in exponents:
+ s = sign + coeff + exp
+
+ ctriple = decimal_as_triple(C.Decimal(s))
+ ptriple = as_triple(P.Decimal(s))
+ self.assertEqual(ctriple, ptriple)
+
+ c = decimal_from_triple(ctriple)
+ p = decimal_from_triple(ptriple)
+ self.assertEqual(str(c), str(p))
+
+ for s in ["NaN", "-NaN", "sNaN", "-sNaN", "NaN123", "sNaN123", "inf", "-inf"]:
+ ctriple = decimal_as_triple(C.Decimal(s))
+ ptriple = as_triple(P.Decimal(s))
+ self.assertEqual(ctriple, ptriple)
+
+ c = decimal_from_triple(ctriple)
+ p = decimal_from_triple(ptriple)
+ self.assertEqual(str(c), str(p))
+
+ def test_decimal_api_errors(self):
+ # Capsule API
+
+ self.assertRaises(TypeError, decimal_as_triple, "X")
+ self.assertRaises(ValueError, decimal_as_triple, C.Decimal(2**128))
+ self.assertRaises(ValueError, decimal_as_triple, C.Decimal(-2**128))
+
+ self.assertRaises(TypeError, decimal_from_triple, "X")
+ self.assertRaises(ValueError, decimal_from_triple, ())
+ self.assertRaises(ValueError, decimal_from_triple, (1, 2, 3, 4, 5))
+ self.assertRaises(ValueError, decimal_from_triple, (2**8, 0, 0, 0))
+ self.assertRaises(OverflowError, decimal_from_triple, (0, 2**64, 0, 0))
+ self.assertRaises(OverflowError, decimal_from_triple, (0, 0, 2**64, 0))
+ self.assertRaises(OverflowError, decimal_from_triple, (0, 0, 0, 2**63))
+ self.assertRaises(OverflowError, decimal_from_triple, (0, 0, 0, -2**63-1))
+ self.assertRaises(ValueError, decimal_from_triple, (0, 0, 0, "X"))
+ self.assertRaises(TypeError, decimal_from_triple, (0, 0, 0, ()))
+
+ with C.localcontext(C.Context()):
+ self.assertRaises(C.InvalidOperation, decimal_from_triple, (2, 0, 0, 0))
+ self.assertRaises(C.InvalidOperation, decimal_from_triple, (0, 0, 0, 2**63-1))
+ self.assertRaises(C.InvalidOperation, decimal_from_triple, (0, 0, 0, -2**63))
+
+ self.assertRaises(TypeError, decimal_is_special, "X")
+ self.assertRaises(TypeError, decimal_is_nan, "X")
+ self.assertRaises(TypeError, decimal_is_infinite, "X")
+ self.assertRaises(TypeError, decimal_get_digits, "X")
+
class CWhitebox(unittest.TestCase):
"""Whitebox testing for _decimal"""