Skip to content

Commit 708d1bd

Browse files
LilithHafnerLilith Hafnermcabbott
authored
Add internal top_set_bit function (#47523)
* add top_set_bit Co-authored-by: Lilith Hafner <[email protected]> Co-authored-by: Michael Abbott <[email protected]>
1 parent f6b5157 commit 708d1bd

File tree

10 files changed

+85
-21
lines changed

10 files changed

+85
-21
lines changed

base/abstractdict.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ function convert(::Type{T}, x::AbstractDict) where T<:AbstractDict
575575
end
576576

577577
# hashing objects by identity
578-
_tablesz(x::T) where T <: Integer = x < 16 ? T(16) : one(T)<<((sizeof(T)<<3)-leading_zeros(x-one(T)))
578+
_tablesz(x::T) where T <: Integer = x < 16 ? T(16) : one(T)<<(top_set_bit(x-one(T)))
579579

580580
TP{K,V} = Union{Type{Tuple{K,V}},Type{Pair{K,V}}}
581581

base/bitarray.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,12 +1545,12 @@ function unsafe_bitfindprev(Bc::Vector{UInt64}, start::Int)
15451545

15461546
@inbounds begin
15471547
if Bc[chunk_start] & mask != 0
1548-
return (chunk_start-1) << 6 + (64 - leading_zeros(Bc[chunk_start] & mask))
1548+
return (chunk_start-1) << 6 + (top_set_bit(Bc[chunk_start] & mask))
15491549
end
15501550

15511551
for i = (chunk_start-1):-1:1
15521552
if Bc[i] != 0
1553-
return (i-1) << 6 + (64 - leading_zeros(Bc[i]))
1553+
return (i-1) << 6 + (top_set_bit(Bc[i]))
15541554
end
15551555
end
15561556
end

base/float.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ end
221221

222222
function Float32(x::UInt128)
223223
x == 0 && return 0f0
224-
n = 128-leading_zeros(x) # ndigits0z(x,2)
224+
n = top_set_bit(x) # ndigits0z(x,2)
225225
if n <= 24
226226
y = ((x % UInt32) << (24-n)) & 0x007f_ffff
227227
else
@@ -237,7 +237,7 @@ function Float32(x::Int128)
237237
x == 0 && return 0f0
238238
s = ((x >>> 96) % UInt32) & 0x8000_0000 # sign bit
239239
x = abs(x) % UInt128
240-
n = 128-leading_zeros(x) # ndigits0z(x,2)
240+
n = top_set_bit(x) # ndigits0z(x,2)
241241
if n <= 24
242242
y = ((x % UInt32) << (24-n)) & 0x007f_ffff
243243
else

base/gmp.jl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import .Base: *, +, -, /, <, <<, >>, >>>, <=, ==, >, >=, ^, (~), (&), (|), xor,
1010
trailing_zeros, trailing_ones, count_ones, count_zeros, tryparse_internal,
1111
bin, oct, dec, hex, isequal, invmod, _prevpow2, _nextpow2, ndigits0zpb,
1212
widen, signed, unsafe_trunc, trunc, iszero, isone, big, flipsign, signbit,
13-
sign, hastypemax, isodd, iseven, digits!, hash, hash_integer
13+
sign, hastypemax, isodd, iseven, digits!, hash, hash_integer, top_set_bit
1414

1515
if Clong == Int32
1616
const ClongMax = Union{Int8, Int16, Int32}
@@ -396,7 +396,7 @@ function Float64(x::BigInt, ::RoundingMode{:Nearest})
396396
z = Float64((unsafe_load(x.d, 2) % UInt64) << BITS_PER_LIMB + unsafe_load(x.d))
397397
else
398398
y1 = unsafe_load(x.d, xsize) % UInt64
399-
n = 64 - leading_zeros(y1)
399+
n = top_set_bit(y1)
400400
# load first 54(1 + 52 bits of fraction + 1 for rounding)
401401
y = y1 >> (n - (precision(Float64)+1))
402402
if Limb == UInt64
@@ -586,6 +586,12 @@ Number of ones in the binary representation of abs(x).
586586
"""
587587
count_ones_abs(x::BigInt) = iszero(x) ? 0 : MPZ.mpn_popcount(x)
588588

589+
function top_set_bit(x::BigInt)
590+
x < 0 && throw(DomainError(x, "top_set_bit only supports negative arguments when they have type BitSigned."))
591+
x == 0 && return 0
592+
Int(ccall((:__gmpz_sizeinbase, :libgmp), Csize_t, (Base.GMP.MPZ.mpz_t, Cint), x, 2))
593+
end
594+
589595
divrem(x::BigInt, y::BigInt) = MPZ.tdiv_qr(x, y)
590596
divrem(x::BigInt, y::Integer) = MPZ.tdiv_qr(x, big(y))
591597

base/int.jl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,31 @@ julia> trailing_ones(3)
482482
"""
483483
trailing_ones(x::Integer) = trailing_zeros(~x)
484484

485+
"""
486+
top_set_bit(x::Integer) -> Integer
487+
488+
The number of bits in `x`'s binary representation, excluding leading zeros.
489+
490+
Equivalently, the position of the most significant set bit in `x`'s binary
491+
representation, measured from the least significant side.
492+
493+
Negative `x` are only supported when `x::BitSigned`.
494+
495+
See also: [`ndigits0z`](@ref), [`ndigits`](@ref).
496+
497+
# Examples
498+
```jldoctest
499+
julia> top_set_bit(4)
500+
3
501+
502+
julia> top_set_bit(0)
503+
0
504+
505+
julia> top_set_bit(-1)
506+
64
507+
"""
508+
top_set_bit(x::BitInteger) = 8sizeof(x) - leading_zeros(x)
509+
485510
## integer comparisons ##
486511

487512
(< )(x::T, y::T) where {T<:BitUnsigned} = ult_int(x, y)

base/intfuncs.jl

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -391,9 +391,9 @@ end
391391
# optimization: promote the modulus m to BigInt only once (cf. widemul in generic powermod above)
392392
powermod(x::Integer, p::Integer, m::Union{Int128,UInt128}) = oftype(m, powermod(x, p, big(m)))
393393

394-
_nextpow2(x::Unsigned) = oneunit(x)<<((sizeof(x)<<3)-leading_zeros(x-oneunit(x)))
394+
_nextpow2(x::Unsigned) = oneunit(x)<<(top_set_bit(x-oneunit(x)))
395395
_nextpow2(x::Integer) = reinterpret(typeof(x),x < 0 ? -_nextpow2(unsigned(-x)) : _nextpow2(unsigned(x)))
396-
_prevpow2(x::Unsigned) = one(x) << unsigned((sizeof(x)<<3)-leading_zeros(x)-1)
396+
_prevpow2(x::Unsigned) = one(x) << unsigned(top_set_bit(x)-1)
397397
_prevpow2(x::Integer) = reinterpret(typeof(x),x < 0 ? -_prevpow2(unsigned(-x)) : _prevpow2(unsigned(x)))
398398

399399
"""
@@ -526,7 +526,7 @@ const powers_of_ten = [
526526
0x002386f26fc10000, 0x016345785d8a0000, 0x0de0b6b3a7640000, 0x8ac7230489e80000,
527527
]
528528
function bit_ndigits0z(x::Base.BitUnsigned64)
529-
lz = (sizeof(x)<<3)-leading_zeros(x)
529+
lz = top_set_bit(x)
530530
nd = (1233*lz)>>12+1
531531
nd -= x < powers_of_ten[nd]
532532
end
@@ -571,12 +571,12 @@ function ndigits0zpb(x::Integer, b::Integer)
571571
x = abs(x)
572572
if x isa Base.BitInteger
573573
x = unsigned(x)::Unsigned
574-
b == 2 && return sizeof(x)<<3 - leading_zeros(x)
575-
b == 8 && return (sizeof(x)<<3 - leading_zeros(x) + 2) ÷ 3
574+
b == 2 && return top_set_bit(x)
575+
b == 8 && return (top_set_bit(x) + 2) ÷ 3
576576
b == 16 && return sizeof(x)<<1 - leading_zeros(x)>>2
577577
b == 10 && return bit_ndigits0z(x)
578578
if ispow2(b)
579-
dv, rm = divrem(sizeof(x)<<3 - leading_zeros(x), trailing_zeros(b))
579+
dv, rm = divrem(top_set_bit(x), trailing_zeros(b))
580580
return iszero(rm) ? dv : dv + 1
581581
end
582582
end
@@ -638,6 +638,9 @@ function ndigits0z(x::Integer, b::Integer)
638638
end
639639
end
640640

641+
# Extends the definition in base/int.jl
642+
top_set_bit(x::Integer) = ceil(Integer, log2(x + oneunit(x)))
643+
641644
"""
642645
ndigits(n::Integer; base::Integer=10, pad::Integer=1)
643646
@@ -673,7 +676,7 @@ ndigits(x::Integer; base::Integer=10, pad::Integer=1) = max(pad, ndigits0z(x, ba
673676
## integer to string functions ##
674677

675678
function bin(x::Unsigned, pad::Int, neg::Bool)
676-
m = 8 * sizeof(x) - leading_zeros(x)
679+
m = top_set_bit(x)
677680
n = neg + max(pad, m)
678681
a = StringVector(n)
679682
# for i in 0x0:UInt(n-1) # automatic vectorization produces redundant codes
@@ -700,7 +703,7 @@ function bin(x::Unsigned, pad::Int, neg::Bool)
700703
end
701704

702705
function oct(x::Unsigned, pad::Int, neg::Bool)
703-
m = div(8 * sizeof(x) - leading_zeros(x) + 2, 3)
706+
m = div(top_set_bit(x) + 2, 3)
704707
n = neg + max(pad, m)
705708
a = StringVector(n)
706709
i = n

base/sort.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module Sort
55
using Base.Order
66

77
using Base: copymutable, midpoint, require_one_based_indexing, uinttype,
8-
sub_with_overflow, add_with_overflow, OneTo, BitSigned, BitIntegerType
8+
sub_with_overflow, add_with_overflow, OneTo, BitSigned, BitIntegerType, top_set_bit
99

1010
import Base:
1111
sort,

base/special/rem_pio2.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ function fromfraction(f::Int128)
109109
# 1. get leading term truncated to 26 bits
110110
s = ((f < 0) % UInt64) << 63 # sign bit
111111
x = abs(f) % UInt128 # magnitude
112-
n1 = 128-leading_zeros(x) # ndigits0z(x,2)
112+
n1 = Base.top_set_bit(x) # ndigits0z(x,2)
113113
m1 = ((x >> (n1-26)) % UInt64) << 27
114114
d1 = ((n1-128+1021) % UInt64) << 52
115115
z1 = reinterpret(Float64, s | (d1 + m1))
@@ -119,7 +119,7 @@ function fromfraction(f::Int128)
119119
if x2 == 0
120120
return (z1, 0.0)
121121
end
122-
n2 = 128-leading_zeros(x2)
122+
n2 = Base.top_set_bit(x2)
123123
m2 = (x2 >> (n2-53)) % UInt64
124124
d2 = ((n2-128+1021) % UInt64) << 52
125125
z2 = reinterpret(Float64, s | (d2 + m2))

base/twiceprecision.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ nbitslen(::Type{T}, len, offset) where {T<:IEEEFloat} =
254254
min(cld(precision(T), 2), nbitslen(len, offset))
255255
# The +1 here is for safety, because the precision of the significand
256256
# is 1 bit higher than the number that are explicitly stored.
257-
nbitslen(len, offset) = len < 2 ? 0 : ceil(Int, log2(max(offset-1, len-offset))) + 1
257+
nbitslen(len, offset) = len < 2 ? 0 : top_set_bit(max(offset-1, len-offset) - 1) + 1
258258

259259
eltype(::Type{TwicePrecision{T}}) where {T} = T
260260

@@ -310,7 +310,7 @@ function *(x::TwicePrecision, v::Number)
310310
end
311311
function *(x::TwicePrecision{<:IEEEFloat}, v::Integer)
312312
v == 0 && return TwicePrecision(x.hi*v, x.lo*v)
313-
nb = ceil(Int, log2(abs(v)))
313+
nb = top_set_bit(abs(v)-1)
314314
u = truncbits(x.hi, nb)
315315
TwicePrecision(canonicalize2(u*v, ((x.hi-u) + x.lo)*v)...)
316316
end

test/intfuncs.jl

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,12 +441,42 @@ end
441441
end
442442
end
443443

444-
@testset "leading_ones and count_zeros" begin
444+
@testset "leading_ones, count_zeros, etc." begin
445445
@test leading_ones(UInt32(Int64(2) ^ 32 - 2)) == 31
446446
@test leading_ones(1) == 0
447447
@test leading_zeros(Int32(1)) == 31
448448
@test leading_zeros(UInt32(Int64(2) ^ 32 - 2)) == 0
449449

450+
@test Base.top_set_bit(3) == 2
451+
@test Base.top_set_bit(-Int64(17)) == 64
452+
@test Base.top_set_bit(big(15)) != Base.top_set_bit(big(16)) == Base.top_set_bit(big(17)) == 5
453+
@test_throws DomainError Base.top_set_bit(big(-17))
454+
455+
struct MyInt <: Integer
456+
x::Int
457+
end
458+
MyInt(x::MyInt) = x
459+
Base.:+(a::MyInt, b::MyInt) = a.x + b.x
460+
461+
for n in 0:100
462+
x = ceil(Int, log2(n + 1))
463+
@test x == Base.top_set_bit(Int128(n)) == Base.top_set_bit(unsigned(Int128(n)))
464+
@test x == Base.top_set_bit(Int32(n)) == Base.top_set_bit(unsigned(Int64(n)))
465+
@test x == Base.top_set_bit(Int8(n)) == Base.top_set_bit(unsigned(Int8(n)))
466+
@test x == Base.top_set_bit(big(n)) # BigInt fallback
467+
@test x == Base.top_set_bit(MyInt(n)) # generic fallback
468+
end
469+
470+
for n in -10:-1
471+
@test 128 == Base.top_set_bit(Int128(n)) == Base.top_set_bit(unsigned(Int128(n)))
472+
@test 32 == Base.top_set_bit(Int32(n)) == Base.top_set_bit(unsigned(Int32(n)))
473+
@test 8 == Base.top_set_bit(Int8(n)) == Base.top_set_bit(unsigned(Int8(n)))
474+
@test_throws DomainError Base.top_set_bit(big(n))
475+
# This error message should never be exposed to the end user anyway.
476+
err = n == -1 ? InexactError : DomainError
477+
@test_throws err Base.top_set_bit(MyInt(n))
478+
end
479+
450480
@test count_zeros(Int64(1)) == 63
451481
end
452482

0 commit comments

Comments
 (0)