Your terminology is mixing up some language-level features with implementation details, but I’ll cut to the chase. A GMP integer is implemented with an internal dynamic array, which allow cheap increases in value (internally resizing the array) in most cases by preallocating more memory than necessary, only doing the more expensive reallocation when the memory isn’t enough. The user can manually reallocate, but reallocations are typically handled automatically. Again, because the allocated size isn’t fixed, these allocations must be on the heap. A dynamic array is more directly implemented by Julia’s Vector, and the array’s size/length can easily be observed to not match its allocated size.
Julia’s Base treats Numbers like immutable objects even when they’re not, so BigInt API actually doesn’t let us leverage GMP’s dynamic reallocation. Julia internally does wrap some of GMP’s in-place arithmetic API:
julia> x = big(123); y = x; # same object
julia> using Base.GMP.MPZ: add!
julia> for i in 1:5
println(y, y.d) # value, dynamic array address
add!(y, y) # double value
end
123Ptr{UInt64} @0x0000022bf42a49d0
246Ptr{UInt64} @0x0000022bfd098f40
492Ptr{UInt64} @0x0000022bfd098f40
984Ptr{UInt64} @0x0000022bfd098f40
1968Ptr{UInt64} @0x0000022bfd098f40
julia> using BenchmarkTools
julia> @btime add!($y, $(big(typemax(Int128)))) # reallocated by GMP, not Julia
11.011 ns (0 allocations: 0 bytes)
1784866255233235935950497391318785927013203163
julia> y.d # address proves reallocation
Ptr{UInt64} @0x0000022b903eb890
julia> x === y # still the same object, all in-place mutations
true
But there’s no implementation of wider in-place operations that would involve conversions to BigFloat, e.g. sin.