Here’s an example of a wrapper type that will automatically decide whether to convert to `BigInt`

before multiplication (enabling power by squaring).

It’s significantly faster than `BigInt`

for the specific case `10^10`

. (This calculation takes 20 nanoseconds on my system, which is amazing given that I didn’t modify the `power_by_squaring`

function at all, and the multiplication code is not even type stable.)

```
struct LazyBigInt{T} <: Integer
x::T
end
# Conservative bound on when to move from Int64 to higher precision
const cutoff64 = 0.5 * Float64(typemax(Int))
function Base.:*(a::LazyBigInt{Int}, b::LazyBigInt{Int})
rf = Float64(a.x) * Float64(b.x)
-cutoff64 <= rf <= cutoff64 ? LazyBigInt(a.x * b.x) : big(a.x) * b.x
end
Base.promote_type(::Type{<:LazyBigInt}, ::Type{BigInt}) = BigInt
Base.promote_type(::Type{<:LazyBigInt{T}}, ::Type{T}) where {T} = LazyBigInt{T}
Base.BigInt(x::LazyBigInt) = BigInt(x.x)
@assert LazyBigInt(10)^10 == 10000000000
@assert LazyBigInt(10)^40 == 10000000000000000000000000000000000000000
using BenchmarkTools
@benchmark $(BigInt(10))^10
@benchmark $(LazyBigInt(10))^10
```

Of course, this is just an example. It would take some effort to design proper heuristics for when to switch to higher precision for each type of operation.