Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is (-27)**(1.0/3.0) not -3.0 in Python?

Tags:

python

In math, you are allowed to take cubic roots of negative numbers, because a negative number multiplied by two other negative numbers results in a negative number. Raising something to a fractional power 1/n is the same as taking the nth root of it. Therefore, the cubic root of -27, or (-27)**(1.0/3.0) comes out to -3.

But in Python 2, when I type in (-27)**(1.0/3.0), it gives me an error:

Traceback (most recent call last):
  File "python", line 1, in <module>
ValueError: negative number cannot be raised to a fractional power

Python 3 doesn't produce an exception, but it gives a complex number that doesn't look anything like -3:

>>> (-27)**(1.0/3.0)
(1.5000000000000004+2.598076211353316j)

Why don't I get the result that makes mathematical sense? And is there a workaround for this?

like image 552
mdlp0716 Avatar asked Dec 24 '22 14:12

mdlp0716


1 Answers

-27 has a real cube root (and two non-real cube roots), but (-27)**(1.0/3.0) does not mean "take the real cube root of -27".

First, 1.0/3.0 doesn't evaluate to exactly one third, due to the limits of floating-point representation. It evaluates to exactly

0.333333333333333314829616256247390992939472198486328125

though by default, Python won't print the exact value.

Second, ** is not a root-finding operation, whether real roots or principal roots or some other choice. It is the exponentiation operator. General exponentiation of negative numbers to arbitrary real powers is messy, and the usual definitions don't match with real nth roots; for example, the usual definition of (-27)^(1/3) would give you the principal root, a complex number, not -3.

Python 2 decides that it's probably better to raise an error for stuff like this unless you make your intentions explicit, for example by exponentiating the absolute value and then applying the sign:

def real_nth_root(x, n):
    # approximate
    # if n is even, x must be non-negative, and we'll pick the non-negative root.
    if n % 2 == 0 and x < 0:
        raise ValueError("No real root.")
    return (abs(x) ** (1.0/n)) * (-1 if x < 0 else 1)

or by using complex exp and log to take the principal root:

import cmath
def principal_nth_root(x, n):
    # still approximate
    return cmath.exp(cmath.log(x)/n)

or by just casting to complex for complex exponentiation (equivalent to the exp-log thing up to rounding error):

>>> complex(-27)**(1.0/3.0)
(1.5000000000000004+2.598076211353316j)

Python 3 uses complex exponentiation for negative-number-to-noninteger, which gives the principal nth root for y == 1.0/n:

>>> (-27)**(1/3)  # Python 3
(1.5000000000000004+2.598076211353316j)
like image 101
user2357112 supports Monica Avatar answered Jan 13 '23 17:01

user2357112 supports Monica