Issue #19543: Implementation of isclose as per PEP 485
For details, see:
PEP 0485 -- A Function for testing approximate equality
Functions added: math.isclose() and cmath.isclose().
Original code by Chris Barker. Patch by Tal Einat.
diff --git a/Modules/clinic/cmathmodule.c.h b/Modules/clinic/cmathmodule.c.h
index e8fa6cb..7d61649 100644
--- a/Modules/clinic/cmathmodule.c.h
+++ b/Modules/clinic/cmathmodule.c.h
@@ -806,4 +806,55 @@
exit:
return return_value;
}
-/*[clinic end generated code: output=274f59792cf4f418 input=a9049054013a1b77]*/
+
+PyDoc_STRVAR(cmath_isclose__doc__,
+"isclose($module, /, a, b, *, rel_tol=1e-09, abs_tol=0.0)\n"
+"--\n"
+"\n"
+"Determine whether two complex numbers are close in value.\n"
+"\n"
+" rel_tol\n"
+" maximum difference for being considered \"close\", relative to the\n"
+" magnitude of the input values\n"
+" abs_tol\n"
+" maximum difference for being considered \"close\", regardless of the\n"
+" magnitude of the input values\n"
+"\n"
+"Return True if a is close in value to b, and False otherwise.\n"
+"\n"
+"For the values to be considered close, the difference between them must be\n"
+"smaller than at least one of the tolerances.\n"
+"\n"
+"-inf, inf and NaN behave similarly to the IEEE 754 Standard. That is, NaN is\n"
+"not close to anything, even itself. inf and -inf are only close to themselves.");
+
+#define CMATH_ISCLOSE_METHODDEF \
+ {"isclose", (PyCFunction)cmath_isclose, METH_VARARGS|METH_KEYWORDS, cmath_isclose__doc__},
+
+static int
+cmath_isclose_impl(PyModuleDef *module, Py_complex a, Py_complex b,
+ double rel_tol, double abs_tol);
+
+static PyObject *
+cmath_isclose(PyModuleDef *module, PyObject *args, PyObject *kwargs)
+{
+ PyObject *return_value = NULL;
+ static char *_keywords[] = {"a", "b", "rel_tol", "abs_tol", NULL};
+ Py_complex a;
+ Py_complex b;
+ double rel_tol = 1e-09;
+ double abs_tol = 0.0;
+ int _return_value;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "DD|$dd:isclose", _keywords,
+ &a, &b, &rel_tol, &abs_tol))
+ goto exit;
+ _return_value = cmath_isclose_impl(module, a, b, rel_tol, abs_tol);
+ if ((_return_value == -1) && PyErr_Occurred())
+ goto exit;
+ return_value = PyBool_FromLong((long)_return_value);
+
+exit:
+ return return_value;
+}
+/*[clinic end generated code: output=229e9c48c9d27362 input=a9049054013a1b77]*/
diff --git a/Modules/cmathmodule.c b/Modules/cmathmodule.c
index 921eaaa..d12e4c5 100644
--- a/Modules/cmathmodule.c
+++ b/Modules/cmathmodule.c
@@ -1114,6 +1114,73 @@
Py_IS_INFINITY(z.imag));
}
+/*[clinic input]
+cmath.isclose -> bool
+
+ a: Py_complex
+ b: Py_complex
+ *
+ rel_tol: double = 1e-09
+ maximum difference for being considered "close", relative to the
+ magnitude of the input values
+ abs_tol: double = 0.0
+ maximum difference for being considered "close", regardless of the
+ magnitude of the input values
+
+Determine whether two complex numbers are close in value.
+
+Return True if a is close in value to b, and False otherwise.
+
+For the values to be considered close, the difference between them must be
+smaller than at least one of the tolerances.
+
+-inf, inf and NaN behave similarly to the IEEE 754 Standard. That is, NaN is
+not close to anything, even itself. inf and -inf are only close to themselves.
+[clinic start generated code]*/
+
+static int
+cmath_isclose_impl(PyModuleDef *module, Py_complex a, Py_complex b,
+ double rel_tol, double abs_tol)
+/*[clinic end generated code: output=da0c535fb54e2310 input=df9636d7de1d4ac3]*/
+{
+ double diff;
+
+ /* sanity check on the inputs */
+ if (rel_tol < 0.0 || abs_tol < 0.0 ) {
+ PyErr_SetString(PyExc_ValueError,
+ "tolerances must be non-negative");
+ return -1;
+ }
+
+ if ( (a.real == b.real) && (a.imag == b.imag) ) {
+ /* short circuit exact equality -- needed to catch two infinities of
+ the same sign. And perhaps speeds things up a bit sometimes.
+ */
+ return 1;
+ }
+
+ /* This catches the case of two infinities of opposite sign, or
+ one infinity and one finite number. Two infinities of opposite
+ sign would otherwise have an infinite relative tolerance.
+ Two infinities of the same sign are caught by the equality check
+ above.
+ */
+
+ if (Py_IS_INFINITY(a.real) || Py_IS_INFINITY(a.imag) ||
+ Py_IS_INFINITY(b.real) || Py_IS_INFINITY(b.imag)) {
+ return 0;
+ }
+
+ /* now do the regular computation
+ this is essentially the "weak" test from the Boost library
+ */
+
+ diff = _Py_c_abs(_Py_c_diff(a, b));
+
+ return (((diff <= rel_tol * _Py_c_abs(b)) ||
+ (diff <= rel_tol * _Py_c_abs(a))) ||
+ (diff <= abs_tol));
+}
PyDoc_STRVAR(module_doc,
"This module is always available. It provides access to mathematical\n"
@@ -1129,6 +1196,7 @@
CMATH_COS_METHODDEF
CMATH_COSH_METHODDEF
CMATH_EXP_METHODDEF
+ CMATH_ISCLOSE_METHODDEF
CMATH_ISFINITE_METHODDEF
CMATH_ISINF_METHODDEF
CMATH_ISNAN_METHODDEF
diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c
index a65de47..9359eb2 100644
--- a/Modules/mathmodule.c
+++ b/Modules/mathmodule.c
@@ -1990,6 +1990,83 @@
"isinf(x) -> bool\n\n\
Return True if x is a positive or negative infinity, and False otherwise.");
+static PyObject *
+math_isclose(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ double a, b;
+ double rel_tol = 1e-9;
+ double abs_tol = 0.0;
+ double diff = 0.0;
+ long result = 0;
+
+ static char *keywords[] = {"a", "b", "rel_tol", "abs_tol", NULL};
+
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "dd|$dd:isclose",
+ keywords,
+ &a, &b, &rel_tol, &abs_tol
+ ))
+ return NULL;
+
+ /* sanity check on the inputs */
+ if (rel_tol < 0.0 || abs_tol < 0.0 ) {
+ PyErr_SetString(PyExc_ValueError,
+ "tolerances must be non-negative");
+ return NULL;
+ }
+
+ if ( a == b ) {
+ /* short circuit exact equality -- needed to catch two infinities of
+ the same sign. And perhaps speeds things up a bit sometimes.
+ */
+ Py_RETURN_TRUE;
+ }
+
+ /* This catches the case of two infinities of opposite sign, or
+ one infinity and one finite number. Two infinities of opposite
+ sign would otherwise have an infinite relative tolerance.
+ Two infinities of the same sign are caught by the equality check
+ above.
+ */
+
+ if (Py_IS_INFINITY(a) || Py_IS_INFINITY(b)) {
+ Py_RETURN_FALSE;
+ }
+
+ /* now do the regular computation
+ this is essentially the "weak" test from the Boost library
+ */
+
+ diff = fabs(b - a);
+
+ result = (((diff <= fabs(rel_tol * b)) ||
+ (diff <= fabs(rel_tol * a))) ||
+ (diff <= abs_tol));
+
+ return PyBool_FromLong(result);
+}
+
+PyDoc_STRVAR(math_isclose_doc,
+"is_close(a, b, *, rel_tol=1e-09, abs_tol=0.0) -> bool\n"
+"\n"
+"Determine whether two floating point numbers are close in value.\n"
+"\n"
+" rel_tol\n"
+" maximum difference for being considered \"close\", relative to the\n"
+" magnitude of the input values\n"
+" abs_tol\n"
+" maximum difference for being considered \"close\", regardless of the\n"
+" magnitude of the input values\n"
+"\n"
+"Return True if a is close in value to b, and False otherwise.\n"
+"\n"
+"For the values to be considered close, the difference between them\n"
+"must be smaller than at least one of the tolerances.\n"
+"\n"
+"-inf, inf and NaN behave similarly to the IEEE 754 Standard. That\n"
+"is, NaN is not close to anything, even itself. inf and -inf are\n"
+"only close to themselves.");
+
static PyMethodDef math_methods[] = {
{"acos", math_acos, METH_O, math_acos_doc},
{"acosh", math_acosh, METH_O, math_acosh_doc},
@@ -2016,6 +2093,8 @@
{"gamma", math_gamma, METH_O, math_gamma_doc},
{"gcd", math_gcd, METH_VARARGS, math_gcd_doc},
{"hypot", math_hypot, METH_VARARGS, math_hypot_doc},
+ {"isclose", (PyCFunction) math_isclose, METH_VARARGS | METH_KEYWORDS,
+ math_isclose_doc},
{"isfinite", math_isfinite, METH_O, math_isfinite_doc},
{"isinf", math_isinf, METH_O, math_isinf_doc},
{"isnan", math_isnan, METH_O, math_isnan_doc},