# Why doesn't \`^\` promote its arguments?

**URL:** <https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444>\
**Category:** General Usage\
**Created:** [February 14, 2022, 4:29pm UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444 "2022-02-14T16:29:00Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Federico\_Marotta](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/federico_marotta/32/33810_2.png) [@Federico\_Marotta](https://discourse.julialang.org/u/Federico_Marotta)\
**Post date:** [February 14, 2022, 4:29pm UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/1 "2022-02-14T16:29:01Z")

</div>

In Julia, if I write `Int8(127) + 1` I get 128 as an answer (and not an overflow), because the two numbers are promoted to `Int` before performing the addition. The same promotion occurs for other operations like `-`, `*`, and `/`, but _not_ for `^`. I’ve read in [promotion.jl](https://github.com/JuliaLang/julia/blob/f7b2c3d646e6fbf2ecba9f944a1848a7b632cb05/base/promotion.jl#L418) that  
`^(x::Number, y::Number) = ^(promote(x,y)...)`, so I expected that the result of `^(Int8(127), 2)` would be 16129 of type `Int`, but instead the result is 1 of type `Int8`. In fact, the result of `^` between integers seems to be always of the same type of the first argument:

```julia
x = Int64(3)
y = Int8(5)
x^y
# result: 243 of type Int64

```

What is really weird for me is that if I call `promote()` explicitly it works: `^(promote(Int8(127), 2)...)` returns 16129 of type `Int64` as I expected. But then what is the purpose of that line of code in promotion.jl? It doesn’t seem to be applied. What am I missing here?

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [February 14, 2022, 4:33pm UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/2 "2022-02-14T16:33:18Z")

</div>

```julia
julia> @which x^y
# https://github.com/JuliaLang/julia/tree/5e7b6dd090791ca545897d9625a82cae34c116b1/base/intfuncs.jl#L299
^(x::Number, p::Integer) in Base at intfuncs.jl:299

```

---

<div class="post-metadata">

**Author:** ![goerch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerch/32/29122_2.png) [@goerch](https://discourse.julialang.org/u/goerch)\
**Post date:** [February 14, 2022, 4:39pm UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/3 "2022-02-14T16:39:52Z")

</div>

I believe promotion is explicitly defined [here](https://github.com/JuliaLang/julia/blob/68e120fbab58a3a946ba4cf1920e5d98d68889f7/base/int.jl#L986-L993)?

---

<div class="post-metadata">

**Author:** ![Federico\_Marotta](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/federico_marotta/32/33810_2.png) [@Federico\_Marotta](https://discourse.julialang.org/u/Federico_Marotta)\
**Post date:** [February 14, 2022, 5:08pm UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/4 "2022-02-14T17:08:36Z")

</div>

Ahh, I see. Indeed, if I extend `Base.^` by saying `^(x::Number, p::Integer) = ^(promote(x,p)...)`, then `^(Int8(127), Int64(2))` yields 16129 of type `Int64`. But why is `^` different from other operators in this respect? I’ve read some comments in the code about letting the compiler optimise the operation according to the type of the exponent… Can this be the reason? Still, it’s a little puzzling to me that `^` should be so special.

---

<div class="post-metadata">

**Author:** ![Federico\_Marotta](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/federico_marotta/32/33810_2.png) [@Federico\_Marotta](https://discourse.julialang.org/u/Federico_Marotta)\
**Post date:** [February 14, 2022, 5:10pm UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/5 "2022-02-14T17:10:14Z")

</div>

Mhh, thanks! but why is `^` treated differently from the other operators in this snippet?

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [February 14, 2022, 5:19pm UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/6 "2022-02-14T17:19:16Z")

</div>

that’s for literal? from an implementation perspective, power by squaring simply does this:

```julia
while p>1
  x *= x
  p -= 1
end

```

which is why there’s no promotion because `x` only multiplies with its own type.

We can change this behavior if we promote it before the loop. Idk what’s the ramification of that or if that’s even math/IEEE sound strategy

---

<div class="post-metadata">

**Author:** ![goerch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerch/32/29122_2.png) [@goerch](https://discourse.julialang.org/u/goerch)\
**Post date:** [February 14, 2022, 5:23pm UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/7 "2022-02-14T17:23:49Z")

</div>

> [@Federico\_Marotta](#):
>
> Mhh, thanks! but why is `^` treated differently from the other operators in this snippet?

I don’t know, but I could imagine the reason being that the adequate target type of the promotion is dependent of the (value of the) exponent.

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [February 14, 2022, 6:06pm UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/8 "2022-02-14T18:06:14Z")

</div>

> [@Federico\_Marotta](#):
>
> but why is `^` treated differently from the other operators in this snippet?

I would imagine it would be unacceptable from a performance point of view if `x^2` is calculated as `pow(x, 2.0)` instead of as `x*x`. Promoting the exponent to a float would be pretty bad.

---

<div class="post-metadata">

**Author:** ![Federico\_Marotta](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/federico_marotta/32/33810_2.png) [@Federico\_Marotta](https://discourse.julialang.org/u/Federico_Marotta)\
**Post date:** [February 15, 2022, 7:58am UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/9 "2022-02-15T07:58:48Z")

</div>

That’s exactly what happens, though: if we specify either the base or the exponent as a float, promotion occurs. E.g. `3^2.0 == 9.0` or `3.0^2 == 9.0`. This is also what I would expect after reading the documentation. But the problem is just for integers of different sizes (no need to involve floats or other data types). `Int8(3)^Int64(2)` returns `9` of type `Int8`, so no promotion occurs… whereas for all other operations, and for exponentiations between integers and floats, promotion occurs. E.g. `Int8(3) + Int64(2)` returns `5` of type `Int64`. I agree with @jling that the proximal reason is that exponentiation between integers is implemented as `x * x` repeated `p` times, so the type of the exponent is not considered. Isn’t this behaviour inconsistent? It could be easily fixed by adjusting the [power\_by\_squaring](https://github.com/JuliaLang/julia/blob/5e7b6dd090791ca545897d9625a82cae34c116b1/base/intfuncs.jl#L263) function. And since we are limited to exponentiations between integers (albeit of different sizes) I don’t think performance plays an important role here.

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [February 15, 2022, 8:40am UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/10 "2022-02-15T08:40:28Z")

</div>

> [@Federico\_Marotta](#):
>
> That’s exactly what happens, though: if we specify either the base or the exponent as a float, promotion occurs. E.g. `3^2.0 == 9.0` or `3.0^2 == 9.0` .

No, promotion does not happen for 3.0^2, it just turns into `3.0*3.0`, as you actually note later in your post.

> [@Federico\_Marotta](#):
>
> It could be easily fixed by adjusting the [power\_by\_squaring](https://github.com/JuliaLang/julia/blob/5e7b6dd090791ca545897d9625a82cae34c116b1/base/intfuncs.jl#L263) function.

What sort of changes and behaviour would you like to see?

---

<div class="post-metadata">

**Author:** ![Federico\_Marotta](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/federico_marotta/32/33810_2.png) [@Federico\_Marotta](https://discourse.julialang.org/u/Federico_Marotta)\
**Post date:** [February 15, 2022, 9:23am UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/11 "2022-02-15T09:23:32Z")

</div>

Pardon my poor choice of words, what I mean is the following.

- In the case of `3.0^2`, the result is of type `Float`, the ‘common type’ between `Float` and `Int`.
- Similarly, in the case of `Int8(3) + Int64(2)`, the result is of type `Int64`, the ‘common type’ between `Int8` and `Int64`.

From my point of view, the expected behaviour of `Int8(3)^Int64(2)` should be to return 9 of type `Int64`, betcause `Int64` is the common type between the two arguments. The actual behaviour, however, is that this operation returns a number of type `Int8`, because of how exponentiation is implemented in `power_by_squaring`.

What I would do is, at the beginning of the `power_by_squaring(x_, p::Integer)` function, convert `x_` (the base) to the common type between the base and the exponent, and leave the rest of the algorithm unchanged. It would still result in exponentiation between integers, but the behaviour would be consistent with the other operations.

---

<div class="post-metadata">

**Author:** ![stephancb](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stephancb/32/14243_2.png) [@stephancb](https://discourse.julialang.org/u/stephancb)\
**Post date:** [February 15, 2022, 10:57am UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/12 "2022-02-15T10:57:13Z")

</div>

Now

```julia
julia> Int8(16)^2
0

```

After the change

```julia
julia> Int8(16)^2
256

```

Probably some users had liked to have this from the beginning. Nevertheless, now it would be a breaking change.

---

<div class="post-metadata">

**Author:** ![heliosdrm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/heliosdrm/32/3851_2.png) [@heliosdrm](https://discourse.julialang.org/u/heliosdrm)\
**Post date:** [February 15, 2022, 11:03am UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/13 "2022-02-15T11:03:59Z")

</div>

Related, with full discusion of why Julia does as it does:

> [@Julia messes up integer exponents?](https://discourse.julialang.org/t/julia-messes-up-integer-exponents/20773):
>
> I was using avogadro’s number in a calculation and couldn’t figure out why the results were not matching my matlab or python code… Turns out using integers as exponents gets you in trouble in Julia?They start off innocent but at higher powers, but really not that big for science (Avogadro’s number is very popular!!! as are many other constants…) julia\> 10^1 10 julia\> 10^2 100 julia\> 10^5 100000 julia\> 10^15 1000000000000000 julia\> 10^20 7766279631452241920 julia\> 10^16 10000000000000000 julia\> …

---

<div class="post-metadata">

**Author:** ![Palli](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/palli/32/3380_2.png) [@Palli](https://discourse.julialang.org/u/Palli)\
**Post date:** [February 15, 2022, 11:20am UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/14 "2022-02-15T11:20:34Z")

</div>

> [@Federico\_Marotta](#):
>
> The same promotion occurs for other operations like `-` , `*` , and `/` , but _not_ for `^` .

No, `-` , `*` will always give integers for integer input, while `/` will always give Float64, and when b in a^b is negative (and since it is special-cased for the sign, I’m guessing it was chosen to ignore type of b when positive, unless when its BigInt). In both cases Rational is arguable more correct, but slower, so tat was decided.

See my thread (`/` is the most problematic operator, and simply promoting to type of either a or b is not enough, nor is it enough for any other operator, just most likely to be enough):

> [@Potential solution to the overflow problem; 64-bit considered harmful, 32- or 21-bit better](https://discourse.julialang.org/t/potential-solution-to-the-overflow-problem-64-bit-considered-harmful-32-or-21-bit-better/69918):
>
> We can implement FastIntegers.jl, faster than the current default Int64 and eliminate overflows from the language by leveraging Julia’s type system, and no hardware support is needed. As a package it of course doesn’t change the language itself, it would be a prototype, or actually very useful as such, but even better if integrated into the language. That could happen for Julia 2.0 but not if people are against the idea. If we consider even very simple functions: double(x) = 2x then it’s not …

See very long discussion:

> [@Discussion about integer overflow](https://discourse.julialang.org/t/discussion-about-integer-overflow/69627):
>
> Yes, it is a limitation but I would not call it big since we could use BigInt if Int64 is not enough and an overflow is something we can catch. I find more worrying this: \> julia\> 1 / 10^20000 \> Inf I’d rather have an overflow here than Inf, or at least an Infinitesimal value… Maybe something we could ask for future versions of Julia.

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [February 15, 2022, 11:26am UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/15 "2022-02-15T11:26:17Z")

</div>

I think they’re describing the apparent behavior of:

```julia

julia> for op in (+, -, *, /)
           a = 1
           b = 1.0
           @assert op(a,b) isa Float64
           @assert op(b,a) isa Float64
       end

```

which is not true for `^`, but the above behavior is just an accident for `/`, and the reason `^` doesn’t promote is also an accident

---

<div class="post-metadata">

**Author:** ![Federico\_Marotta](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/federico_marotta/32/33810_2.png) [@Federico\_Marotta](https://discourse.julialang.org/u/Federico_Marotta)\
**Post date:** [February 15, 2022, 1:30pm UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/16 "2022-02-15T13:30:16Z")

</div>

> [@Palli](#):
>
> No, `-` , `*` will always give integers for integer input, while `/` will always give Float64, and when b in a^b is negative (and since it is special-cased for the sign, I’m guessing it was chosen to ignore type of b when positive, unless when its BigInt). In both cases Rational is arguable more correct, but slower, so tat was decided.

You are right, `/` will always give `Float64`.

Regarding the discussions about [integer exponents](https://discourse.julialang.org/t/julia-messes-up-integer-exponents/20773) and [integer overflow](https://discourse.julialang.org/t/discussion-about-integer-overflow/69627), note that I don’t have a problem with unchecked integer overflow. `2^63` equals `-9223372036854775808`, which is totally acceptable and unsurprising, I’m just saying that `Int8(2)^63` should give the same result, because one of the arguments of `^`, namely `63`, is of type `Int64` (at least on my system). On the other hand, `Int8(2)^Int8(63)` should give `0`.

I also think that my discussion does not involve `Floats` at all. It seems that powers with float arguments are handled by a different function:

```julia
julia> @which 3.0^2
^(x::Float64, y::Integer) in Base.Math at math.jl:922

```

whereas

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

```

So, by changing the `power_by_squaring()` function, there wouldn’t be a performance decrease stemming from promoting integer exponents to floats.

Anyway, as @stephancb said, changing `power_by_squaring()` would be breaking, at this point. I guess I’ll just note down this behaviour and be careful with my code 🙂

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [February 15, 2022, 1:58pm UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/17 "2022-02-15T13:58:21Z")

</div>

maybe we can look at it this way: when the exponent is an integer, you’re just multiplying base to itself that many times, you’re not really doing (basic) arithmetic with two numbers, one of them is for counting purposes, so what type of integer the exponent is doesn’t matter at all.

---

<div class="post-metadata">

**Author:** ![Palli](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/palli/32/3380_2.png) [@Palli](https://discourse.julialang.org/u/Palli)\
**Post date:** [February 15, 2022, 11:11pm UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/18 "2022-02-15T23:11:42Z")

</div>

> [@Federico\_Marotta](#):
>
> I’m just saying that `Int8(2)^63` should give the same result, because one of the arguments of `^` , namely `63`, is of type `Int64` (at least on my system).

But that’s just a bad argument. Conceptually it’s the same as Int8(2) \* Int8(2) \* … \* Int8(2), 63 times and if you spell it out it would evaluate to Int8. Neither the former or latter type is ideal, so I disagree with “should”. And:

> [@Federico\_Marotta](#):
>
> On the other hand, `Int8(2)^Int8(63)` should give `0` .

Since you’re ok with modular arithmetic (many would want a BigInt result), it’s not too bad to get result only consistent with the first argument.

> [@Federico\_Marotta](#):
>
> I also think that my discussion does not involve `Floats` at all.

I had this in mind:

```julia
julia> typeof(Int8(2)^-63)
Float64

so simply promoting to the latter type will not do, in fact neither. That works because of some magic in Julia, I thought it actually equivalent to:

julia> typeof(Int8(2)^Int64(-63))
ERROR: DomainError with -63:
Cannot raise an integer x to a negative power -63.
Make x or -63 a float by adding a zero decimal (e.g., 2.0^-63 or 2^-63.0 instead of 2^-63), or write 1/x^63, float(x)^-63, x^float(-63) or (x//1)^-63
Stacktrace:
 [1] throw_domerr_powbysq(#unused#::Int8, p::Int64)
   @ Base ./intfuncs.jl:228
 [2] power_by_squaring(x_::Int8, p::Int64)
   @ Base ./intfuncs.jl:249
 [3] ^(x::Int8, p::Int64)
   @ Base ./intfuncs.jl:274
 [4] top-level scope
   @ REPL[46]:1

```

---

<div class="post-metadata">

**Author:** ![mikmoore](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mikmoore/32/31109_2.png) [@mikmoore](https://discourse.julialang.org/u/mikmoore)\
**Post date:** [February 16, 2022, 2:57am UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/19 "2022-02-16T02:57:50Z")

</div>

A primary motivator for promotion seems to be to “ensure” the output type is capable of representing “most” results. However, this isn’t really feasible for integers since even `Int128(2)^typemax(Int8)` will overflow. Since even a base of 2 can overflow the largest native type with the smallest integer type exponent, there’s really no correspondence between the exponent type and the safe width of the output.

Personally, I am happy with the behavior of inheriting the type from the base. And while I haven’t given it any thought before, needing to write literal powers `X^UInt8(3)` to avoid promotion seems annoying.

---

<div class="post-metadata">

**Author:** ![Federico\_Marotta](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/federico_marotta/32/33810_2.png) [@Federico\_Marotta](https://discourse.julialang.org/u/Federico_Marotta)\
**Post date:** [February 16, 2022, 9:28am UTC](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444/20 "2022-02-16T09:28:54Z")

</div>

I mean, the fact that `2^63` is the same as `2*2*...*2` is just because of the current implementation in Julia, not necessarily because of the _concept_ of exponentiation. Yes, one way to mathematically define `2^63` is `2*2*...*2`, but there are many other ways. It’s the same with addition: you can define `x+p` as `x+1+1+...+1` with `p` ones. A direct _implementation_ of this definition would just increment the binary value of `x` (`p` times), not caring at all about the type of `p`. With such implementation, `Int8(127)+1` would overflow; but with the current implementation of `+` in Julia, `Int8(127)+1` doesn’t overflow.

For sure there is a reason why addition is not implemented as repeated incrementation, and this reason may well be Speed. The same reason might underlie the choice of implementing `2^63` as `2*2*...*2`. However, I would rather have a small set of rules that are independent of the algorithmic implementation, than having to remember how each operation is implemented. In this case, after reading the documentation for the first time I thought I could apply the following rule: “When Julia does a mathematical operation, the type of the result is the common type between the arguments (except when division is involved, in which case it’s more complicated)”. So I was surprised when I found that this rule didn’t apply to powers between positive integers. I really don’t care about overflow, what I was after is just consistency.

* * *

> [@Palli](#):
>
> I had this in mind:
> 
> ```julia
> julia> typeof(Int8(2)^-63)
> Float64
> 
> ```

Ah, I see… So yes, you guys made good points that this behaviour is reasonable, and there are a lot of other corner cases like this that make the matter more complicated.

[Next page](https://discourse.julialang.org/t/why-doesnt-promote-its-arguments/76444.md?page=2)
