From 32f453eaa476c78376cd721d29ba8ab726e400bb Mon Sep 17 00:00:00 2001
From: Tim Peters <tim.peters@gmail.com>
Date: Mon, 3 Sep 2001 08:35:41 +0000
Subject: New restriction on pow(x, y, z):  If z is not None, x and y must be
 of integer types, and y must be >= 0.  See discussion at
 http://sf.net/tracker/index.php?func=detail&aid=457066&group_id=5470&atid=105470

---
 Doc/lib/libfuncs.tex     | 18 ++++++++++++------
 Lib/test/output/test_pow |  4 ----
 Lib/test/test_b2.py      | 27 +++++++++++++++++++--------
 Lib/test/test_long.py    | 17 +++++++++++++----
 Lib/test/test_pow.py     | 18 +++++++++---------
 Misc/NEWS                |  6 ++++++
 Objects/floatobject.c    | 22 +++++++---------------
 Objects/intobject.c      |  5 +++++
 Objects/longobject.c     | 11 ++++++++---
 9 files changed, 79 insertions(+), 49 deletions(-)

diff --git a/Doc/lib/libfuncs.tex b/Doc/lib/libfuncs.tex
index 53970ee..d74abf6 100644
--- a/Doc/lib/libfuncs.tex
+++ b/Doc/lib/libfuncs.tex
@@ -118,7 +118,7 @@ class instances are callable if they have a \method{__call__()} method.
   operations.
 \end{funcdesc}
 
-\begin{funcdesc}{compile}{string, filename, kind\optional{, 
+\begin{funcdesc}{compile}{string, filename, kind\optional{,
                           flags\optional{, dont_inherit}}}
   Compile the \var{string} into a code object.  Code objects can be
   executed by an \keyword{exec} statement or evaluated by a call to
@@ -408,7 +408,7 @@ raised.
 \begin{funcdesc}{locals}{}
 Return a dictionary representing the current local symbol table.
 \strong{Warning:} The contents of this dictionary should not be
-modified; changes may not affect the values of local variables used by 
+modified; changes may not affect the values of local variables used by
 the interpreter.
 \end{funcdesc}
 
@@ -478,7 +478,7 @@ the interpreter.
   ignored).  If the file cannot be opened, \exception{IOError} is
   raised.
 
-  If \var{mode} is omitted, it defaults to \code{'r'}.  When opening a 
+  If \var{mode} is omitted, it defaults to \code{'r'}.  When opening a
   binary file, you should append \code{'b'} to the \var{mode} value
   for improved portability.  (It's useful even on systems which don't
   treat binary and text files differently, where it serves as
@@ -519,8 +519,14 @@ the interpreter.
   case, all arguments are converted to float and a float result is
   delivered.  For example, \code{10**2} returns \code{100}, but
   \code{10**-2} returns \code{0.01}.  (This last feature was added in
-  Python 2.2.  In Python 2.1 and before, a negative second argument
-  would raise an exception.)
+  Python 2.2.  In Python 2.1 and before, if both arguments were of integer
+  types and the second argument was negative, an exception was raised.)
+  If the second argument is negative, the third argument must be omitted. 
+  If \var{z} is present, \var{x} and \var{y} must be of integer types,
+  and \var{y} must be non-negative.  (This restriction was added in
+  Python 2.2.  In Python 2.1 and before, floating 3-argument \code{pow()}
+  returned platform-dependent results depending on floating-point
+  rounding accidents.)
 \end{funcdesc}
 
 \begin{funcdesc}{range}{\optional{start,} stop\optional{, step}}
@@ -731,7 +737,7 @@ returns a dictionary corresponding to the object's symbol table.
 The returned dictionary should not be modified: the effects on the
 corresponding symbol table are undefined.\footnote{
   In the current implementation, local variable bindings cannot
-  normally be affected this way, but variables retrieved from 
+  normally be affected this way, but variables retrieved from
   other scopes (such as modules) can be.  This may change.}
 \end{funcdesc}
 
diff --git a/Lib/test/output/test_pow b/Lib/test/output/test_pow
index e236862..9649936 100644
--- a/Lib/test/output/test_pow
+++ b/Lib/test/output/test_pow
@@ -22,8 +22,4 @@ The number in both columns should match.
 -3L -3L
 -7L -7L
 
-3.0 3.0
--5.0 -5.0
--1.0 -1.0
--7.0 -7.0
 
diff --git a/Lib/test/test_b2.py b/Lib/test/test_b2.py
index ae07f5b..1a21689 100644
--- a/Lib/test/test_b2.py
+++ b/Lib/test/test_b2.py
@@ -82,17 +82,28 @@ if fcmp(pow(2.,10), 1024.): raise TestFailed, 'pow(2.,10)'
 if fcmp(pow(2.,20), 1024.*1024.): raise TestFailed, 'pow(2.,20)'
 if fcmp(pow(2.,30), 1024.*1024.*1024.): raise TestFailed, 'pow(2.,30)'
 #
-# XXX These don't work -- negative float to the float power...
-#if fcmp(pow(-2.,0), 1.): raise TestFailed, 'pow(-2.,0)'
-#if fcmp(pow(-2.,1), -2.): raise TestFailed, 'pow(-2.,1)'
-#if fcmp(pow(-2.,2), 4.): raise TestFailed, 'pow(-2.,2)'
-#if fcmp(pow(-2.,3), -8.): raise TestFailed, 'pow(-2.,3)'
-#
+if fcmp(pow(-2.,0), 1.): raise TestFailed, 'pow(-2.,0)'
+if fcmp(pow(-2.,1), -2.): raise TestFailed, 'pow(-2.,1)'
+if fcmp(pow(-2.,2), 4.): raise TestFailed, 'pow(-2.,2)'
+if fcmp(pow(-2.,3), -8.): raise TestFailed, 'pow(-2.,3)'
+
+from types import FloatType
 for x in 2, 2L, 2.0:
     for y in 10, 10L, 10.0:
         for z in 1000, 1000L, 1000.0:
-            if fcmp(pow(x, y, z), 24.0):
-                raise TestFailed, 'pow(%s, %s, %s)' % (x, y, z)
+            if isinstance(x, FloatType) or \
+               isinstance(y, FloatType) or \
+               isinstance(z, FloatType):
+                try:
+                    pow(x, y, z)
+                except TypeError:
+                    pass
+                else:
+                    raise TestFailed("3-arg float pow() should have "
+                                     "raised TypeError %r" % (x, y, z))
+            else:
+                if fcmp(pow(x, y, z), 24.0):
+                    raise TestFailed, 'pow(%s, %s, %s)' % (x, y, z)
 
 print 'range'
 if range(3) != [0, 1, 2]: raise TestFailed, 'range(3)'
diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py
index 3b6081e..b0eaff7 100644
--- a/Lib/test/test_long.py
+++ b/Lib/test/test_long.py
@@ -314,10 +314,19 @@ def test_auto_overflow():
                 checkit(x, '**', y)
 
                 for z in special:
-                    if z != 0:
-                        expected = pow(longx, longy, long(z))
-                        got = pow(x, y, z)
-                        checkit('pow', x, y, '%', z)
+                    if z != 0 :
+                        if y >= 0:
+                            expected = pow(longx, longy, long(z))
+                            got = pow(x, y, z)
+                            checkit('pow', x, y, '%', z)
+                        else:
+                            try:
+                                pow(longx, longy, long(z))
+                            except TypeError:
+                                pass
+                            else:
+                                raise TestFailed("pow%r should have raised "
+                                "TypeError" % ((longx, longy, long(z))))
 
 # ---------------------------------------------------------------- do it
 
diff --git a/Lib/test/test_pow.py b/Lib/test/test_pow.py
index 51d7484..41911bf 100644
--- a/Lib/test/test_pow.py
+++ b/Lib/test/test_pow.py
@@ -64,6 +64,15 @@ def powtest(type):
         for j in range(jl, jh+1):
             for k in range(kl, kh+1):
                 if k != 0:
+                    if type == float or j < 0:
+                        try:
+                            pow(type(i),j,k)
+                        except TypeError:
+                            pass
+                        else:
+                            raise TestFailed("expected TypeError from "
+                                "pow%r" % ((type(i), j, k)))
+                        continue
                     if compare(pow(type(i),j,k), pow(type(i),j)% type(k)):
                         raise ValueError, "pow(" +str(i)+ "," +str(j)+ \
                              "," +str(k)+ ") != pow(" +str(i)+ "," + \
@@ -96,10 +105,6 @@ print `pow(-3L,3L) % -8`, `pow(-3L,3L,-8)`
 print `pow(5L,2) % -8`, `pow(5L,2,-8)`
 print
 
-print pow(3.0,3.0) % 8, pow(3.0,3.0,8)
-print pow(3.0,3.0) % -8, pow(3.0,3.0,-8)
-print pow(3.0,2) % -2, pow(3.0,2,-2)
-print pow(5.0,2) % -8, pow(5.0,2,-8)
 print
 
 for i in range(-10, 11):
@@ -112,8 +117,3 @@ for i in range(-10, 11):
             if j >= 0 and k != 0:
                 o = pow(long(i),j) % k
                 n = pow(long(i),j,k)
-                if o != n: print 'Long mismatch:', i,j,k
-            if i >= 0 and k != 0:
-                o = pow(float(i),j) % k
-                n = pow(float(i),j,k)
-                if o != n: print 'Float mismatch:', i,j,k
diff --git a/Misc/NEWS b/Misc/NEWS
index a1557f8..c082fce 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -3,6 +3,12 @@ What's New in Python 2.2a3?
 
 Core
 
+- The 3-argument builtin pow() no longer allows a third non-None argument
+  if either of the first two arguments is a float, or if both are of
+  integer types and the second argument is negative (in which latter case
+  the arguments are converted to float, so this is really the same
+  restriction).
+
 - The builtin dir() now returns more information, and sometimes much
   more, generally naming all attributes of an object, and all attributes
   reachable from the object via its class, and from its class's base
diff --git a/Objects/floatobject.c b/Objects/floatobject.c
index 39eba8e..8cd26b4 100644
--- a/Objects/floatobject.c
+++ b/Objects/floatobject.c
@@ -492,11 +492,13 @@ static PyObject *
 float_pow(PyObject *v, PyObject *w, PyObject *z)
 {
 	double iv, iw, ix;
- /* XXX Doesn't handle overflows if z!=None yet; it may never do so :(
-  * The z parameter is really only going to be useful for integers and
-  * long integers.  Maybe something clever with logarithms could be done.
-  * [AMK]
-  */
+
+	if ((PyObject *)z != Py_None) {
+		PyErr_SetString(PyExc_TypeError,
+			"3rd argument to floating pow() must be None");
+		return NULL;
+	}
+
 	CONVERT_TO_DOUBLE(v, iv);
 	CONVERT_TO_DOUBLE(w, iw);
 
@@ -538,16 +540,6 @@ float_pow(PyObject *v, PyObject *w, PyObject *z)
 		PyErr_SetFromErrno(PyExc_OverflowError);
 		return NULL;
 	}
-	if ((PyObject *)z != Py_None) {
-		double iz;
-		CONVERT_TO_DOUBLE(z, iz);
-		PyFPE_START_PROTECT("pow", return 0)
-	 	ix = fmod(ix, iz);	/* XXX To Be Rewritten */
-	 	if (ix != 0 && ((iv < 0 && iz > 0) || (iv > 0 && iz < 0) )) {
-		     ix += iz;
-		}
-  		PyFPE_END_PROTECT(ix)
-	}
 	return PyFloat_FromDouble(ix);
 }
 
diff --git a/Objects/intobject.c b/Objects/intobject.c
index 108e658..eaf869f 100644
--- a/Objects/intobject.c
+++ b/Objects/intobject.c
@@ -575,6 +575,11 @@ int_pow(PyIntObject *v, PyIntObject *w, PyIntObject *z)
 	CONVERT_TO_LONG(v, iv);
 	CONVERT_TO_LONG(w, iw);
 	if (iw < 0) {
+		if ((PyObject *)z != Py_None) {
+			PyErr_SetString(PyExc_TypeError, "integer pow() arg "
+			     "3 must not be specified when arg 2 is < 0");
+			return NULL;
+		}
 		/* Return a float.  This works because we know that
 		   this calls float_pow() which converts its
 		   arguments to double. */
diff --git a/Objects/longobject.c b/Objects/longobject.c
index 4209419..4d4a959 100644
--- a/Objects/longobject.c
+++ b/Objects/longobject.c
@@ -1598,12 +1598,17 @@ long_pow(PyObject *v, PyObject *w, PyObject *x)
 	
 	size_b = b->ob_size;
 	if (size_b < 0) {
-		/* Return a float.  This works because we know that
-		   this calls float_pow() which converts its
-		   arguments to double. */
 		Py_DECREF(a);
 		Py_DECREF(b);
 		Py_DECREF(c);
+		if (x != Py_None) {
+			PyErr_SetString(PyExc_TypeError, "integer pow() arg "
+			     "3 must not be specified when arg 2 is < 0");
+			return NULL;
+		}
+		/* Return a float.  This works because we know that
+		   this calls float_pow() which converts its
+		   arguments to double. */
 		return PyFloat_Type.tp_as_number->nb_power(v, w, x);
 	}
 	z = (PyLongObject *)PyLong_FromLong(1L);
-- 
cgit v0.12

