Unexpected behaviour with integer sqrt and integer ^

Motivation:
I stumbled upon

√(16)^16 # 0.0

and

√(17)^17 # DomainError with -2.8632214305930583e18:

The reason: This is of course due to integer arithmetic. On my machine:

16^16 # 0
17^17 # -2863221430593058543

Why I think this is unexpected behaviour
I, and I guess most programmers, read √(16)^16 as:

  1. call the method with argument 16
  2. raise the output to the power 16.

This would indeed be the case if I would have used

sqrt(16)^16 # 4.294967296e9

On the other hand, I can imagine, that Julia replaces √(x) by x^(0.5) under the hood. However, ^ acts right associative, so then I would expect something along the line

√(16)^16 == 16^0.5^16 == 16^8.0 == 4.294967296e9

In short, I would not have expected integer arithmetic to matter for √(16)^16, as I expected √(16) naturally promotes to Float64 and the power operation happens after this.

Relevance and next steps?
I found this in a loop:

for M in 1:20
  #...
  det = √(M)^M
end

i.e., in a setting where one naturally works with integers, so I guess this can be relevant. The fixes are of course obvious: sqrt(M)^M or √(1.0 * M)^M.

I tested this both on Julia 1.6.3 and 1.7.2. on a MacBook Pro 2017.

Is this indeed unexpected behaviour and so far unknown? In that case, should I open an Issue on Github?

1 Like

The problem seems to be that that √ is a prefix operator with lower precedence than ^, resulting in sqrt(16^16) being the code that is run. The strange thing is that looking at the precedences I would interpret it as √ having higher precedence since it is defined on line 99 compared to ^ which is on line 30?

julia> dump(:(√(16)^16))
Expr
  head: Symbol call
  args: Array{Any}((2,))
    1: Symbol √
    2: Expr
      head: Symbol call
      args: Array{Any}((3,))
        1: Symbol ^
        2: Int64 16
        3: Int64 16

julia> √16^16 # Parenthesis doesn't matter here since √ is parsed as an operator
0.0

julia> (√16)^16 # This forces sqrt to be run first
4.294967296e9
2 Likes

sqrt and the sqrt symbol are the same function

However operator precedence in the parser might be the problem:

julia> Base.operator_precedence(:^)
15

julia> Base.operator_precedence(:√)
0

^ is one of the operator with highest priority.

3 Likes

No, the documentation clearly states that ^ has higher precedence:

https://docs.julialang.org/en/v1/manual/mathematical-operations/#Operator-Precedence-and-Associativity

2 Likes

julia> Base.operator_precedence(:^)
15
julia> Base.operator_precedence(:√)
0

I don’t think this is representative, see:

julia> Base.operator_precedence(:*)
12
julia> √(4)*4
8.0

i.e., has to have a higher precedence as 0.

1 Like
help?> Base.operator_precedence
  operator_precedence(s::Symbol)

  Return an integer representing the precedence of operator s, relative to other operators. Higher-numbered operators take precedence over lower-numbered operators. Return 0 if s is not a valid operator.

yes 0 means invalid operator. I should have read the documentation. Then I do not know about how the parser handles the precedence of \sqrt wrt to * relative to \sqrt wrt to ^

1 Like

I would guess that was just another name for sqrt, this is, it is just a function name and not an operator but apparently it can be used without parenthesis, so it does not seem to be the case. The documentation uses the term “prefix operator”.

"√" can be typed by \sqrt<tab>

search: √

  sqrt(x)

  Return \sqrt{x}. Throws DomainError for negative Real arguments. Use complex
  negative arguments instead. The prefix operator √ is equivalent to sqrt.

1 Like

Nice! Went directly to source to try to find it but reasonable that there is good docs for it :slight_smile:

My confusion was referring to the comment on the first line in the linked file which stated ;; Operator precedence table, lowest at top which I interpreted as lower precedence at top of file. Not sure how this is supposed to be interpreted, but probably not as I did at least…

1 Like

Okay, it seem it is now clear what is happening… so my question is: does it make sense like that :smiley:

Idk, but I feel like should have higher precedence than ^ after observing the effects of the converse. Also √(16) feels more like a function call than applying a right associative operator. I quickly scanned parts of the documentation and so far I exclusively found examples with the √(x) syntax instead of √x, so the standard seems to support the “method call” interpretation.

Are there other opinions?

I believe that because is a prefix operator, it should be used as √x not √(x). For example, Meta.parse("√x") == Meta.parse("√(x)") == :(√x). If the documentation uses it otherwise, it should be changed.

Could you point us to specific examples? I found 71 occurrences of in the JuliaLang/Julia codebase (including /doc) only three of which use parentheses:

julia> .√(1:4)
julia> √(a^2 + a^2) # a^2 overflows
“compositions of unary operators are parsed with right-associativity, e. g., √√-a as √(√(-a)).”

1 Like

Guess lucky-me directly found the few examples of √(x). To be fair, they are on the mathematics page, so it was an obvious place to search for :tipping_hand_man: