From ecbdd2e9b0a5af20a2b8784ac91338739b99ce3d Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Mon, 9 Jun 2008 06:54:45 +0000 Subject: [PATCH] Issue #2138: Add math.factorial(). --- Doc/library/math.rst | 4 ++++ Lib/test/test_math.py | 15 +++++++++++++ Misc/NEWS | 2 ++ Modules/mathmodule.c | 50 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+) diff --git a/Doc/library/math.rst b/Doc/library/math.rst index b98f1649a32..d6e0205398a 100644 --- a/Doc/library/math.rst +++ b/Doc/library/math.rst @@ -42,6 +42,10 @@ Number-theoretic and representation functions: Return the absolute value of *x*. +.. function:: factorial(x) + + Return *x* factorial. Raises :exc:`ValueError` if *x* is not intergral or + is negative. .. function:: floor(x) diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index 426ca7b6d59..3123954c922 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -6,6 +6,7 @@ import unittest import math import os import sys +import random eps = 1E-05 NAN = float('nan') @@ -277,6 +278,20 @@ class MathTests(unittest.TestCase): self.ftest('fabs(0)', math.fabs(0), 0) self.ftest('fabs(1)', math.fabs(1), 1) + def testFactorial(self): + def fact(n): + result = 1 + for i in range(1, int(n)+1): + result *= i + return result + values = range(10) + [50, 100, 500] + random.shuffle(values) + for x in range(10): + for cast in (int, long, float): + self.assertEqual(math.factorial(cast(x)), fact(x), (x, fact(x), math.factorial(x))) + self.assertRaises(ValueError, math.factorial, -1) + self.assertRaises(ValueError, math.factorial, math.pi) + def testFloor(self): self.assertRaises(TypeError, math.floor) # These types will be int in py3k. diff --git a/Misc/NEWS b/Misc/NEWS index b5afbccc591..4337e5daff9 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -40,6 +40,8 @@ Core and Builtins Extension Modules ----------------- +- Issue #2138: Add factorial() the math module. + - The heapq module does comparisons using LT instead of LE. This makes its implementation match that used by list.sort(). diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 5520ca96379..7ace9972c38 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -512,6 +512,55 @@ PyDoc_STRVAR(math_sum_doc, Return an accurate floating point sum of values in the iterable.\n\ Assumes IEEE-754 floating point arithmetic."); + +static PyObject * +math_factorial(PyObject *self, PyObject *arg) +{ + long i, x; + PyObject *result, *iobj, *newresult; + + if (PyFloat_Check(arg)) { + double dx = PyFloat_AS_DOUBLE((PyFloatObject *)arg); + if (dx != floor(dx)) { + PyErr_SetString(PyExc_ValueError, + "factorial() only accepts integral values"); + return NULL; + } + } + + x = PyInt_AsLong(arg); + if (x == -1 && PyErr_Occurred()) + return NULL; + if (x < 0) { + PyErr_SetString(PyExc_ValueError, + "factorial() not defined for negative values"); + return NULL; + } + + result = (PyObject *)PyInt_FromLong(1); + if (result == NULL) + return NULL; + for (i=1 ; i<=x ; i++) { + iobj = (PyObject *)PyInt_FromLong(i); + if (iobj == NULL) + goto error; + newresult = PyNumber_Multiply(result, iobj); + Py_DECREF(iobj); + if (newresult == NULL) + goto error; + Py_DECREF(result); + result = newresult; + } + return result; + +error: + Py_DECREF(result); + Py_XDECREF(iobj); + return NULL; +} + +PyDoc_STRVAR(math_factorial_doc, "Return n!"); + static PyObject * math_trunc(PyObject *self, PyObject *number) { @@ -949,6 +998,7 @@ static PyMethodDef math_methods[] = { {"degrees", math_degrees, METH_O, math_degrees_doc}, {"exp", math_exp, METH_O, math_exp_doc}, {"fabs", math_fabs, METH_O, math_fabs_doc}, + {"factorial", math_factorial, METH_O, math_factorial_doc}, {"floor", math_floor, METH_O, math_floor_doc}, {"fmod", math_fmod, METH_VARARGS, math_fmod_doc}, {"frexp", math_frexp, METH_O, math_frexp_doc},