A question regarding BigFloat

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.

1 Like