Index: PCbuild/pythoncore.vcproj
===================================================================
--- PCbuild/pythoncore.vcproj (revision 74875)
+++ PCbuild/pythoncore.vcproj (working copy)
@@ -1091,6 +1091,14 @@
>
+
+
+
+
Index: setup.py
===================================================================
--- setup.py (revision 74875)
+++ setup.py (working copy)
@@ -414,7 +414,7 @@
libraries=math_libs) )
# math library functions, e.g. sin()
- exts.append( Extension('math', ['mathmodule.c'],
+ exts.append( Extension('math', ['mathmodule.c', 'math_extra.c'],
libraries=math_libs) )
# fast string operations implemented in C
exts.append( Extension('strop', ['stropmodule.c']) )
Index: PC/VS7.1/pythoncore.vcproj
===================================================================
--- PC/VS7.1/pythoncore.vcproj (revision 74875)
+++ PC/VS7.1/pythoncore.vcproj (working copy)
@@ -659,6 +659,12 @@
RelativePath="..\..\Python\marshal.c">
+
+
+
+
+
+
+
+
Index: PC/VC6/pythoncore.dsp
===================================================================
--- PC/VC6/pythoncore.dsp (revision 74875)
+++ PC/VC6/pythoncore.dsp (working copy)
@@ -519,6 +519,10 @@
# End Source File
# Begin Source File
+SOURCE=..\..\Modules\math_extra.c
+# End Source File
+# Begin Source File
+
SOURCE=..\..\Modules\mathmodule.c
# End Source File
# Begin Source File
Index: Doc/library/math.rst
===================================================================
--- Doc/library/math.rst (revision 74875)
+++ Doc/library/math.rst (working copy)
@@ -308,6 +308,14 @@
Return the hyperbolic tangent of *x*.
+Special functions
+-----------------
+
+.. function:: gamma(x)
+
+ Return the Gamma function at *x*.
+
+
Constants
---------
Index: Lib/test/test_math.py
===================================================================
--- Lib/test/test_math.py (revision 74875)
+++ Lib/test/test_math.py (working copy)
@@ -7,6 +7,7 @@
import os
import sys
import random
+import struct
eps = 1E-05
NAN = float('nan')
@@ -24,8 +25,50 @@
else:
file = __file__
test_dir = os.path.dirname(file) or os.curdir
+math_testcases = os.path.join(test_dir, 'math_testcases.txt')
test_file = os.path.join(test_dir, 'cmath_testcases.txt')
+def to_ulps(x):
+ """Convert a non-NaN float x to an integer, in such a way that
+ adjacent floats are converted to adjacent integers. Then
+ abs(ulps(x) - ulps(y)) gives the difference in ulps between two
+ floats.
+
+ The results from this function will only make sense on platforms
+ where C doubles are represented in IEEE 754 binary64 format.
+
+ """
+ n = struct.unpack('q', struct.pack(' expected [flag]*
+
+ """
+ with open(fname) as fp:
+ for line in fp:
+ # strip comments, and skip blank lines
+ if '--' in line:
+ line = line[:line.index('--')]
+ if not line.strip():
+ continue
+
+ lhs, rhs = line.split('->')
+ id, fn, arg = lhs.split()
+ rhs_pieces = rhs.split()
+ exp = rhs_pieces[0]
+ flags = rhs_pieces[1:]
+
+ yield (id, fn, float(arg), float(exp), flags)
+
def parse_testfile(fname):
"""Parse a file with test values
@@ -884,6 +927,51 @@
self.fail(message)
self.ftest("%s:%s(%r)" % (id, fn, ar), result, er)
+ @unittest.skipUnless(float.__getformat__("double").startswith("IEEE"),
+ "test requires IEEE 754 doubles")
+ def test_mtestfile(self):
+ ALLOWED_ERROR = 100 # permitted error, in ulps
+ fail_fmt = "{}:{}({!r}): expected {!r}, got {!r}"
+
+ failures = []
+ for id, fn, arg, expected, flags in parse_mtestfile(math_testcases):
+ func = getattr(math, fn)
+
+ if 'invalid' in flags or 'divide-by-zero' in flags:
+ expected = 'ValueError'
+ elif 'overflow' in flags:
+ expected = 'OverflowError'
+
+ try:
+ got = func(arg)
+ except ValueError:
+ got = 'ValueError'
+ except OverflowError:
+ got = 'OverflowError'
+
+ diff_ulps = None
+ if isinstance(got, float) and isinstance(expected, float):
+ if math.isnan(expected) and math.isnan(got):
+ continue
+ if not math.isnan(expected) and not math.isnan(got):
+ diff_ulps = to_ulps(expected) - to_ulps(got)
+ if diff_ulps <= ALLOWED_ERROR:
+ continue
+
+ if isinstance(got, str) and isinstance(expected, str):
+ if got == expected:
+ continue
+
+ fail_msg = fail_fmt.format(id, fn, arg, expected, got)
+ if diff_ulps is not None:
+ fail_msg += ' ({} ulps)'.format(diff_ulps)
+ failures.append(fail_msg)
+
+ if failures:
+ self.fail('Failures in test_mtestfile:\n ' +
+ '\n '.join(failures))
+
+
def test_main():
from doctest import DocFileSuite
suite = unittest.TestSuite()
Index: Lib/test/math_testcases.txt
===================================================================
--- Lib/test/math_testcases.txt (revision 0)
+++ Lib/test/math_testcases.txt (revision 0)
@@ -0,0 +1,136 @@
+-- Testcases for functions in math.
+--
+-- Each line takes the form:
+--
+-- ->
+--
+-- where:
+--
+-- is a short name identifying the test,
+--
+-- is the function to be tested (exp, cos, asinh, ...),
+--
+-- is a string representing a floating-point value
+--
+-- is the expected (ideal) output value, again
+-- represented as a string.
+--
+-- is a list of the floating-point flags required by C99
+--
+-- The possible flags are:
+--
+-- divide-by-zero : raised when a finite input gives a
+-- mathematically infinite result.
+--
+-- overflow : raised when a finite input gives a finite result that
+-- is too large to fit in the usual range of an IEEE 754 double.
+--
+-- invalid : raised for invalid inputs (e.g., sqrt(-1))
+--
+-- ignore-sign : indicates that the sign of the result is
+-- unspecified; e.g., if the result is given as inf,
+-- then both -inf and inf should be accepted as correct.
+--
+-- Flags may appear in any order.
+--
+-- Lines beginning with '--' (like this one) start a comment, and are
+-- ignored. Blank lines, or lines containing only whitespace, are also
+-- ignored.
+
+-- Many of the values below were computed with the help of
+-- version 2.4 of the MPFR library for multiple-precision
+-- floating-point computations with correct rounding. All output
+-- values in this file are (modulo yet-to-be-discovered bugs)
+-- correctly rounded, provided that each input and output decimal
+-- floating-point value below is interpreted as a representation of
+-- the corresponding nearest IEEE 754 double-precision value. See the
+-- MPFR homepage at http://www.mpfr.org for more information about the
+-- MPFR project.
+
+---------------------------
+-- gamma: Gamma function --
+---------------------------
+
+-- special values
+gam0000 gamma 0.0 -> inf divide-by-zero
+gam0001 gamma -0.0 -> -inf divide-by-zero
+gam0002 gamma inf -> inf
+gam0003 gamma -inf -> nan invalid
+gam0004 gamma nan -> nan
+
+-- negative integers inputs are invalid
+gam0010 gamma -1 -> nan invalid
+gam0011 gamma -2 -> nan invalid
+gam0012 gamma -1e16 -> nan invalid
+gam0013 gamma -1e300 -> nan invalid
+
+-- small positive integers give factorials
+gam0020 gamma 1 -> 1
+gam0021 gamma 2 -> 1
+gam0022 gamma 3 -> 2
+gam0023 gamma 4 -> 6
+gam0024 gamma 5 -> 24
+gam0025 gamma 6 -> 120
+
+-- half integers
+gam0030 gamma 0.5 -> 1.7724538509055161
+gam0031 gamma 1.5 -> 0.88622692545275805
+gam0032 gamma 2.5 -> 1.3293403881791370
+gam0033 gamma 3.5 -> 3.3233509704478426
+gam0034 gamma -0.5 -> -3.5449077018110322
+gam0035 gamma -1.5 -> 2.3632718012073548
+gam0036 gamma -2.5 -> -0.94530872048294190
+gam0037 gamma -3.5 -> 0.27008820585226911
+
+-- values near 0
+gam0040 gamma 0.1 -> 9.5135076986687306
+gam0041 gamma 0.01 -> 99.432585119150602
+gam0042 gamma 1e-8 -> 99999999.422784343
+gam0043 gamma 1e-16 -> 10000000000000000
+gam0044 gamma 1e-30 -> 9.9999999999999988e+29
+gam0045 gamma 1e-160 -> 1.0000000000000000e+160
+gam0046 gamma 1e-308 -> 1.0000000000000000e+308
+gam0047 gamma 5.6e-309 -> 1.7857142857142848e+308
+gam0048 gamma 5.5e-309 -> inf overflow
+gam0049 gamma 1e-309 -> inf overflow
+gam0050 gamma 1e-323 -> inf overflow
+gam0051 gamma 5e-324 -> inf overflow
+gam0060 gamma -0.1 -> -10.686287021193193
+gam0061 gamma -0.01 -> -100.58719796441078
+gam0062 gamma -1e-8 -> -100000000.57721567
+gam0063 gamma -1e-16 -> -10000000000000000
+gam0064 gamma -1e-30 -> -9.9999999999999988e+29
+gam0065 gamma -1e-160 -> -1.0000000000000000e+160
+gam0066 gamma -1e-308 -> -1.0000000000000000e+308
+gam0067 gamma -5.6e-309 -> -1.7857142857142848e+308
+gam0068 gamma -5.5e-309 -> -inf overflow
+gam0069 gamma -1e-309 -> -inf overflow
+gam0070 gamma -1e-323 -> -inf overflow
+gam0071 gamma -5e-324 -> -inf overflow
+
+-- values near negative integers
+gam0080 gamma -0.99999999999999989 -> -9007199254740992.0
+gam0081 gamma -1.0000000000000002 -> 4503599627370495.5
+gam0082 gamma -1.9999999999999998 -> 2251799813685248.5
+gam0083 gamma -2.0000000000000004 -> -1125899906842623.5
+gam0084 gamma -100.00000000000001 -> -7.5400833348831090e-145
+gam0085 gamma -99.999999999999986 -> 7.5400833348840962e-145
+
+-- large inputs
+gam0100 gamma 170 -> 4.2690680090047051e+304
+gam0101 gamma 171 -> 7.2574156153079990e+306
+gam0102 gamma 171.624 -> 1.7942117599248104e+308
+gam0103 gamma 171.625 -> inf overflow
+gam0104 gamma 172 -> inf overflow
+gam0105 gamma 2000 -> inf overflow
+gam0106 gamma 1.7e308 -> inf overflow
+
+-- inputs for which gamma(x) is tiny
+gam0120 gamma -100.5 -> -3.3536908198076787e-159
+gam0121 gamma -160.5 -> -5.2555464470078293e-286
+gam0122 gamma -170.5 -> -3.3127395215386074e-308
+gam0123 gamma -171.5 -> 1.9316265431711902e-310
+gam0124 gamma -176.5 -> -1.1956388629358166e-321
+gam0125 gamma -177.5 -> 4.9406564584124654e-324
+gam0126 gamma -178.5 -> -0.0
+
Index: Modules/math_extra.h
===================================================================
--- Modules/math_extra.h (revision 0)
+++ Modules/math_extra.h (revision 0)
@@ -0,0 +1 @@
+double py_tgamma(double);
Index: Modules/mathmodule.c
===================================================================
--- Modules/mathmodule.c (revision 74875)
+++ Modules/mathmodule.c (working copy)
@@ -54,6 +54,7 @@
#include "Python.h"
#include "longintrepr.h" /* just for SHIFT */
+#include "math_extra.h"
#ifdef _OSF_SOURCE
/* OSF1 5.1 doesn't make this available with XOPEN_SOURCE_EXTENDED defined */
@@ -247,6 +248,26 @@
return PyFloat_FromDouble(r);
}
+/* variant of math_1, to be used when the function being wrapped is known
+ to set errno properly. In particular, math_1 should be used to wrap
+ functions defined in the Modules/math_extra.c file. */
+
+static PyObject *
+math_1a(PyObject *arg, double (*func) (double))
+{
+ double x, r;
+ x = PyFloat_AsDouble(arg);
+ if (x == -1.0 && PyErr_Occurred())
+ return NULL;
+ errno = 0;
+ PyFPE_START_PROTECT("in math_1a", return 0);
+ r = (*func)(x);
+ PyFPE_END_PROTECT(r);
+ if (errno && is_error(r))
+ return NULL;
+ return PyFloat_FromDouble(r);
+}
+
/*
math_2 is used to wrap a libm function f that takes two double
arguments and returns a double.
@@ -313,6 +334,12 @@
}\
PyDoc_STRVAR(math_##funcname##_doc, docstring);
+#define FUNC1A(funcname, func, docstring) \
+ static PyObject * math_##funcname(PyObject *self, PyObject *args) { \
+ return math_1a(args, func); \
+ }\
+ PyDoc_STRVAR(math_##funcname##_doc, docstring);
+
#define FUNC2(funcname, func, docstring) \
static PyObject * math_##funcname(PyObject *self, PyObject *args) { \
return math_2(args, func, #funcname); \
@@ -350,6 +377,8 @@
FUNC1(floor, floor, 0,
"floor(x)\n\nReturn the floor of x as a float.\n"
"This is the largest integral value <= x.")
+FUNC1A(gamma, py_tgamma,
+ "gamma(x)\n\nGamma function at x.")
FUNC1(log1p, log1p, 1,
"log1p(x)\n\nReturn the natural logarithm of 1+x (base e).\n\
The result is computed in a way which is accurate for x near zero.")
@@ -1077,6 +1106,7 @@
{"fmod", math_fmod, METH_VARARGS, math_fmod_doc},
{"frexp", math_frexp, METH_O, math_frexp_doc},
{"fsum", math_fsum, METH_O, math_fsum_doc},
+ {"gamma", math_gamma, METH_O, math_gamma_doc},
{"hypot", math_hypot, METH_VARARGS, math_hypot_doc},
{"isinf", math_isinf, METH_O, math_isinf_doc},
{"isnan", math_isnan, METH_O, math_isnan_doc},
Index: Modules/Setup.dist
===================================================================
--- Modules/Setup.dist (revision 74875)
+++ Modules/Setup.dist (working copy)
@@ -169,7 +169,7 @@
#array arraymodule.c # array objects
#cmath cmathmodule.c # -lm # complex math library functions
-#math mathmodule.c # -lm # math library functions, e.g. sin()
+#math mathmodule.c math_extra.c # -lm # math library functions, e.g. sin()
#_struct _struct.c # binary structure packing/unpacking
#time timemodule.c # -lm # time operations and variables
#operator operator.c # operator.add() and similar goodies
Index: Modules/math_extra.c
===================================================================
--- Modules/math_extra.c (revision 0)
+++ Modules/math_extra.c (revision 0)
@@ -0,0 +1,169 @@
+/* This file contains substitutes for various C99 math functions, for
+ those platforms whose math library doesn't define these functions. */
+
+/* Note on exceptional cases: ideally, the functions defined in this file
+ should behave as follows (on IEEE 754 machines)
+
+ - where C99 Annex G recommends signalling divide-by-zero,
+ return +-infinity and set errno = EDOM
+ - where C99 Annex G recommends signalling invalid,
+ return NaN and set errno = EDOM
+ - where C99 Annex G recommends signalling overflow,
+ return +-infinity and set errno = ERANGE
+ - in all other cases, don't set errno
+
+ In general, Python needs both the return value *and*
+ the errno value to determine what to do.
+
+ */
+
+#include "Python.h"
+
+/* sin(pi*x). Gives accurate values for all x, including x close to
+ an integer. Should also satisfy: sinpi(+-0.0) = +-0.0,
+ sinpi(n) = 0.0 and sinpi(-n) = -0.0 for positive integers n.
+ And sinpi(inf) = sinpi(-inf) = nan.
+ */
+
+static const double pi = 3.1415926535897931;
+
+static double
+sinpi(double x)
+{
+ double y, r;
+ int n;
+ y = fmod(fabs(x), 2.0);
+ n = round(2.0*y);
+ assert(0 <= n && n <= 4);
+ switch (n) {
+ case 0:
+ r = sin(pi*y);
+ break;
+ case 1:
+ r = cos(pi*(y-0.5));
+ break;
+ case 2:
+ /* N.B. y = 1.0 should produce r = 0.0, not -0.0 */
+ r = sin(pi*(1.0-y));
+ break;
+ case 3:
+ r = -cos(pi*(y-1.5));
+ break;
+ case 4:
+ r = sin(pi*(y-2.0));
+ break;
+ default:
+ /* should never get here */
+ return -1.23e200;
+ }
+ return copysign(1.0, x)*r;
+}
+
+/* For the gamma function we use the Lanczos approximation, with
+ parameters used by the Boost implementation. These parameters are
+ tailored for the most common double format (IEEE 754 binary64),
+ but should give valid results for any format. */
+
+#define LANCZOS_NTERMS 13
+static const double lanczos_g = 6.024680040776729583740234375;
+static const double lanczos_numerators[LANCZOS_NTERMS] = {
+ 23531376880.41075968857200767445163675473,
+ 42919803642.64909876895789904700198885093,
+ 35711959237.35566804944018545154716670596,
+ 17921034426.03720969991975575445893111267,
+ 6039542586.35202800506429164430729792107,
+ 1439720407.311721673663223072794912393972,
+ 248874557.8620541565114603864132294232163,
+ 31426415.58540019438061423162831820536287,
+ 2876370.628935372441225409051620849613599,
+ 186056.2653952234950402949897160456992822,
+ 8071.672002365816210638002902272250613822,
+ 210.8242777515793458725097339207133627117,
+ 2.506628274631000270164908177133837338626,
+};
+static const double lanczos_denominators[LANCZOS_NTERMS] = {
+ 0,
+ 39916800,
+ 120543840,
+ 150917976,
+ 105258076,
+ 45995730,
+ 13339535,
+ 2637558,
+ 357423,
+ 32670,
+ 1925,
+ 66,
+ 1
+};
+
+static double py_tgamma_inner(double x) {
+ /* get numerator */
+ double num = 0.0, den = 0.0;
+ int i;
+ /* numerator and denominator of lanczos polynomial */
+ for (i = LANCZOS_NTERMS; --i >= 0; ) {
+ num = num * x + lanczos_numerators[i];
+ den = den * x + lanczos_denominators[i];
+ }
+ if (x < 140.)
+ return num / den / exp(x + lanczos_g - 0.5) *
+ pow(x+lanczos_g-0.5, x-0.5);
+ else if (x < 180.) {
+ /* for large x, pow(x+lanczos_g-0.5, x-0.5 can overflow even
+ though the result does not */
+ double sqrtpow = pow(x+lanczos_g-0.5, x/2-0.25);
+ return num / den / exp(x + lanczos_g - 0.5) * sqrtpow * sqrtpow;
+ }
+ else {
+ return Py_HUGE_VAL;
+ }
+
+}
+
+double py_tgamma(double x) {
+ /* deal with special cases, then delegate to the libm version */
+ double r;
+ if (!Py_IS_FINITE(x)) {
+ if (Py_IS_NAN(x) || x > 0.0) /* tgamma(nan) = nan, tgamma(inf) = inf */
+ return x;
+ else {
+ errno = EDOM;
+ return Py_NAN; /* tgamma(-inf) = nan, invalid */
+ }
+ }
+ if (x < 0.5) {
+ if (x == floor(x)) {
+ /* integer argument: result is either an infinity or a nan */
+ if (x == 0.0) {
+ errno = EDOM;
+ return 1.0/x;
+ }
+ else {
+ errno = EDOM;
+ return Py_NAN;
+ }
+ }
+ else {
+ /* use reflection formula */
+ if (x > -170.)
+ r = pi/sinpi(x)/py_tgamma_inner(1-x);
+ else
+ /* For x <= -170, use the duplication formula to compute
+ gamma(1-x); for IEEE 754 doubles, this avoids problems
+ where gamma(1-x) overflows but gamma(x) doesn't underflow
+ to zero. */
+ r = pow(pi, 1.5)/sinpi(x)/py_tgamma_inner((1-x)/2.)/
+ py_tgamma_inner((2.-x)/2.)/pow(2., -x);
+ }
+ }
+ else {
+ r = py_tgamma_inner(x);
+ }
+
+ /* make sure to set errno on overflow */
+ if (Py_IS_INFINITY(r))
+ errno = ERANGE;
+ return r;
+}
+