4

I like to incorporate Python in my exploration of functions, however I have encountered a behavior that I didn't expect or want for these assessments.

>>> def h(x):
...     return -1 / x**(1/3)
...
>>> h(-343)
(-0.07142857142857145 + 0.12371791482634838j)

I would like a true inverse to the following function:

>>> def f(x):
...     return x**3
...
>>> f(-7)
-343

Such that:

>>> def h(x):
...     return -1/inverse_f(x)
...
>>> h(-343)
0.14285714285714285

Is there a Pythonic way to get this behavior?

Nayuki
  • 17,911
  • 6
  • 53
  • 80
Galen
  • 1,128
  • 1
  • 14
  • 31
  • Python programmers do not normally use `lambda` when a function will be called by name. You want to define named functions, such as `def h(x): return -1 / x**(1/3)`. – DYZ Jan 04 '17 at 07:29
  • Thank you for the feedback. I have changed the definitions accordingly. – Galen Jan 04 '17 at 07:33
  • Well, now you have incorrect indentation. I would actually say it was fine before, as long as you _know_ that `lambda` is not ordinarily used in this way. – David Z Jan 04 '17 at 07:35
  • @DavidZ The OPs indentation is fine. – DYZ Jan 04 '17 at 07:36
  • I appreciate the comments on style all the same. I am aware of PEP8, and pylint to improve my code style as I develop. – Galen Jan 04 '17 at 07:41
  • 1
    In fact, your functions are correct, but a cubic root of a negative number has three values; you are getting one of them, but apparently want another one. Is that correct? – DYZ Jan 04 '17 at 07:45
  • 1
    @DYZ (3 comments up) And using named lambda functions in an example is also fine. Neither of these things are best practices, sure, and that's something to be aware of, but neither one is a problem when asking a question. – David Z Jan 04 '17 at 08:08
  • 2
    @Galen the built-in `x**(1/3)` returns the so-called principal branch of the cube root function. The principal branch is the one with the smallest argument (i.e. angle away from the positive x axis). There are two other branches; you want the one which falls on the negative x axis. Whenever you work with nth roots, you'll have to address this issue about principal versus some other branch. Try a web search for "nth roots of unity" for an introduction to this interesting and important topic. – Robert Dodier Jan 05 '17 at 06:38

2 Answers2

6

You are getting problems because a negative number raised to a fractional power can be a complex number.


The solution is to apply a math identity. We know that if x is negative, then x1/3 is equal to −((−x)1/3). In other words, we turn x into a positive number, take the cube root, and negate it again. Here is the Python code to do so:

def h(x):
    if x >= 0:
        return -1.0 / x**(1.0/3.0)
    else:  # x < 0
        return -h(-x)

To explain why you are getting the problem in the first place, it helps to look at the implementation of x**y (the power operator). The key mathematical identity is that xy = exp(log(x) · y). This identity makes it easier to handle powers, because the exponent is treated as a regular number and doesn't need to be analyzed (is it an integer? is it a fraction? is it negative? etc.).

When x is a positive number, log(x) is a real number. As long as y is also a real number, exp(log(x) · y) will be a real number.

But when x is a negative number, log(x) is a complex number. Specifically, it is equal to [log(-x) + π · i]. When we multiply such a complex number by y and then apply exp(), the result will usually be a complex number - which is not what you hoped for.

Nayuki
  • 17,911
  • 6
  • 53
  • 80
1

if you want to work in the integers only (and ignore the complex solutions of your function) this may be a way. at least for your example in the title it does what you want. (so this only addresses the title of your question; as the rest has changed now it will not help with that... Nayuki's answer will)

gmpy2 has an iroot method:

import gmpy2
print(gmpy2.iroot(343, 3))  # -> (mpz(7), True)

starting from there you should be able to combine your function.

import gmpy2
from fractions import Fraction

def h(x):
    sign = 1 if x >= 0 else -1
    root, is_int = gmpy2.iroot(abs(x), 3)
    if not is_int:
        return None  # the result is not an integer
    root = int(root)
    return -sign * Fraction(1, root)

print(h(-343))  # -> 1/7

and the inverse:

def g(x):
    return -Fraction(1, x**3)

print(g(h(-343)))  # -> -343
Community
  • 1
  • 1
hiro protagonist
  • 44,693
  • 14
  • 86
  • 111
  • @DYZ: exactly, that is what the `sign` and the `abs` are for! (oh, your comment was probably before i added the full function...) – hiro protagonist Jan 04 '17 at 07:44
  • A negative number has three cubic roots. Your code computes only one of them. – DYZ Jan 04 '17 at 07:46
  • @DYZ: the program calculates the only integer one (or returns `None` if that does not exist). it looked to me that was what the OP was looking for. the complex ones are not mentioned anywhere. oops, right, now they appear in the question... will adapt my answer a bit. – hiro protagonist Jan 04 '17 at 07:48