https://github.com/python/cpython/commit/6ecf77dbdec7838e9ce2298cb8d16e8c2250da81
commit: 6ecf77dbdec7838e9ce2298cb8d16e8c2250da81
branch: main
author: Sergey B Kirpichev <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2025-10-14T11:02:02Z
summary:

gh-102431: Clarify constraints on operands of Decimal logical operations 
(GH-102836)

Sync C/Python implementation of the decimal: logical_ops for contexts.

files:
A Misc/NEWS.d/next/Library/2023-03-21-10-59-40.gh-issue-102431.eUDnf4.rst
M Lib/_pydecimal.py
M Modules/_decimal/_decimal.c
M Modules/_decimal/clinic/_decimal.c.h

diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py
index 9b8e42a2342536..97a629fe92ccec 100644
--- a/Lib/_pydecimal.py
+++ b/Lib/_pydecimal.py
@@ -3340,7 +3340,10 @@ def _fill_logical(self, context, opa, opb):
         return opa, opb
 
     def logical_and(self, other, context=None):
-        """Applies an 'and' operation between self and other's digits."""
+        """Applies an 'and' operation between self and other's digits.
+
+        Both self and other must be logical numbers.
+        """
         if context is None:
             context = getcontext()
 
@@ -3357,14 +3360,20 @@ def logical_and(self, other, context=None):
         return _dec_from_triple(0, result.lstrip('0') or '0', 0)
 
     def logical_invert(self, context=None):
-        """Invert all its digits."""
+        """Invert all its digits.
+
+        The self must be logical number.
+        """
         if context is None:
             context = getcontext()
         return self.logical_xor(_dec_from_triple(0,'1'*context.prec,0),
                                 context)
 
     def logical_or(self, other, context=None):
-        """Applies an 'or' operation between self and other's digits."""
+        """Applies an 'or' operation between self and other's digits.
+
+        Both self and other must be logical numbers.
+        """
         if context is None:
             context = getcontext()
 
@@ -3381,7 +3390,10 @@ def logical_or(self, other, context=None):
         return _dec_from_triple(0, result.lstrip('0') or '0', 0)
 
     def logical_xor(self, other, context=None):
-        """Applies an 'xor' operation between self and other's digits."""
+        """Applies an 'xor' operation between self and other's digits.
+
+        Both self and other must be logical numbers.
+        """
         if context is None:
             context = getcontext()
 
diff --git 
a/Misc/NEWS.d/next/Library/2023-03-21-10-59-40.gh-issue-102431.eUDnf4.rst 
b/Misc/NEWS.d/next/Library/2023-03-21-10-59-40.gh-issue-102431.eUDnf4.rst
new file mode 100644
index 00000000000000..e82ddb6e1011ad
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-03-21-10-59-40.gh-issue-102431.eUDnf4.rst
@@ -0,0 +1,2 @@
+Clarify constraints for "logical" arguments in methods of
+:class:`decimal.Context`.
diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c
index 04b6695f8af06a..4e2a4953126360 100644
--- a/Modules/_decimal/_decimal.c
+++ b/Modules/_decimal/_decimal.c
@@ -5235,13 +5235,15 @@ _decimal_Decimal_copy_negate_impl(PyObject *self, 
PyTypeObject *cls)
 /*[clinic input]
 _decimal.Decimal.logical_invert = _decimal.Decimal.exp
 
-Return the digit-wise inversion of the (logical) operand.
+Invert all its digits.
+
+The self must be logical number.
 [clinic start generated code]*/
 
 static PyObject *
 _decimal_Decimal_logical_invert_impl(PyObject *self, PyTypeObject *cls,
                                      PyObject *context)
-/*[clinic end generated code: output=c626ed4b104a97b7 input=3531dac8b9548dad]*/
+/*[clinic end generated code: output=c626ed4b104a97b7 input=7158d5b525417955]*/
 Dec_UnaryFuncVA(mpd_qinvert)
 
 /*[clinic input]
@@ -5471,37 +5473,43 @@ _decimal_Decimal_same_quantum_impl(PyObject *self, 
PyTypeObject *cls,
 /*[clinic input]
 _decimal.Decimal.logical_and = _decimal.Decimal.compare
 
-Return the digit-wise 'and' of the two (logical) operands.
+Applies an 'and' operation between self and other's digits.
+
+Both self and other must be logical numbers.
 [clinic start generated code]*/
 
 static PyObject *
 _decimal_Decimal_logical_and_impl(PyObject *self, PyTypeObject *cls,
                                   PyObject *other, PyObject *context)
-/*[clinic end generated code: output=9a4cbb74c180b0bb input=2b319baee8970929]*/
+/*[clinic end generated code: output=9a4cbb74c180b0bb input=f22460f1285782d2]*/
 Dec_BinaryFuncVA(mpd_qand)
 
 /*[clinic input]
 _decimal.Decimal.logical_or = _decimal.Decimal.compare
 
-Return the digit-wise 'or' of the two (logical) operands.
+Applies an 'or' operation between self and other's digits.
+
+Both self and other must be logical numbers.
 [clinic start generated code]*/
 
 static PyObject *
 _decimal_Decimal_logical_or_impl(PyObject *self, PyTypeObject *cls,
                                  PyObject *other, PyObject *context)
-/*[clinic end generated code: output=063c4de18dc41ecb input=75e0e1d4dd373b90]*/
+/*[clinic end generated code: output=063c4de18dc41ecb input=b5afa1e1fdebdfce]*/
 Dec_BinaryFuncVA(mpd_qor)
 
 /*[clinic input]
 _decimal.Decimal.logical_xor = _decimal.Decimal.compare
 
-Return the digit-wise 'xor' of the two (logical) operands.
+Applies an 'xor' operation between self and other's digits.
+
+Both self and other must be logical numbers.
 [clinic start generated code]*/
 
 static PyObject *
 _decimal_Decimal_logical_xor_impl(PyObject *self, PyTypeObject *cls,
                                   PyObject *other, PyObject *context)
-/*[clinic end generated code: output=829b09cb49926ad7 input=a1ed8d6ac38c1c9e]*/
+/*[clinic end generated code: output=829b09cb49926ad7 input=84d722ada08a2da7]*/
 Dec_BinaryFuncVA(mpd_qxor)
 
 /*[clinic input]
@@ -7099,13 +7107,26 @@ DecCtx_UnaryFunc(mpd_qlogb)
 /*[clinic input]
 _decimal.Context.logical_invert = _decimal.Context.abs
 
-Invert all digits of x.
+Invert all the digits in the operand.
+
+The operand must be a logical number.
+
+    >>> ExtendedContext.logical_invert(Decimal('0'))
+    Decimal('111111111')
+    >>> ExtendedContext.logical_invert(Decimal('1'))
+    Decimal('111111110')
+    >>> ExtendedContext.logical_invert(Decimal('111111111'))
+    Decimal('0')
+    >>> ExtendedContext.logical_invert(Decimal('101010101'))
+    Decimal('10101010')
+    >>> ExtendedContext.logical_invert(1101)
+    Decimal('111110010')
 [clinic start generated code]*/
 
 static PyObject *
 _decimal_Context_logical_invert_impl(PyObject *context, PyTypeObject *cls,
                                      PyObject *x)
-/*[clinic end generated code: output=97760277a958e2b0 input=1fa8dcc59c557fcc]*/
+/*[clinic end generated code: output=97760277a958e2b0 input=8e568f4c745ab596]*/
 DecCtx_UnaryFunc(mpd_qinvert)
 
 /*[clinic input]
@@ -7262,37 +7283,100 @@ _decimal_Context_copy_sign_impl(PyObject *context, 
PyTypeObject *cls,
 /*[clinic input]
 _decimal.Context.logical_and = _decimal.Context.add
 
-Digit-wise and of x and y.
+Applies the logical operation 'and' between each operand's digits.
+
+The operands must be both logical numbers.
+
+    >>> ExtendedContext.logical_and(Decimal('0'), Decimal('0'))
+    Decimal('0')
+    >>> ExtendedContext.logical_and(Decimal('0'), Decimal('1'))
+    Decimal('0')
+    >>> ExtendedContext.logical_and(Decimal('1'), Decimal('0'))
+    Decimal('0')
+    >>> ExtendedContext.logical_and(Decimal('1'), Decimal('1'))
+    Decimal('1')
+    >>> ExtendedContext.logical_and(Decimal('1100'), Decimal('1010'))
+    Decimal('1000')
+    >>> ExtendedContext.logical_and(Decimal('1111'), Decimal('10'))
+    Decimal('10')
+    >>> ExtendedContext.logical_and(110, 1101)
+    Decimal('100')
+    >>> ExtendedContext.logical_and(Decimal(110), 1101)
+    Decimal('100')
+    >>> ExtendedContext.logical_and(110, Decimal(1101))
+    Decimal('100')
 [clinic start generated code]*/
 
 static PyObject *
 _decimal_Context_logical_and_impl(PyObject *context, PyTypeObject *cls,
                                   PyObject *x, PyObject *y)
-/*[clinic end generated code: output=009dfa08ecaa2ac8 input=30ee33b5b365fd80]*/
+/*[clinic end generated code: output=009dfa08ecaa2ac8 input=bcb7d3d6ab7530de]*/
 DecCtx_BinaryFunc(mpd_qand)
 
 /*[clinic input]
 _decimal.Context.logical_or = _decimal.Context.add
 
-Digit-wise or of x and y.
+Applies the logical operation 'or' between each operand's digits.
+
+The operands must be both logical numbers.
+
+    >>> ExtendedContext.logical_or(Decimal('0'), Decimal('0'))
+    Decimal('0')
+    >>> ExtendedContext.logical_or(Decimal('0'), Decimal('1'))
+    Decimal('1')
+    >>> ExtendedContext.logical_or(Decimal('1'), Decimal('0'))
+    Decimal('1')
+    >>> ExtendedContext.logical_or(Decimal('1'), Decimal('1'))
+    Decimal('1')
+    >>> ExtendedContext.logical_or(Decimal('1100'), Decimal('1010'))
+    Decimal('1110')
+    >>> ExtendedContext.logical_or(Decimal('1110'), Decimal('10'))
+    Decimal('1110')
+    >>> ExtendedContext.logical_or(110, 1101)
+    Decimal('1111')
+    >>> ExtendedContext.logical_or(Decimal(110), 1101)
+    Decimal('1111')
+    >>> ExtendedContext.logical_or(110, Decimal(1101))
+    Decimal('1111')
 [clinic start generated code]*/
 
 static PyObject *
 _decimal_Context_logical_or_impl(PyObject *context, PyTypeObject *cls,
                                  PyObject *x, PyObject *y)
-/*[clinic end generated code: output=eb38617e8d31bf12 input=3b1a6725d0262fb9]*/
+/*[clinic end generated code: output=eb38617e8d31bf12 input=47b45d296fb90846]*/
 DecCtx_BinaryFunc(mpd_qor)
 
 /*[clinic input]
 _decimal.Context.logical_xor = _decimal.Context.add
 
-Digit-wise xor of x and y.
+Applies the logical operation 'xor' between each operand's digits.
+
+The operands must be both logical numbers.
+
+    >>> ExtendedContext.logical_xor(Decimal('0'), Decimal('0'))
+    Decimal('0')
+    >>> ExtendedContext.logical_xor(Decimal('0'), Decimal('1'))
+    Decimal('1')
+    >>> ExtendedContext.logical_xor(Decimal('1'), Decimal('0'))
+    Decimal('1')
+    >>> ExtendedContext.logical_xor(Decimal('1'), Decimal('1'))
+    Decimal('0')
+    >>> ExtendedContext.logical_xor(Decimal('1100'), Decimal('1010'))
+    Decimal('110')
+    >>> ExtendedContext.logical_xor(Decimal('1111'), Decimal('10'))
+    Decimal('1101')
+    >>> ExtendedContext.logical_xor(110, 1101)
+    Decimal('1011')
+    >>> ExtendedContext.logical_xor(Decimal(110), 1101)
+    Decimal('1011')
+    >>> ExtendedContext.logical_xor(110, Decimal(1101))
+    Decimal('1011')
 [clinic start generated code]*/
 
 static PyObject *
 _decimal_Context_logical_xor_impl(PyObject *context, PyTypeObject *cls,
                                   PyObject *x, PyObject *y)
-/*[clinic end generated code: output=23cd81fdcd865d5a input=5ebbbe8bb35da380]*/
+/*[clinic end generated code: output=23cd81fdcd865d5a input=fcaaf828c1d2d089]*/
 DecCtx_BinaryFunc(mpd_qxor)
 
 /*[clinic input]
diff --git a/Modules/_decimal/clinic/_decimal.c.h 
b/Modules/_decimal/clinic/_decimal.c.h
index ccfbf63a7cead5..b09200845d12e9 100644
--- a/Modules/_decimal/clinic/_decimal.c.h
+++ b/Modules/_decimal/clinic/_decimal.c.h
@@ -2744,7 +2744,9 @@ PyDoc_STRVAR(_decimal_Decimal_logical_invert__doc__,
 "logical_invert($self, /, context=None)\n"
 "--\n"
 "\n"
-"Return the digit-wise inversion of the (logical) operand.");
+"Invert all its digits.\n"
+"\n"
+"The self must be logical number.");
 
 #define _DECIMAL_DECIMAL_LOGICAL_INVERT_METHODDEF    \
     {"logical_invert", _PyCFunction_CAST(_decimal_Decimal_logical_invert), 
METH_METHOD|METH_FASTCALL|METH_KEYWORDS, 
_decimal_Decimal_logical_invert__doc__},
@@ -3336,7 +3338,9 @@ PyDoc_STRVAR(_decimal_Decimal_logical_and__doc__,
 "logical_and($self, /, other, context=None)\n"
 "--\n"
 "\n"
-"Return the digit-wise \'and\' of the two (logical) operands.");
+"Applies an \'and\' operation between self and other\'s digits.\n"
+"\n"
+"Both self and other must be logical numbers.");
 
 #define _DECIMAL_DECIMAL_LOGICAL_AND_METHODDEF    \
     {"logical_and", _PyCFunction_CAST(_decimal_Decimal_logical_and), 
METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_logical_and__doc__},
@@ -3402,7 +3406,9 @@ PyDoc_STRVAR(_decimal_Decimal_logical_or__doc__,
 "logical_or($self, /, other, context=None)\n"
 "--\n"
 "\n"
-"Return the digit-wise \'or\' of the two (logical) operands.");
+"Applies an \'or\' operation between self and other\'s digits.\n"
+"\n"
+"Both self and other must be logical numbers.");
 
 #define _DECIMAL_DECIMAL_LOGICAL_OR_METHODDEF    \
     {"logical_or", _PyCFunction_CAST(_decimal_Decimal_logical_or), 
METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_logical_or__doc__},
@@ -3468,7 +3474,9 @@ PyDoc_STRVAR(_decimal_Decimal_logical_xor__doc__,
 "logical_xor($self, /, other, context=None)\n"
 "--\n"
 "\n"
-"Return the digit-wise \'xor\' of the two (logical) operands.");
+"Applies an \'xor\' operation between self and other\'s digits.\n"
+"\n"
+"Both self and other must be logical numbers.");
 
 #define _DECIMAL_DECIMAL_LOGICAL_XOR_METHODDEF    \
     {"logical_xor", _PyCFunction_CAST(_decimal_Decimal_logical_xor), 
METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Decimal_logical_xor__doc__},
@@ -6235,7 +6243,20 @@ PyDoc_STRVAR(_decimal_Context_logical_invert__doc__,
 "logical_invert($self, x, /)\n"
 "--\n"
 "\n"
-"Invert all digits of x.");
+"Invert all the digits in the operand.\n"
+"\n"
+"The operand must be a logical number.\n"
+"\n"
+"    >>> ExtendedContext.logical_invert(Decimal(\'0\'))\n"
+"    Decimal(\'111111111\')\n"
+"    >>> ExtendedContext.logical_invert(Decimal(\'1\'))\n"
+"    Decimal(\'111111110\')\n"
+"    >>> ExtendedContext.logical_invert(Decimal(\'111111111\'))\n"
+"    Decimal(\'0\')\n"
+"    >>> ExtendedContext.logical_invert(Decimal(\'101010101\'))\n"
+"    Decimal(\'10101010\')\n"
+"    >>> ExtendedContext.logical_invert(1101)\n"
+"    Decimal(\'111110010\')");
 
 #define _DECIMAL_CONTEXT_LOGICAL_INVERT_METHODDEF    \
     {"logical_invert", _PyCFunction_CAST(_decimal_Context_logical_invert), 
METH_METHOD|METH_FASTCALL|METH_KEYWORDS, 
_decimal_Context_logical_invert__doc__},
@@ -6556,7 +6577,28 @@ PyDoc_STRVAR(_decimal_Context_logical_and__doc__,
 "logical_and($self, x, y, /)\n"
 "--\n"
 "\n"
-"Digit-wise and of x and y.");
+"Applies the logical operation \'and\' between each operand\'s digits.\n"
+"\n"
+"The operands must be both logical numbers.\n"
+"\n"
+"    >>> ExtendedContext.logical_and(Decimal(\'0\'), Decimal(\'0\'))\n"
+"    Decimal(\'0\')\n"
+"    >>> ExtendedContext.logical_and(Decimal(\'0\'), Decimal(\'1\'))\n"
+"    Decimal(\'0\')\n"
+"    >>> ExtendedContext.logical_and(Decimal(\'1\'), Decimal(\'0\'))\n"
+"    Decimal(\'0\')\n"
+"    >>> ExtendedContext.logical_and(Decimal(\'1\'), Decimal(\'1\'))\n"
+"    Decimal(\'1\')\n"
+"    >>> ExtendedContext.logical_and(Decimal(\'1100\'), Decimal(\'1010\'))\n"
+"    Decimal(\'1000\')\n"
+"    >>> ExtendedContext.logical_and(Decimal(\'1111\'), Decimal(\'10\'))\n"
+"    Decimal(\'10\')\n"
+"    >>> ExtendedContext.logical_and(110, 1101)\n"
+"    Decimal(\'100\')\n"
+"    >>> ExtendedContext.logical_and(Decimal(110), 1101)\n"
+"    Decimal(\'100\')\n"
+"    >>> ExtendedContext.logical_and(110, Decimal(1101))\n"
+"    Decimal(\'100\')");
 
 #define _DECIMAL_CONTEXT_LOGICAL_AND_METHODDEF    \
     {"logical_and", _PyCFunction_CAST(_decimal_Context_logical_and), 
METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_logical_and__doc__},
@@ -6603,7 +6645,28 @@ PyDoc_STRVAR(_decimal_Context_logical_or__doc__,
 "logical_or($self, x, y, /)\n"
 "--\n"
 "\n"
-"Digit-wise or of x and y.");
+"Applies the logical operation \'or\' between each operand\'s digits.\n"
+"\n"
+"The operands must be both logical numbers.\n"
+"\n"
+"    >>> ExtendedContext.logical_or(Decimal(\'0\'), Decimal(\'0\'))\n"
+"    Decimal(\'0\')\n"
+"    >>> ExtendedContext.logical_or(Decimal(\'0\'), Decimal(\'1\'))\n"
+"    Decimal(\'1\')\n"
+"    >>> ExtendedContext.logical_or(Decimal(\'1\'), Decimal(\'0\'))\n"
+"    Decimal(\'1\')\n"
+"    >>> ExtendedContext.logical_or(Decimal(\'1\'), Decimal(\'1\'))\n"
+"    Decimal(\'1\')\n"
+"    >>> ExtendedContext.logical_or(Decimal(\'1100\'), Decimal(\'1010\'))\n"
+"    Decimal(\'1110\')\n"
+"    >>> ExtendedContext.logical_or(Decimal(\'1110\'), Decimal(\'10\'))\n"
+"    Decimal(\'1110\')\n"
+"    >>> ExtendedContext.logical_or(110, 1101)\n"
+"    Decimal(\'1111\')\n"
+"    >>> ExtendedContext.logical_or(Decimal(110), 1101)\n"
+"    Decimal(\'1111\')\n"
+"    >>> ExtendedContext.logical_or(110, Decimal(1101))\n"
+"    Decimal(\'1111\')");
 
 #define _DECIMAL_CONTEXT_LOGICAL_OR_METHODDEF    \
     {"logical_or", _PyCFunction_CAST(_decimal_Context_logical_or), 
METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_logical_or__doc__},
@@ -6650,7 +6713,28 @@ PyDoc_STRVAR(_decimal_Context_logical_xor__doc__,
 "logical_xor($self, x, y, /)\n"
 "--\n"
 "\n"
-"Digit-wise xor of x and y.");
+"Applies the logical operation \'xor\' between each operand\'s digits.\n"
+"\n"
+"The operands must be both logical numbers.\n"
+"\n"
+"    >>> ExtendedContext.logical_xor(Decimal(\'0\'), Decimal(\'0\'))\n"
+"    Decimal(\'0\')\n"
+"    >>> ExtendedContext.logical_xor(Decimal(\'0\'), Decimal(\'1\'))\n"
+"    Decimal(\'1\')\n"
+"    >>> ExtendedContext.logical_xor(Decimal(\'1\'), Decimal(\'0\'))\n"
+"    Decimal(\'1\')\n"
+"    >>> ExtendedContext.logical_xor(Decimal(\'1\'), Decimal(\'1\'))\n"
+"    Decimal(\'0\')\n"
+"    >>> ExtendedContext.logical_xor(Decimal(\'1100\'), Decimal(\'1010\'))\n"
+"    Decimal(\'110\')\n"
+"    >>> ExtendedContext.logical_xor(Decimal(\'1111\'), Decimal(\'10\'))\n"
+"    Decimal(\'1101\')\n"
+"    >>> ExtendedContext.logical_xor(110, 1101)\n"
+"    Decimal(\'1011\')\n"
+"    >>> ExtendedContext.logical_xor(Decimal(110), 1101)\n"
+"    Decimal(\'1011\')\n"
+"    >>> ExtendedContext.logical_xor(110, Decimal(1101))\n"
+"    Decimal(\'1011\')");
 
 #define _DECIMAL_CONTEXT_LOGICAL_XOR_METHODDEF    \
     {"logical_xor", _PyCFunction_CAST(_decimal_Context_logical_xor), 
METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _decimal_Context_logical_xor__doc__},
@@ -6896,4 +6980,4 @@ _decimal_Context_same_quantum(PyObject *context, 
PyTypeObject *cls, PyObject *co
 #ifndef _DECIMAL_CONTEXT_APPLY_METHODDEF
     #define _DECIMAL_CONTEXT_APPLY_METHODDEF
 #endif /* !defined(_DECIMAL_CONTEXT_APPLY_METHODDEF) */
-/*[clinic end generated code: output=e938de3a355a353a input=a9049054013a1b77]*/
+/*[clinic end generated code: output=b288181c82fdc9f1 input=a9049054013a1b77]*/

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to