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.