This is a bit-wrangling question: I would like to decompose a positive integer x into 2^p+m, for the highest p such that m \ge 0.
Expected output:
julia> for i in 1:7
println(i => decompose(i))
end
1 => (0, 0)
2 => (1, 0)
3 => (1, 1)
4 => (2, 0)
5 => (2, 1)
6 => (2, 2)
7 => (2, 3)
an example implementation, but it uses an internal function:
"""
Let `p` be the highest integer such that ``x = 2^p + m`` and `m ≥ 0`. Return `p, m`.
"""
function decompose(x::Integer)
@assert x > 0
p = Base.top_set_bit(x) - 1
p, x - (1 << p)
end
In what sense? This doesn’t work for BigInt (but does for Int128). I suppose you don’t care or need it.
EDIT: Not better, just as good, see my next answer:
julia> @btime decompose(UInt(1)) # same speed as for leading_zeros(1) which is basically just the one lzcnt instruction
2.407 ns (0 allocations: 0 bytes)
(0, 0x0000000000000000)
vs 2.973 ns for your, with:
function decompose2(x::UInt64)
# @assert x > 0
# top_set_bit(x::BitInteger) = 8sizeof(x) - leading_zeros(x)
p = 64 + leading_zeros(x) - 1
p, x - (1 << p)
end
Base.top_set_bit is there to get access to lzcnt assembly instruction as I suppose you know (it does seem to be used for BigInt too, a bit surprisingly).
help?> Base.top_set_bit
│ Warning
│
│ The following bindings may be internal; they may change or be removed in future versions:
│
│ • Base.top_set_bit
top_set_bit(x::Integer)::Integer ..
That’s strictly not true, since it only takes in Base.BitInteger. I suppose the docs could be changed to reflect that; or made more general to account for BigInt too. I’m guessing BitInteger doesn’t cover Int256 that might be in a package, so Integer intentional…
Note, I thought would not work for BigInt, since it uses sizeof (and it Core.sizeof), and would be nonsensical, but it seems taken care of:
Yours if fine, the min sometime beat mine when testing again but shouldn’t, and occasionally I get your exact min with mine too (my machine is a bit noisy, it seemed consistent when I first benchmarked), and if fully generic:
julia> @benchmark decompose(1)
BenchmarkTools.Trial: 10000 samples with 1000 evaluations per sample.
Range (min … max): 1.944 ns … 30.947 ns ┊ GC (min … max): 0.00% … 0.00%
Time (median): 2.629 ns ┊ GC (median): 0.00%
Emulating the assembly instruction in software would be slower… and I can’t see your source code can be much simpler; my @code_native decompose2(1) is much shorter, I worried a bit about for your, but I guess the fast path there fast enough…