[issue44344] Documentation for pow() should include the possibility of complex numbers as a return type

New submission from Erik Y. Adams <erikyadams@outlook.com>: https://docs.python.org/3/library/functions.html#pow The built-in pow() function will return a complex number if the base is negative and the exponent is a float between 0 and 1. For example, the value returned by `pow(-1, 1.0/3)` is `(1.0000000000000002+1.7320508075688772j)` The answer is mathematically correct, but `-2.0` is also mathematically correct. There is nothing in the documentation currently to suggest that a complex number might be returned; in fact, given the statement "[with] mixed operand types, the coercion rules for binary arithmetic operators apply", one might reasonably expect `-2.0` as the answer. I suggest the following sentences be added to the end of the second paragraph: "If `base` is negative and the `exp` is a `float` between 0 and 1, a complex number will be returned. For example, `pow(-8, 1.0/3)` will return `(1.0000000000000002+1.7320508075688772j)`, and not `-2.0.`" ---------- assignee: docs@python components: Documentation messages: 395305 nosy: docs@python, eyadams priority: normal severity: normal status: open title: Documentation for pow() should include the possibility of complex numbers as a return type type: enhancement versions: Python 3.9 _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue44344> _______________________________________

Dennis Sweeney <sweeney.dennis650@gmail.com> added the comment: For some prior art, https://www.wolframalpha.com/input/?i=%28-8%29+%5E+%281%2F3%29 says it defaults to using "the principal root" over "the real-valued root" Also, I think the relevant property is that the exponent is not an integer; being between 0 and 1 is irrelevant:
pow(-8, 4/3) (-8.000000000000005-13.856406460551014j)
Maybe the tweak could be something like "Note that using a negative base with a non-integer exponent will return the principal complex exponent value, even if a different real value exists." ---------- nosy: +Dennis Sweeney _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue44344> _______________________________________

Mark Dickinson <dickinsm@gmail.com> added the comment: [Dennis]
I think the relevant property is that the exponent is not an integer
Yep: the delegation to complex pow kicks in after handling infinities and nans, and only for strictly negative base (-0.0 doesn't count as negative for this purpose) and non-integral exponent. Here's the relevant code: https://github.com/python/cpython/blob/257e400a19b34c7da6e2aa500d80b54e4c4db... To avoid confusion, we should probably not mention fractions like `1/3` and `4/3` as example exponents in the documentation, since those hit the What-You-See-Is-Not-What-You-Get nature of binary floating-point. Mathematically, `z^(1/3)` is a very different thing from `z^(6004799503160661/18014398509481984)` for a negative real number `z`, and the latter is what's _actually_ being computed with `z**(1/3)`. The advantage of the principal branch approach is that it's continuous in the exponent, so that `z^(1/3)` and `z^(6004799503160661/18014398509481984)` only differ by a tiny amount. ---------- nosy: +mark.dickinson _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue44344> _______________________________________

Change by Steven D'Aprano <steve+python@pearwood.info>: ---------- nosy: +steven.daprano _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue44344> _______________________________________

Erik Y. Adams <erikyadams@outlook.com> added the comment: I still think the most important aspect of this is that pow() will return complex numbers, contrary to what is implied by the statement I quoted at the beginning of this thread. Perhaps we should just borrow from the documentation for the power operator, which says: Raising 0.0 to a negative power results in a ZeroDivisionError. Raising a negative number to a fractional power results in a complex number. (In earlier versions it raised a ValueError.) https://docs.python.org/3/reference/expressions.html#the-power-operator ---------- _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue44344> _______________________________________

Mark Dickinson <dickinsm@gmail.com> added the comment:
Perhaps we should just borrow from the documentation for the power operator, which says [...]
That sounds good to me. ---------- _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue44344> _______________________________________

Change by Mark Dickinson <dickinsm@gmail.com>: ---------- keywords: +patch pull_requests: +26312 stage: -> patch review pull_request: https://github.com/python/cpython/pull/27853 _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue44344> _______________________________________

Change by Mark Dickinson <dickinsm@gmail.com>: ---------- assignee: docs@python -> mark.dickinson _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue44344> _______________________________________

Mark Dickinson <dickinsm@gmail.com> added the comment: @Erik: Do you have a moment to look at the PR (GH-27853) and see if the proposed changes work for you? ---------- _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue44344> _______________________________________

Raymond Hettinger <raymond.hettinger@gmail.com> added the comment: The pow() docs could use substantial updating. All of the logic for pow() is implemented in base.__pow__(exp, [mod]). Technically, it isn't restricted to numeric types, that is just a norm. The relationship between "pow(a,b)" and "a**b" is that both make the same call, a.__pow__(b). The discussion of coercion rules dates back to Python 2 where used to have a coerce() builtin. Now, the cross-type logic is buried in either in type(a).__pow__ or in type(b).__rpow__. The equivalence of pow(a, b, c) to a more efficient form of "a ** b % c" is a norm but not a requirement and is only supported by ints or third-party types. My suggestions 1st paragraphs: Stay at a high level, covering only the most common use case and simple understanding of how most people use pow(): Return *base* to the power *exp* giving the same result as ``base**exp``. If *mod* is present and all the arguments are integers, return *base* to the power *exp*, modulo *mod*. This gives the same result as ``base ** exp % mod`` but is computed much more efficiently. 2nd paragraph: Be precise about what pow() actually does, differentiating the typical case from what is actually required: The :func:`pow` function calls the base's meth:`__pow__` method falling back to the exp's meth:`__rpow__` if needed. The logic and semantics of those methods varies depending on the type. Typically, the arguments have numeric types but this is not required. For types that support the three-argument form, the usual semantics are that ``pow(b, e, m)`` is equivalent to ``a ** b % c`` but this is not required. 3rd paragraph: Cover behaviors common to int, float, and complex. 4th paragraph and later: Cover type specific behaviors (i.e. only int supports the three argument form and the other arguments must be ints as well). ---------- nosy: +rhettinger _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue44344> _______________________________________

Łukasz Langa <lukasz@langa.pl> added the comment: New changeset 887a55705bb6c05a507c2886c9978a9e0cff0dd7 by Mark Dickinson in branch 'main': bpo-44344: Document that pow can return a complex number for non-complex inputs. (GH-27853) https://github.com/python/cpython/commit/887a55705bb6c05a507c2886c9978a9e0cf... ---------- nosy: +lukasz.langa _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue44344> _______________________________________

Change by miss-islington <mariatta.wijaya+miss-islington@gmail.com>: ---------- nosy: +miss-islington nosy_count: 7.0 -> 8.0 pull_requests: +27410 pull_request: https://github.com/python/cpython/pull/29134 _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue44344> _______________________________________

Change by miss-islington <mariatta.wijaya+miss-islington@gmail.com>: ---------- pull_requests: +27411 pull_request: https://github.com/python/cpython/pull/29135 _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue44344> _______________________________________

Łukasz Langa <lukasz@langa.pl> added the comment: New changeset 9b3cda56870d087cf50f605e91f3d26964868640 by Miss Islington (bot) in branch '3.10': bpo-44344: Document that pow can return a complex number for non-complex inputs. (GH-27853) (GH-29135) https://github.com/python/cpython/commit/9b3cda56870d087cf50f605e91f3d269648... ---------- _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue44344> _______________________________________

Łukasz Langa <lukasz@langa.pl> added the comment: New changeset c53428fe8980aab6eda3e573bafed657e6798e6e by Miss Islington (bot) in branch '3.9': bpo-44344: Document that pow can return a complex number for non-complex inputs. (GH-27853) (GH-29134) https://github.com/python/cpython/commit/c53428fe8980aab6eda3e573bafed657e67... ---------- _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue44344> _______________________________________

Łukasz Langa <lukasz@langa.pl> added the comment: Thanks, Mark! ✨ 🍰 ✨ ---------- resolution: -> fixed stage: patch review -> resolved status: open -> closed versions: +Python 3.10, Python 3.11 _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue44344> _______________________________________
participants (7)
-
Dennis Sweeney
-
Erik Y. Adams
-
Mark Dickinson
-
miss-islington
-
Raymond Hettinger
-
Steven D'Aprano
-
Łukasz Langa