From 0656acbd807ef3b9c495fdd2d418c40da497b862 Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 11 Mar 2026 08:00:05 -0400 Subject: [PATCH 1/2] Revise Quotient[] builtin --- mathics/builtin/arithfns/basic.py | 5 --- mathics/builtin/intfns/divlike.py | 74 ++++++++++++++++++++++++++----- mathics/builtin/messages.py | 1 + mathics/eval/intfns/divlike.py | 4 ++ 4 files changed, 69 insertions(+), 15 deletions(-) diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py index 27f300298..ceb239b0f 100644 --- a/mathics/builtin/arithfns/basic.py +++ b/mathics/builtin/arithfns/basic.py @@ -369,11 +369,6 @@ class Power(InfixOperator, MPMathFunction): mpmath_name = "power" - messages = { - "infy": "Infinite expression `1` encountered.", - "indet": "Indeterminate expression `1` encountered.", - } - nargs = {2} rules = { "Power[]": "1", diff --git a/mathics/builtin/intfns/divlike.py b/mathics/builtin/intfns/divlike.py index 93593f1a0..ab091ba98 100644 --- a/mathics/builtin/intfns/divlike.py +++ b/mathics/builtin/intfns/divlike.py @@ -5,7 +5,7 @@ """ import sys -from typing import List, Optional +from typing import List, Optional, Union import sympy from sympy import Q, ask @@ -25,12 +25,18 @@ from mathics.core.convert.python import from_bool from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression +from mathics.core.symbols import Symbol from mathics.core.systemsymbols import ( SymbolComplexInfinity, SymbolQuotient, SymbolQuotientRemainder, ) -from mathics.eval.intfns.divlike import eval_GCD, eval_LCM, eval_ModularInverse +from mathics.eval.intfns.divlike import ( + eval_GCD, + eval_LCM, + eval_ModularInverse, + eval_Quotient, +) class CompositeQ(Builtin): @@ -294,34 +300,82 @@ def eval(self, a: Integer, b: Integer, m: Integer, evaluation: Evaluation): class Quotient(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/Quotient.html + :Quotient:https://en.wikipedia.org/wiki/Quotient ( + :WMA link:https://reference.wolfram.com/language/ref/Quotient.html)
'Quotient[m, n]' -
computes the integer quotient of $m$ and $n$. +
computes the integer quotient of $m$ and $n$. For non-complex numbers, this \ + equivalent to 'Floor[m/n]'. When a complex number is involved, it is 'Round[m/n]'. +
'Quotient[m, n, d]' +
computes the integer quotient of $m-d$ and $n$. For non-complex numbers, this \ + is equivalent to 'Floor[(m-d)/n]'. When a complex number is involved, it is \ + 'Round[(m-d)/n]'.
+ ## Plot showing the step-like 'Floor' behavior of 'Quotient': + ## + ## >> DiscretePlot[Quotient[n, 5], {n, 30}] + ## = -Graphics- + + Integer-argument 'Quotient': >> Quotient[23, 7] = 3 + + Rational-argument 'Quotient': + >> Quotient[19/3, 5/2] + = 2 + + 'Quotient' with inexact numbers: + >> Quotient[4.56, 2.5] + = 1 + + 'Quotient' with two complex numbers is same as 'Round[m/n]': + >> Quotient[10.4 + 8 I, 4. + 5 I] + = 2 + + 'Quotient' for large integers: + >> Quotient[10^90, NextPrime[10^80]] + = 9999999999 + + 'Quotient' threads elementwise over lists: + >> Quotient[{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 3] + = {0, 0, 1, 1, 1, 2, 2, 2, 3, 3} """ attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED eval_error = Builtin.generic_argument_error expected_args = (2, 3) - messages = { - "infy": "Infinite expression `1` encountered.", + rules = { + "Quotient[m_Complex, n_?NumberQ]": "Round[m / n]", + "Quotient[m_?NumberQ, n_Complex]": "Round[m / n]", + "Quotient[l_List, n_?NumberQ]": "Map[Quotient[#, m] &, l]", + "Quotient[l_List, n_?NumberQ, d_?NumberQ]": "Map[Quotient[#, m, d] &, l]", } - summary_text = "integer quotient" - def eval(self, m: Integer, n: Integer, evaluation: Evaluation): - "Quotient[m_Integer, n_Integer]" + summary_text = "compute integer quotient" + + def eval(self, m, n, evaluation: Evaluation) -> Union[Symbol, Integer]: + "Quotient[m_?NumberQ, n_?NumberQ]" + py_m = m.value + py_n = n.value + if py_n == 0: + evaluation.message("Quotient", "infy", Expression(SymbolQuotient, m, n)) + return SymbolComplexInfinity + return eval_Quotient(py_m, py_n, 0) + + def eval_with_offset( + self, m, n, d, evaluation: Evaluation + ) -> Union[Symbol, Integer]: + "Quotient[m_?NumberQ, n_?NumberQ, d_?NumberQ]" py_m = m.value py_n = n.value + py_d = d.value if py_n == 0: evaluation.message("Quotient", "infy", Expression(SymbolQuotient, m, n)) return SymbolComplexInfinity - return Integer(py_m // py_n) + return eval_Quotient(py_m, py_n, py_d) class QuotientRemainder(Builtin): diff --git a/mathics/builtin/messages.py b/mathics/builtin/messages.py index b26333655..d7b0998f4 100644 --- a/mathics/builtin/messages.py +++ b/mathics/builtin/messages.py @@ -206,6 +206,7 @@ class General(Builtin): "Single or list of non-negative integers expected at " "position `1`." ), "indet": "Indeterminate expression `1` encountered.", + "infy": "Infinite expression `1` encountered.", "innf": "Non-negative integer or Infinity expected at position `1` in `2`", "int": "Integer expected.", "intp": "Positive integer expected.", diff --git a/mathics/eval/intfns/divlike.py b/mathics/eval/intfns/divlike.py index ca29a95e1..b3f8d8bec 100644 --- a/mathics/eval/intfns/divlike.py +++ b/mathics/eval/intfns/divlike.py @@ -42,3 +42,7 @@ def eval_ModularInverse(k: int, n: int) -> Optional[Integer]: except ValueError: return return Integer(r) + + +def eval_Quotient(m, n, d) -> Integer: + return Integer((m - d) // n) From d8d6c1c9c6057400c5a50e3e556ea3b3caf07a6a Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 11 Mar 2026 09:44:27 -0400 Subject: [PATCH 2/2] Adjust some boundary cases --- mathics/builtin/intfns/divlike.py | 21 +++++++++++++++++---- test/builtin/intfns/test_divlike.py | 14 +++++++++++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/mathics/builtin/intfns/divlike.py b/mathics/builtin/intfns/divlike.py index ab091ba98..1c6d02d83 100644 --- a/mathics/builtin/intfns/divlike.py +++ b/mathics/builtin/intfns/divlike.py @@ -28,6 +28,7 @@ from mathics.core.symbols import Symbol from mathics.core.systemsymbols import ( SymbolComplexInfinity, + SymbolIndeterminate, SymbolQuotient, SymbolQuotientRemainder, ) @@ -361,8 +362,14 @@ def eval(self, m, n, evaluation: Evaluation) -> Union[Symbol, Integer]: py_m = m.value py_n = n.value if py_n == 0: - evaluation.message("Quotient", "infy", Expression(SymbolQuotient, m, n)) - return SymbolComplexInfinity + if py_m == 0: + tag = "indet" + result = SymbolIndeterminate + else: + tag = "infy" + result = SymbolComplexInfinity + evaluation.message("Quotient", tag, Expression(SymbolQuotient, m, n)) + return result return eval_Quotient(py_m, py_n, 0) def eval_with_offset( @@ -373,8 +380,14 @@ def eval_with_offset( py_n = n.value py_d = d.value if py_n == 0: - evaluation.message("Quotient", "infy", Expression(SymbolQuotient, m, n)) - return SymbolComplexInfinity + if py_m - py_d == 0: + tag = "indet" + result = SymbolIndeterminate + else: + tag = "infy" + result = SymbolComplexInfinity + evaluation.message("Quotient", tag, Expression(SymbolQuotient, m, n, d)) + return result return eval_Quotient(py_m, py_n, py_d) diff --git a/test/builtin/intfns/test_divlike.py b/test/builtin/intfns/test_divlike.py index 0a5cc2782..39fa39a65 100644 --- a/test/builtin/intfns/test_divlike.py +++ b/test/builtin/intfns/test_divlike.py @@ -15,7 +15,19 @@ "Quotient[13, 0]", ("Infinite expression Quotient[13, 0] encountered.",), "ComplexInfinity", - None, + "Check Quotient two-argument divide by 0 error result", + ), + ( + "Quotient[1, 0, 1]", + ("Indeterminate expression Quotient[1, 0, 1] encountered.",), + "Indeterminate", + "Check Quotient three-argument 0/0 error result", + ), + ( + "Quotient[0, 0]", + ("Indeterminate expression Quotient[0, 0] encountered.",), + "Indeterminate", + "Check Quotient two-argument 0/0 error", ), ("Quotient[-17, 7]", None, "-3", None), ("Quotient[-17, -4]", None, "4", None),