Question regarding Base.power_by_squaring

I’ve been playing with the @code_typed macro and checking how some pieces of code behave. I was trying to check the differences between ^(2,5) and ^(2,-5) but @code_typed shows the same output for these two cases (I’m using Julia 1.6.1):

julia> @code_typed ^(2,5)
CodeInfo(
1 ─ %1 = invoke Base.power_by_squaring(_2::Int64, _3::Int64)::Int64
└── return %1
) => Int64

julia> @code_typed ^(2,-5)
CodeInfo(
1 ─ %1 = invoke Base.power_by_squaring(_2::Int64, _3::Int64)::Int64
└── return %1
) => Int64

Using @which ^(2,5), I was able to find where the implementation of this function is:

julia> @which ^(2,5)
^(x::T, p::T) where T<:Integer in Base at intfuncs.jl:289

I found out that it calls power_by_squaring and that if the exponent is negative and the base is different from 1 or -1, it should throw an error:

function power_by_squaring(x_, p::Integer)
x = to_power_type(x_)
if p == 1
return copy(x)
elseif p == 0
return one(x)
elseif p == 2
return x*x
elseif p < 0
isone(x) && return copy(x)
isone(-x) && return iseven(p) ? one(x) : copy(x)
throw_domerr_powbysq(x, p)
end
t = trailing_zeros(p) + 1
p >>= t
while (t -= 1) > 0
x *= x
end
y = x
while p > 0
t = trailing_zeros(p) + 1
p >>= t
while (t -= 1) >= 0
x *= x
end
y *= x
end
return y
end

^(x::T, p::T) where {T<:Integer} = power_by_squaring(x,p)

So I’m left with two unanswered questions:

  1. Why, in @code_typed, the return type of ^(2,-5) is Int64 and not Float64?
  2. Why ^(2,-5) does not throw an exception?
1 Like

I think the @code... -macros are wrong here. 2^5 (and 2^-5) actually lowers to Base.literal_pow(^, 2, Val(5)). If in the REPL you do a @less 2^-5, you see the comment:

# x^p for any literal integer p is lowered to Base.literal_pow(^, x, Val(p))
# to enable compile-time optimizations specialized to p.

This has a somewhat weird effect:

julia> 2^-5
0.03125

julia> p = -5
-5

julia> 2^p
ERROR: DomainError with -5:
Cannot raise an integer x to a negative power -5.
Make x or -5 a float by adding a zero decimal (e.g., 2.0^-5 or 2^-5.0 instead of 2^-5)or write 1/x^5, float(x)^-5, x^float(-5) or (x//1)^-5.
Stacktrace:
...
2 Likes

This seems to be a limitation of the @code_… macros. If you look at the corresponding function e.g.:

help?> code_lowered
search: code_lowered @code_lowered

  code_lowered(f, types; generated=true, debuginfo=:default)
  ...

you see, that it is working only on the types, e.g.:

ulia> code_lowered( ^, (Int,Int) )
1-element Vector{Core.CodeInfo}:
 CodeInfo(
1 ─ %1 = Base.power_by_squaring(x, p)
└──      return %1
)

therefor you get the the wrong implementation. You can overcome this by:

julia> function show_power()
       res=^(2,-5)
       end
show_power (generic function with 1 method)

julia> @code_lowered show_power()
CodeInfo(
1 ─ %1 = Core.apply_type(Base.Val, -5)
│   %2 = (%1)()
│   %3 = Base.literal_pow(Main.:^, 2, %2)
│        res = %3
└──      return %3
)

You see now that Base.literal_pow is actually called.

(expanded on @sgaure answer)

2 Likes