Negative Base and Negative Exponent

Hello. I’ve spent some time reading about the reasons behind why Julia deals with negative exponents the way it does and I’m not interested in resurrecting that debate. I’m working on a script and I’ve run into a problem that boils down to the following:

-1.5 ^ -1.5 # Works!

b = -1.5
b ^ b # Doesn't work! :(

# Why? Because:
(-1.5) ^ (-1.5) # also doesn't work while
- (1.5) ^ (-1.5) # works, and is what the above is evaluated as.

How can I get Julia to accept taking a variable with a negative base to a negative exponent?

It’s not the negative exponents, it’s the fractionals; which correspond to roots. You have to make it a complex number, then it works:

julia> (-1.5+0im) ^ (0.5)                                                                                                                             
7.499399432609231e-17 + 1.224744871391589im                    

julia> (-1.5+0im) ^ (-1.5)                                                                                                                            
-9.999199243478975e-17 + 0.5443310539518174im                                                                                                                                                                                         

Note that -1.5 ^ -1.5 is not being parsed as you probably expect:

julia> parse("-1.5 ^ -1.5")
:(-(1.5 ^ -1.5))

When in doubt, check how your expression is parsed with parse, in any case use parentheses to avoid ambiguities.

Also observe that this is standard mathematical parsing:
When people write -x^2 they mean -(x^2) not (-x)^2.

4 Likes

Are there any other programming languages that give ^ higher precedence than unary - though?
It seems like something rather ripe for confusion and hard to find bugs.

Python and Ruby, just to name two:

>>> -2**2
-4
irb(main):001:0> -2^2
=> -4
julia> -2^2
-4

R, Perl …

Prolog is tricky: the arithmetic expression -2 ** 2 evaluates as 4.0 even though exponentiation does have precedence over negation. (I’m not sure why it goes to a float. It’s been a while since I knew these things.)

That’s because in Prolog the minus sign in -2 is not the negation operator but a part of literal syntax.

In the arithmetic expression -B ** 2, where the base is a variable, the minus sign is an operator. That expression is parsed as -(B ** 2) and maybe should not be spaced the way I did.

In Matlab:

>> -2^2

ans =

    -4

I would be very surprised if -2^2=4.

I still find it very confusing that:

julia> dump(parse("-2.0 * u"))
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol *
    2: Float64 -2.0
    3: Symbol u
  typ: Any

but with ^, it is parsed differently, i.e.:

julia> dump(parse("-2.0 ^ u"))
Expr
  head: Symbol call
  args: Array{Any}((2,))
    1: Symbol -
    2: Expr
      head: Symbol call
      args: Array{Any}((3,))
        1: Symbol ^
        2: Float64 2.0
        3: Symbol u
      typ: Any
  typ: Any

This sort of inconsistency, and having special handling in the parser for ^ vs. *, just seems wrong to me.

I think it’s just a peephole optimization built into the Julia parser.

The parses of "-2 * 2" and "-u * w", resp. "-2^2" and "-u^w" are consistent with exponentiation having precedence over unary minus, and unary minus over multiplication.

That doesn’t fit well with the idea that one can have different meanings for the operators, depending on what types they are applied to (not that that’s a good thing - I’d prefer that Julia eliminated the type punning for AbstractString on * and ^).

IIRC, didn’t Fortress have a way of giving a warning or error when the spacing indicated that the programmer intention was clearly at odds with what the compiler would do, in this sort of case?

There seems to be an actual inconsistency (in the REPL in Julia 0.5.0) with what I guessed to be a peepholing in the parser if a program overrides the meaning of unary minus.

julia> import Base.-

julia> function (-)(x::Int) 3 end
- (generic function with 191 methods)

julia> -2, -(2), - 2
(-2,3,3)

julia> u = 2 ; -u, -(u), - u
(3,3,3)

But is it ever a good idea to override Base methods of unary minus?

By the way, while a macro could undo the parse-time analysis and reanalyze a negative number as a negation of a number, there is no way to recover certain occurrences of a unary plus from the parse tree. Those are truly gone.

I wouldn’t worry about either of these. They affect some built-in literal types only, and those I think are best left to Julia.