Negative Base and Negative Exponent


#1

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?


#2

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                                                                                                                                                                                         

#3

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.


#4

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


#5

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.


#6

Python and Ruby, just to name two:

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

#7

R, Perl …


#8

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.


#9

In Matlab:

>> -2^2

ans =

    -4

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


#10

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.


#11

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.


#12

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?


#13

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.