Issue44344

Created on **2021-06-08 04:13** by **eyadams**, last changed **2021-09-09 17:22** by **rhettinger**.

Pull Requests | |||
---|---|---|---|

URL | Status | Linked | Edit |

PR 27853 | open | mark.dickinson, 2021-08-20 10:20 |

Messages (7) | |||
---|---|---|---|

msg395305 - (view) | Author: Erik Y. Adams (eyadams) | Date: 2021-06-08 04:13 | |

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.`" |
|||

msg395308 - (view) | Author: Dennis Sweeney (Dennis Sweeney) * | Date: 2021-06-08 05:56 | |

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." |
|||

msg395339 - (view) | Author: Mark Dickinson (mark.dickinson) * | Date: 2021-06-08 16:20 | |

[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/257e400a19b34c7da6e2aa500d80b54e4c4dbf6f/Objects/floatobject.c#L773-L782 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. |
|||

msg396254 - (view) | Author: Erik Y. Adams (eyadams) | Date: 2021-06-21 15:38 | |

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 |
|||

msg396267 - (view) | Author: Mark Dickinson (mark.dickinson) * | Date: 2021-06-21 16:15 | |

> Perhaps we should just borrow from the documentation for the power operator, which says [...] That sounds good to me. |
|||

msg401439 - (view) | Author: Mark Dickinson (mark.dickinson) * | Date: 2021-09-09 07:28 | |

@Erik: Do you have a moment to look at the PR (GH-27853) and see if the proposed changes work for you? |
|||

msg401500 - (view) | Author: Raymond Hettinger (rhettinger) * | Date: 2021-09-09 17:22 | |

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). |

History | |||
---|---|---|---|

Date | User | Action | Args |

2021-09-09 17:22:40 | rhettinger | set | nosy:
+ rhettinger messages: + msg401500 |

2021-09-09 07:28:48 | mark.dickinson | set | messages: + msg401439 |

2021-08-20 10:22:03 | mark.dickinson | set | assignee: docs@python -> mark.dickinson |

2021-08-20 10:20:40 | mark.dickinson | set | keywords:
+ patch stage: patch review pull_requests: + pull_request26312 |

2021-06-21 16:15:43 | mark.dickinson | set | messages: + msg396267 |

2021-06-21 15:38:07 | eyadams | set | messages: + msg396254 |

2021-06-09 08:52:14 | steven.daprano | set | nosy:
+ steven.daprano |

2021-06-08 16:20:04 | mark.dickinson | set | nosy:
+ mark.dickinson messages: + msg395339 |

2021-06-08 05:56:47 | Dennis Sweeney | set | nosy:
+ Dennis Sweeney messages: + msg395308 |

2021-06-08 04:13:36 | eyadams | create |