8000 add math.fmax and math.fmin · python/cpython@a82d298 · GitHub
[go: up one dir, main page]

Skip to content

Commit a82d298

Browse files
committed
add math.fmax and math.fmin
1 parent ef4fc86 commit a82d298

File tree

6 files changed

+285
-4
lines changed

6 files changed

+285
-4
lines changed

Doc/library/math.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ noted otherwise, all return values are floats.
4242
:func:`fabs(x) <fabs>` Absolute value of *x*
4343
:func:`floor(x) <floor>` Floor of *x*, the largest integer less than or equal to *x*
4444
:func:`fma(x, y, z) <fma>` Fused multiply-add operation: ``(x * y) + z``
45+
:func:`fmax(x, y) <fmax>` Maximum of two floating-point values
46+
:func:`fmin(x, y) <fmin>` Minimum of two floating-point values
4547
:func:`fmod(x, y) <fmod>` Remainder of division ``x / y``
4648
:func:`modf(x) <modf>` Fractional and integer parts of *x*
4749
:func:`remainder(x, y) <remainder>` Remainder of *x* with respect to *y*
@@ -247,6 +249,26 @@ Floating point arithmetic
247249
.. versionadded:: 3.13
248250

249251

252+
.. function:: fmax(x, y, /)
2 8000 53+
254+
Get the larger of two floating-point values, treating NaNs as missing data.
255+
256+
If *x* and *y* are NaNs of same sign *s*, return ``copysign(nan, s)``.
257+
If *x* and *y* are NaNs of different sign, return ``copysign(nan, 1)``.
258+
259+
.. versionadded:: next
260+
261+
262+
.. function:: fmin(x, y, /)
263+
264+
Get the smaller of two floating-point values, treating NaNs as missing data.
265+
266+
If *x* and *y* are NaNs of same sign *s*, return ``copysign(nan, s)``.
267+
If *x* and *y* are NaNs of different sign, return ``copysign(nan, -1)``.
268+
269+
.. versionadded:: next
270+
271+
250272
.. function:: fmod(x, y)
251273

252274
Return the floating-point remainder of ``x / y``,

Doc/whatsnew/3.15.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ math
115115
* Add :func:`math.isnormal` and :func:`math.issubnormal` functions.
116116
(Contributed by Sergey B Kirpichev in :gh:`132908`.)
117117

118+
* Add :func:`math.fmax` and :func:`math.fmin` functions.
119+
(Contributed by Bénédikt Tran in :gh:`135853`.)
120+
118121

119122
os.path
120123
-------

Lib/test/test_math.py

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
eps = 1E-05
1919
NAN = float('nan')
20+
NNAN = float('-nan')
2021
INF = float('inf')
2122
NINF = float('-inf')
2223
FLOAT_MAX = sys.float_info.max
@@ -37,6 +38,11 @@
3738
test_file = os.path.join(test_dir, 'mathdata', 'cmath_testcases.txt')
3839

3940

41+
def is_signed_nan(x, x0):
42+
"""Check if x is a NaN with the same sign as x0."""
43+
return math.isnan(x) and math.copysign(1, x) == math.copysign(1, x0)
44+
45+
4046
def to_ulps(x):
4147
"""Convert a non-NaN float x to an integer, in such a way that
4248
adjacent floats are converted to adjacent integers. Then
@@ -253,9 +259,10 @@ def ftest(self, name, got, expected, ulp_tol=5, abs_tol=0.0):
253259
non-finite floats, exact equality is demanded. Also, nan==nan
254260
in this function.
255261
"""
256-
failure = result_check(expected, got, ulp_tol, abs_tol)
257-
if failure is not None:
258-
self.fail("{}: {}".format(name, failure))
262+
with self.subTest(name):
263+
failure = result_check(expected, got, ulp_tol, abs_tol)
264+
if failure is not None:
265+
self.fail(failure)
259266

260267
def testConstants(self):
261268
# Ref: Abramowitz & Stegun (Dover, 1965)
@@ -623,6 +630,101 @@ def testFmod(self):
623630
self.assertEqual(math.fmod(0.0, NINF), 0.0)
624631
self.assertRaises(ValueError, math.fmod, INF, INF)
625632

633+
def test_fmax(self):
634+
self.assertRaises(TypeError, math.fmax)
635+
self.assertRaises(TypeError, math.fmax, 'x', 'y')
636+
637+
self.assertEqual(math.fmax(0., 0.), 0.)
638+
self.assertEqual(math.fmax(0., -0.), 0.)
639+
self.assertEqual(math.fmax(-0., 0.), 0.)
640+
641+
self.assertEqual(math.fmax(1., 0.), 1.)
642+
self.assertEqual(math.fmax(0., 1.), 1.)
643+
self.assertEqual(math.fmax(1., -0.), 1.)
644+
self.assertEqual(math.fmax(-0., 1.), 1.)
645+
646+
self.assertEqual(math.fmax(-1., 0.), 0.)
647+
self.assertEqual(math.fmax(0., -1.), 0.)
648+
self.assertEqual(math.fmax(-1., -0.), -0.)
649+
self.assertEqual(math.fmax(-0., -1.), -0.)
650+
651+
for x in [NINF, -1., -0., 0., 1., INF]:
652+
self.assertFalse(math.isnan(x))
653+
654+
with self.subTest("math.fmax(INF, x)", x=x):
655+
self.assertEqual(math.fmax(INF, x), INF)
656+
with self.subTest("math.fmax(x, INF)", x=x):
657+
self.assertEqual(math.fmax(x, INF), INF)
658+
659+
with self.subTest("math.fmax(NINF, x)", x=x):
660+
self.assertEqual(math.fmax(NINF, x), x)
661+
with self.subTest("math.fmax(x, NINF)", x=x):
662+
self.assertEqual(math.fmax(x, NINF), x)
663+
664+
@requires_IEEE_754
665+
def test_fmax_nans(self):
666+
# When exactly one operand is NaN, the other is returned.
667+
for x in [NINF, -1., -0., 0., 1., INF]:
668+
with self.subTest(x=x):
669+
self.assertFalse(math.isnan(math.fmax(NAN, x)))
670+
self.assertFalse(math.isnan(math.fmax(x, NAN)))
671+
self.assertFalse(math.isnan(math.fmax(NNAN, x)))
672+
self.assertFalse(math.isnan(math.fmax(x, NNAN)))
673+
# When operands are NaNs with identical sign, return this signed NaN.
674+
self.assertTrue(is_signed_nan(math.fmax(NAN, NAN), 1))
675+
self.assertTrue(is_signed_nan(math.fmax(NNAN, NNAN), -1))
676+
# When operands are NaNs of different signs, return the positive NaN.
677+
self.assertTrue(is_signed_nan(math.fmax(NAN, NNAN), 1))
678+
self.assertTrue(is_signed_nan(math.fmax(NNAN, NAN), 1))
679+
680+
def test_fmin(self):
681+
self.assertRaises(TypeError, math.fmin)
682+
self.assertRaises(TypeError, math.fmin, 'x', 'y')
683+
684+
self.assertEqual(math.fmin(0., 0.), 0.)
685+
self.assertEqual(math.fmin(0., -0.), -0.)
686+
self.assertEqual(math.fmin(-0., 0.), -0.)
687+
688+
self.assertEqual(math.fmin(1., 0.), 0.)
689+
self.assertEqual(math.fmin(0., 1.), 0.)
690+
self.assertEqual(math.fmin(1., -0.), -0.)
691+
self.assertEqual(math.fmin(-0., 1.), -0.)
692+
693+
self.assertEqual(math.fmin(-1., 0.), -1.)
694+
self.assertEqual(math.fmin(0., -1.), -1.)
695+
self.assertEqual(math.fmin(-1., -0.), -1.)
696+
self.assertEqual(math.fmin(-0., -1.), -1.)
697+
698+
for x in [NINF, -1., -0., 0., 1., INF]:
699+
self.assertFalse(math.isnan(x))
700+
701+
with self.subTest("math.fmin(INF, x)", x=x):
702+
self.assertEqual(math.fmin(INF, x), x)
703+
with self.subTest("math.fmin(x, INF)", x=x):
704+
self.assertEqual(math.fmin(x, INF), x)
705+
706+
with self.subTest("math.fmin(NINF, x)", x=x):
707+
self.assertEqual(math.fmin(NINF, x), NINF)
708+
with self.subTest("math.fmin(x, NINF)", x=x):
709+
self.assertEqual(math.fmin(x, NINF), NINF)
710+
711+
@requires_IEEE_754
712+
def test_fmin_nans(self):
713+
# When exactly one operand is NaN, the other is returned.
714+
for x in [NINF, -1., -0., 0., 1., INF]:
715+
with self.subTest(x=x):
716+
self.assertFalse(math.isnan(x))
717+
self.assertFalse(math.isnan(math.fmin(NAN, x)))
718+
self.assertFalse(math.isnan(math.fmin(x, NAN)))
719+
self.assertFalse(math.isnan(math.fmin(NNAN, x)))
720+
self.assertFalse(math.isnan(math.fmin(x, NNAN)))
721+
# When operands are NaNs with identical sign, return this signed NaN.
722+
self.assertTrue(is_signed_nan(math.fmin(NAN, NAN), 1))
723+
self.assertTrue(is_signed_nan(math.fmin(NNAN, NNAN), -1))
724+
# When operands are NaNs of different signs, return the negative NaN.
725+
self.assertTrue(is_signed_nan(math.fmin(NAN, NNAN), -1))
726+
self.assertTrue(is_signed_nan(math.fmin(NNAN, NAN), -1))
727+
626728
def testFrexp(self):
627729
self.assertRaises(TypeError, math.frexp)
628730

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :func:`math.fmax` and :math:`math.fmin` to get the larger and smaller of
2+
two floating-point values. Patch by Bénédikt Tran.

Modules/clinic/mathmodule.c.h

Lines changed: 107 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/mathmodule.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,6 +1214,50 @@ math_floor(PyObject *module, PyObject *number)
12141214
return PyLong_FromDouble(floor(x));
12151215
}
12161216

1217+
/*[clinic input]
1218+
math.fmax -> double
1219+
1220+
x: double
1221+
y: double
1222+
/
1223+
1224+
Returns the larger of two floating-point arguments.
1225+
1226+
[clinic start generated code]*/
1227+
1228+
static double
1229+
math_fmax_impl(PyObject *module, double x, double y)
1230+
/*[clinic end generated code: output=00692358d312fee2 input=0dcf618bb27f98c7]*/
1231+
{
1232+
if (isnan(x) && isnan(y)) {
1233+
double s = copysign(1, x);
1234+
return s == copysign(1, y) ? copysign(NAN, s) : NAN;
1235+
}
1236+< 10ACD /span>
return fmax(x, y);
1237+
}
1238+
1239+
/*[clinic input]
1240+
math.fmin -> double
1241+
1242+
x: double
1243+
y: double
1244+
/
1245+
1246+
Returns the smaller of two floating-point arguments.
1247+
[clinic start generated code]*/
1248+
1249+
static double
1250+
math_fmin_impl(PyObject *module, double x, double y)
1251+
/*[clinic end generated code: output=3d5b7826bd292dd9 input=f7b5c91de01d766f]*/
1252+
{
1253+
if (isnan(x) && isnan(y)) {
1254+
double s = copysign(1, x);
1255+
// return ±NAN if both are ±NAN and -NAN otherwise.
1256+
return copysign(NAN, s == copysign(1, y) ? s : -1);
1257+
}
1258+
return fmin(x, y);
1259+
}
1260+
12171261
FUNC1AD(gamma, m_tgamma,
12181262
"gamma($module, x, /)\n--\n\n"
12191263
"Gamma function at x.",
@@ -4175,7 +4219,9 @@ static PyMethodDef math_methods[] = {
41754219
MATH_FACTORIAL_METHODDEF
41764220
MATH_FLOOR_METHODDEF
41774221
MATH_FMA_METHODDEF
4222+
MATH_FMAX_METHODDEF
41784223
MATH_FMOD_METHODDEF
4224+
MATH_FMIN_METHODDEF
41794225
MATH_FREXP_METHODDEF
41804226
MATH_FSUM_METHODDEF
41814227
{"gamma", math_gamma, METH_O, math_gamma_doc},

0 commit comments

Comments
 (0)
0