diff --git a/src/methods.jl b/src/methods.jl index 66e7446..f8fa201 100644 --- a/src/methods.jl +++ b/src/methods.jl @@ -124,17 +124,17 @@ Base.typemax(::Type{T}) where T<:EmulatedInteger = reinterpret(T, maxvalue(T)) # Anchor the multiplicative identity on `oneunit` rather than `one`. Both break the `one`/`oneunit` mutual recursion, but `oneunit` has far fewer precompiled callers in Base's numeric code, so it invalidates a much smaller backedge set. Base.oneunit(::Type{T}) where T<:EmulatedInteger = reinterpret(T, T |> storagetypeof |> one) -# Storage has `wastedbits(T)` extra zero (unsigned) or sign-extension (signed) bits at the top; subtract them to get the logical leading-zero count. -Base.leading_zeros(x::T) where T<:EmulatedInteger = (leading_zeros(x[]) - wastedbits(T)) % Int +# Filling low bits caps the zero-input count at the logical width without a branch. +Base.leading_zeros(x::T) where T<:EmulatedSigned = leading_zeros(x[] << wastedbits(T) | ~zero(x[]) >>> bits(T)) % Int +# Unsigned storage has exactly wastedbits(T) extra leading zeros. +Base.leading_zeros(x::T) where T<:EmulatedUnsigned = (leading_zeros(x[]) - wastedbits(T)) % Int +Base.leading_ones(x::T) where T<:EmulatedInteger = leading_ones(x[] << wastedbits(T)) % Int # Mask off the wasted high bits via `zext`, then count ones. Unsigned `zext` is a no-op; signed `zext` masks the sign-extension away, so a single uniform formula serves both. Base.count_ones(x::EmulatedInteger) = count_ones(zext(x)) # Set the wasted high bits to 1 with an OR, then count zeros directly. Mirrors `count_ones`/`zext` but saves a subtraction; the mask folds to a literal (and is `0` for `bits == storagebits`, making the OR a no-op). Base.count_zeros(x::T) where T<:EmulatedInteger = count_zeros(x[] | (~storagetypeof(T)(0) << bits(T))) -# Shift the wasted high bits out, then count leading ones in the storage type. Works for both signednesses because after the shift, the bits that count are the same as the logical high bits. -Base.leading_ones(x::T) where T<:EmulatedInteger = leading_ones(x[] << wastedbits(T)) % Int - Base.rem(x::EmulatedInteger, Target::Base.BitIntegerType) = x[] % Target Base.:/(x::T, y::T) where T<:EmulatedInteger = x[] / y[] # Should result in Float64 diff --git a/test/runtests.jl b/test/runtests.jl index 1651109..048bcfc 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -477,6 +477,32 @@ end # Bit counting # ============================================================================ +@testset "leading_zeros / leading_ones" begin + @emulate Int1 UInt1 Int3 UInt3 Int3_128 UInt3_128 Int129 UInt129 + + @test leading_zeros(typemin(Int129)) === 0 + for Source in (Int1, UInt1, Int3, UInt3, Int3_128, UInt3_128) + for value in Int(typemin(Source)):Int(typemax(Source)) + x = Source(value) + expected = length(collect(Iterators.takewhile(==('0'), bitstring(x)))) + @test (@inferred leading_zeros(x)) === expected + expected_ones = length(collect(Iterators.takewhile(==('1'), bitstring(x)))) + @test (@inferred leading_ones(x)) === expected_ones + end + end + for Source in (Int129, UInt129) + @test (@inferred leading_zeros(zero(Source))) === 129 + @test (@inferred leading_ones(zero(Source))) === 0 + @test (@inferred leading_ones(~zero(Source))) === 129 + @test leading_zeros(typemax(Source)) === (Source <: Signed ? 1 : 0) + for shift in 0:127 + @test (@inferred leading_zeros(Source(big(1) << shift))) === 128 - shift + @test (@inferred leading_ones(~Source(big(1) << shift))) === 128 - shift + end + end + @test leading_zeros(Int129(-1)) === 0 +end + @testset "count_ones / count_zeros" begin @test UInt3(0b101) |> count_ones === 2 @test UInt3(0b101) |> count_zeros === 1