Skip to content

Commit

Permalink
Merge pull request #24 from milankl/mk/round
Browse files Browse the repository at this point in the history
overhauled shave,halfshave,set_one and groom
  • Loading branch information
milankl authored Dec 9, 2021
2 parents 52fa87e + 33ee01d commit ba587d9
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 121 deletions.
11 changes: 7 additions & 4 deletions src/BitInformation.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
module BitInformation

export shave, set_one, groom, halfshave,
shave!, set_one!, groom!, halfshave!, round!

export bittranspose, bitbacktranspose,
shave, set_one, groom, halfshave, kouzround, round!,
xor_delta, unxor_delta, xor_delta!, unxor_delta!,
signed_exponent,
bitinformation, mutual_information, redundancy, bitpattern_entropy,
signed_exponent

export bitinformation, mutual_information, redundancy, bitpattern_entropy,
bitcount, bitcount_entropy, bitpaircount, bit_condprobability,
bit_condentropy

Expand All @@ -13,8 +16,8 @@ module BitInformation
include("which_uint.jl")
include("bitstring.jl")
include("bittranspose.jl")
include("shave_set_groom.jl")
include("round_nearest.jl")
include("shave_set_groom.jl")
include("xor_delta.jl")
include("signed_exponent.jl")
include("bit_information.jl")
Expand Down
15 changes: 12 additions & 3 deletions src/round_nearest.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

"""Shift integer to push the mantissa in the right position. Used to determine
round up or down in the tie case. `keepbits` is the number of mantissa bits to
be kept (i.e. not zero-ed) after rounding."""
Expand Down Expand Up @@ -33,6 +32,16 @@ function get_keep_mask( ::Type{T},
return unsigned(signed(~Base.significand_mask(T)) >> keepbits)
end

"""Returns a mask that's `1` for a given `mantissabit` and `0` else. Mantissa bits
are positive for the mantissa (`mantissabit = 1` is the first mantissa bit), `mantissa = 0`
is the last exponent bit, and negative for the other exponent bits."""
function get_bit_mask( ::Type{T},
mantissabit::Integer
) where {T<:Base.IEEEFloat}
# push the sign mask (1000....) in the right position
return Base.sign_mask(T) >> (Base.exponent_bits(T) + mantissabit)
end

"""IEEE's round to nearest tie to even for Float16/32/64."""
function Base.round(x::T, # Float to be rounded
ulp_half::UIntT, # obtain from get_ulp_half,
Expand Down Expand Up @@ -87,8 +96,8 @@ function Base.iseven(x::T,
mantissabit::Integer
) where {T<:Base.IEEEFloat}

mask = Base.sign_mask(T) >> (Base.exponent_bits(T) + mantissabit)
return 0x0 == reinterpret(typeof(mask),x) & mask
bitmask = get_bit_mask(T,mantissabit)
return 0x0 == reinterpret(typeof(bitmask),x) & bitmask
end

"""Checks a given `mantissabit` of `x` for oddness. 1=odd, 0=even. Mantissa bits
Expand Down
213 changes: 100 additions & 113 deletions src/shave_set_groom.jl
Original file line number Diff line number Diff line change
@@ -1,149 +1,136 @@
"""Creates a UInt32-mask for the trailing non-significant bits of a
Float32 number. `nsb` are the number of significant bits in the mantissa.
E.g. mask(3) returns `00000000000011111111111111111111`,
such that all but the first 3 significant bits can be masked."""
mask32(nsb::Integer) = UInt32(2^(23-nsb)-1)

"""Creates a UInt64-mask for the trailing non-significant bits of a
Float64 number. `nsb` are the number of significant bits in the mantissa."""
mask64(nsb::Integer) = UInt64(2^(52-nsb)-1)

halfshavemask32(nsb::Integer) = UInt32(2^(23-nsb-1))
halfshavemask64(nsb::Integer) = UInt64(2^(52-nsb-1))


"""Shave trailing bits of a Float32 number to zero.
Mask is UInt32 with 1 for the shaved bits, 0 for the retained bits."""
function shave(x::Float32,mask::UInt32)
ui = reinterpret(UInt32,x)
ui &= mask
return reinterpret(Float32,ui)
"""Bitshaving for floats. Sets trailing bits to 0 (round towards zero).
`keepmask` is an unsigned integer with bits being `1` for bits to be kept,
and `0` for those that are shaved off."""
function shave( x::T,
keepmask::UIntT
) where {T<:Base.IEEEFloat,UIntT<:Unsigned}
ui = reinterpret(UIntT,x)
ui &= keepmask # set trailing bits to zero
return reinterpret(T,ui)
end

"""Shave trailing bits of a Float32 number to zero.
Mask is UInt32 with 1 for the shaved bits, 0 for the retained bits."""
function shave(x::Float64,mask::UInt64)
ui = reinterpret(UInt64,x)
ui &= mask
return reinterpret(Float64,ui)
"""Halfshaving for floats. Replaces trailing bits with `1000...` a variant
of round nearest whereby the representable numbers are halfway between those
from shaving or IEEE's round nearest."""
function halfshave( x::T,
keepmask::UIntT,
bitmask::UIntT
) where {T<:Base.IEEEFloat,UIntT<:Unsigned}
ui = reinterpret(UIntT,x)
ui &= keepmask # set trailing bits to zero
ui |= bitmask # set first trailing bit to 1
return reinterpret(T,ui)
end

function halfshave(x::Float32,mask::UInt32,hsmask::UInt32)
ui = reinterpret(UInt32,x)
ui &= mask # set tail bits to zero
ui |= hsmask # set most significant tail bit to one
return reinterpret(Float32,ui)
"""Bitsetting for floats. Replace trailing bits with `1`s (round away from zero).
`setmask` is an unsigned integer with bits being `1` for those that are set to one
and `0` otherwise, such that the bits to keep are unaffected."""
function set_one( x::T,
setmask::UIntT
) where {T<:Base.IEEEFloat,UIntT<:Unsigned}
ui = reinterpret(UIntT,x)
ui |= setmask # set trailing bits to 1
return reinterpret(T,ui)
end

function halfshave(x::Float64,mask::UInt64,hsmask::UInt64)
ui = reinterpret(UInt64,x)
ui &= mask # set tail bits to zero
ui |= hsmask # set most significant tail bit to one
return reinterpret(Float64,ui)
"""Bitshaving of a float `x` given `keepbits` the number of mantissa bits to keep
after shaving."""
function shave(x::T,keepbits::Integer) where {T<:Base.IEEEFloat}
return shave(x,get_keep_mask(T,keepbits))
end

"""Shave trailing bits of a Float32 number to zero.
Providing `nsb` the number of retained significant bits, a mask is created
and applied."""
shave(x::Float32,nsb::Integer) = shave(x,~mask32(nsb))
shave(x::Float64,nsb::Integer) = shave(x,~mask64(nsb))

halfshave(x::Float32,nsb::Integer) = halfshave(x,~mask32(nsb),halfshavemask32(nsb))
halfshave(x::Float64,nsb::Integer) = halfshave(x,~mask64(nsb),halfshavemask64(nsb))

"""Shave trailing bits of a Float32 number to zero.
In case no `sb` argument is applied for `shave`, shave 16 bits, retain 7."""
shave(x::Float32) = shave(x,7)
shave(x::Float64) = shave(x,12)

halfshave(x::Float32) = halfshave(x,7)
halfshave(x::Float64) = halfshave(x,12)

"""Shave trailing bits of a Float32 array to zero.
Creates the shave-mask only once and applies it to every element in `X`."""
shave(X::AbstractArray{Float32},nsb::Integer) = shave.(X,~mask32(nsb))
shave(X::AbstractArray{Float64},nsb::Integer) = shave.(X,~mask64(nsb))

halfshave(X::AbstractArray{Float32},nsb::Integer) = halfshave.(X,~mask32(nsb),halfshavemask32(nsb))
halfshave(X::AbstractArray{Float64},nsb::Integer) = halfshave.(X,~mask64(nsb),halfshavemask64(nsb))

"""Set trailing bits of a Float32 number to one.
Provided a UInt32 mask with 1 for bits to be set to one, and 0 else."""
function set_one(x::Float32,mask::UInt32)
ui = reinterpret(UInt32,x)
ui |= mask
return reinterpret(Float32,ui)
"""Halfshaving of a float `x` given `keepbits` the number of mantissa bits to keep
after halfshaving."""
function halfshave(x::T,keepbits::Integer) where {T<:Base.IEEEFloat}
return halfshave(x,get_keep_mask(T,keepbits),get_bit_mask(T,keepbits+1))
end

function set_one(x::Float64,mask::UInt64)
ui = reinterpret(UInt64,x)
ui |= mask
return reinterpret(Float64,ui)
"""Bitsetting of a float `x` given `keepbits` the number of mantissa bits to keep
after setting."""
function set_one(x::T,keepbits::Integer) where {T<:Base.IEEEFloat}
return set_one(x,~get_keep_mask(T,keepbits))
end

"""Set trailing bits of Float32 number to one, given `nsb` number of significant
bits retained. A mask is created and applied."""
set_one(x::Float32,nsb::Integer) = set_one(x,mask32(nsb))
set_one(x::Float64,nsb::Integer) = set_one(x,mask64(nsb))
"""In-place version of `shave` for any array `X` with floats as elements."""
function shave!(X::AbstractArray{T}, # any array with element type T
keepbits::Integer # how many mantissa bits to keep
) where {T<:Base.IEEEFloat} # constrain element type to Float16/32/64

"""Set trailing bits of a Float32 number to one.
In case no `sb` argument is applied for `set_one`, set 16 bits, retain 7."""
set_one(x::Float32) = set_one(x,7)
set_one(x::Float64) = set_one(x,12)
keep_mask = get_keep_mask(T,keepbits) # mask to zero trailing mantissa bits

"""Set trailing bits of a Float32 number to one.
Creates the setting-mask only once and applies it to every element in `X`."""
set_one(X::AbstractArray{Float32},nsb::Integer) = set_one.(X,mask32(nsb))
set_one(X::AbstractArray{Float64},nsb::Integer) = set_one.(X,mask64(nsb))
@inbounds for i in eachindex(X) # apply rounding to each element
X[i] = shave(X[i],keep_mask)
end

"""Bit-grooming. Alternatingly apply bit-shaving and setting to a Float32 array."""
function groom(X::AbstractArray{Float32},nsb::Integer)
return X
end

Y = similar(X) # preallocate output of same size and type
mask1 = mask32(nsb) # mask for setting
mask0 = ~mask1 # mask for shaving
n = length(X)
"""In-place version of `halfshave` for any array `X` with floats as elements."""
function halfshave!(X::AbstractArray{T}, # any array with element type T
keepbits::Integer # how many mantissa bits to keep
) where {T<:Base.IEEEFloat} # constrain element type to Float16/32/64

keep_mask = get_keep_mask(T,keepbits) # mask to zero trailing mantissa bits
bit_mask = get_bit_mask(T,keepbits+1) # mask to set the first trailing bit to 1

@inbounds for i in 1:2:length(X)-1
Y[i] = shave(X[i],mask0) # every second element is shaved
Y[i+1] = set_one(X[i+1],mask1) # every other 2nd element is set
@inbounds for i in eachindex(X) # apply rounding to each element
X[i] = halfshave(X[i],keep_mask,bit_mask)
end

# for arrays of uneven length shave last element (as exempted from loop)
Y[end] = n % 2 == 1 ? shave(X[end],mask0) : Y[end]
return X
end

return Y
"""In-place version of `set_one` for any array `X` with floats as elements."""
function set_one!( X::AbstractArray{T}, # any array with element type T
keepbits::Integer # how many mantissa bits to keep
) where {T<:Base.IEEEFloat} # constrain element type to Float16/32/64

set_mask = ~get_keep_mask(T,keepbits) # mask to set trailing mantissa bits to 1

@inbounds for i in eachindex(X) # apply rounding to each element
X[i] = set_one(X[i],set_mask)
end

return X
end

function groom(X::AbstractArray{Float64},nsb::Integer)
"""Bitgrooming for a float arrays `X` keeping `keepbits` mantissa bits. In-place version
that shaves/sets the elements of `X` alternatingly."""
function groom!(X::AbstractArray{T}, # any array with element type T
keepbits::Integer # how many mantissa bits to keep
) where {T<:Base.IEEEFloat} # constrain element type to Float16/32/64

Y = similar(X) # preallocate output of same size and type
mask1 = mask64(nsb) # mask for setting
mask0 = ~mask1 # mask for shaving
keep_mask = get_keep_mask(T,keepbits) # mask to zero trailing mantissa bits
set_mask = ~keep_mask # mask to set trailing mantissa bits to 1

n = length(X)

@inbounds for i in 1:2:n-1
Y[i] = shave(X[i],mask0) # every second element is shaved
Y[i+1] = set_one(X[i+1],mask1) # every other 2nd element is set
X[i] = shave(X[i],keep_mask) # every second element is shaved
X[i+1] = set_one(X[i+1],set_mask) # every other 2nd element is set
end

# for arrays of uneven length shave last element (as exempted from loop)
Y[end] = n % 2 == 1 ? shave(X[end],mask0) : Y[end]
@inbounds X[end] = n % 2 == 1 ? shave(X[end],keep_mask) : X[end]

return Y
return X
end

kouzround(x::Union{Float32,Float64},nsb::Integer) = shave(2x-shave(x,nsb),nsb)

function kouzround(x::AbstractArray{Float32},nsb::Integer)
y = similar(x)
mask = ~mask32(nsb)
for i in eachindex(x)
y[i] = shave(2x[i]-shave(x[i],mask),mask)
# Shave, halfshave, set_one, groom which returns a rounded copy of array `X` instead of
# chaning its elements in-place.
for func in (:shave,:halfshave,:set_one,:groom)
func! = Symbol(func,:!)
@eval begin
function $func( X::AbstractArray{T}, # any array with element type T
keepbits::Integer # how many mantissa bits to keep
) where {T<:Base.IEEEFloat} # constrain element type to Float16/32/64

Xcopy = copy(X) # copy to avoid in-place changes of X
$func!(Xcopy,keepbits) # in-place on X's copy
return Xcopy
end
end
return y
end

"""Number of significant bits `nsb` given the number of significant digits `nsd`."""
nsb(nsd::Integer) = Integer(ceil(log(10)/log(2)*nsd))
# """Number of significant bits `nsb` given the number of significant digits `nsd`."""
# nsb(nsd::Integer) = Integer(ceil(log(10)/log(2)*nsd))
2 changes: 1 addition & 1 deletion test/round_nearest.jl
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ end
for k in 0:20
A = rand(T,200,300)
Ar = round(A,k)
Ar2 = round(A,k)
Ar2 = round(Ar,k)
@test Ar == Ar2
end
end
Expand Down
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ import StatsBase.entropy
import Random

include("round_nearest.jl")
include("shave_set_groom.jl")
include("xor_transpose.jl")
include("information.jl")
Loading

0 comments on commit ba587d9

Please sign in to comment.