Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/UnitTest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ jobs:
os: [ubuntu-latest, windows-latest, macos-15-intel]
julia-arch: [x64]
include:
- os: ubuntu-latest # the minimum supported version
julia-version: '1.6'
julia-arch: x64
- os: ubuntu-latest # only test one 32-bit job
julia-version: '1'
julia-arch: x86
Expand Down
6 changes: 3 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "FixedPointNumbers"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.9.2"
version = "0.10.0"

[deps]
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Expand All @@ -15,11 +15,11 @@ FixedPointNumbersStatisticsExt = "Statistics"
[compat]
Aqua = "0.8"
Documenter = "0.27, 1"
Random = "<0.0.1, 1"
Random = "1"
StableRNGs = "1"
Statistics = "1"
Test = "1"
julia = "1"
julia = "1.6"

[extras]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
Expand Down
37 changes: 6 additions & 31 deletions src/FixedPointNumbers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Base: ==, <, <=, -, +, *, /, ~, isapprox,
zero, oneunit, one, typemin, typemax, floatmin, floatmax, eps, reinterpret,
big, rationalize, float, trunc, round, floor, ceil, bswap, clamp,
div, fld, cld, rem, mod, mod1, fld1, min, max, minmax,
signed, unsigned, sign, copysign, flipsign, signbit,
sign, copysign, flipsign, signbit,
length, widen, widemul

import Random: Random, AbstractRNG, SamplerType, rand!
Expand Down Expand Up @@ -137,8 +137,7 @@ zero(::Type{X}) where {X <: FixedPoint} = X(zero(rawtype(X)), 0)
oneunit(::Type{X}) where {X <: FixedPoint} = X(rawone(X), 0)
one(::Type{X}) where {X <: FixedPoint} = oneunit(X)

# for Julia v1.0, which does not fold `div_float` before inlining
inv_rawone(x) = (@generated) ? (y = 1.0 / rawone(x); :($y)) : 1.0 / rawone(x)
inv_rawone(x) = 1.0 / rawone(x)

# traits
eps(::Type{X}) where {X <: FixedPoint} = X(oneunit(rawtype(X)), 0)
Expand Down Expand Up @@ -357,36 +356,24 @@ bitstring(x::FixedPoint) = bitstring(x.i)

bswap(x::X) where {X <: FixedPoint} = sizeof(X) == 1 ? x : X(bswap(x.i), 0)

# At least on Julia v1.5.0 or earlier, the following specialization helps the
# SIMD vectorization. (cf. PR #194)
# Julia 1.10 and earlier vectorize the loops over `clamp` only with this specialization. (cf. PR #194)
clamp(x::X, lo::X, hi::X) where {X <: FixedPoint} = X(clamp(x.i, lo.i, hi.i), 0)

clamp(x, ::Type{X}) where {X <: FixedPoint} = clamp(x, typemin(X), typemax(X)) % X

# Since `FixedPoint` is not an integer type, it is not clear in what type
# `signed` and `unsigned` for `FixedPoint` should return values. They should
# currently throw errors in case we support "unsigned Fixed" or "signed Normed"
# in the future. The following "incomplete" code is necessary for Julia v1.0
# etc. to prevent accidental conversion to an integer type.
signed(x::X) where {X <: FixedPoint} = signed(X)(signed(x.i), 0)
unsigned(x::X) where {X <: FixedPoint} = unsigned(X)(unsigned(x.i), 0)

function sign(x::X) where {T, X <: FixedPoint{T}}
T <: Unsigned && return iszero(x) ? x : oneunit(X)
# `Q0f7`, etc., result in an error for `oneunit(X)`.
x <= zero(X) && return x === zero(X) ? x : X(-1)
return oneunit(X)
end

function copysign(x::X, y::Real) where {T, X <: FixedPoint{T}}
T <: Signed ? X(copysign(x.i, y), 0) : throw_not_a_signed_number_error(x)
end
function flipsign(x::X, y::Real) where {T, X <: FixedPoint{T}}
T <: Signed ? X(flipsign(x.i, y), 0) : throw_not_a_signed_number_error(x)
end
if copysign(-1, 0x1) !== 1 # for Julia v1.0 and v1.1 (julia #30748)
copysign(x::X, y::Unsigned) where {T, X <: FixedPoint{T}} = copysign(x, signed(y))
flipsign(x::X, y::Unsigned) where {T, X <: FixedPoint{T}} = flipsign(x, signed(y))
end
@noinline function throw_not_a_signed_number_error(x)
throw(ArgumentError("$x is not a signed number."))
end
Expand Down Expand Up @@ -515,16 +502,6 @@ function show(io::IO, x::FixedPoint{T,f}) where {T,f}
return nothing
end

if VERSION < v"1.6.0-DEV.356" # JuliaLang/julia#36107
function Base.showarg(io::IO, a::Array{X}, toplevel) where {X<:FixedPoint}
toplevel || print(io, "::")
print(io, "Array{")
showtype(io, X)
print(io, ",$(ndims(a))}")
toplevel && hasalias(X) && print(io, " with eltype ", X)
end
end

include("fixed.jl")
include("normed.jl")
include("deprecations.jl")
Expand Down Expand Up @@ -666,9 +643,7 @@ if !isdefined(Base, :get_extension)
include("../ext/FixedPointNumbersStatisticsExt.jl")
end

if VERSION >= v"1.1" # work around https://github.com/JuliaLang/julia/issues/34121
include("precompile.jl")
_precompile_()
end
include("precompile.jl")
_precompile_()

end # module
32 changes: 16 additions & 16 deletions src/fixed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ fracmask(x::Fixed{T,f}) where {T, f} = ~intmask(x) # Signed

# constructor-style conversions
function _convert(::Type{F}, x::Fixed{T2,f2}) where {T, T2, f, f2, F <: Fixed{T,f}}
y = round(@exp2(f-f2) * reinterpret(x))
y = round(exp2(f-f2) * reinterpret(x))
(typemin(T) <= y) & (y <= typemax(T)) || throw_converterror(F, x)
reinterpret(F, _unsafe_trunc(T, y))
end
Expand All @@ -65,10 +65,10 @@ end

function _convert(::Type{F}, x::AbstractFloat) where {T, f, F <: Fixed{T,f}}
bigx = big(x)
bmin = BigFloat(typemin(F)) - @exp2(-f-1)
bmax = BigFloat(typemax(F)) + @exp2(-f-1)
bmin = BigFloat(typemin(F)) - exp2(-f-1)
bmax = BigFloat(typemax(F)) + exp2(-f-1)
if bmin <= bigx < bmax
reinterpret(F, round(T, bigx * @exp2(f)))
reinterpret(F, round(T, bigx * exp2(f)))
else
throw_converterror(F, x)
end
Expand All @@ -78,8 +78,8 @@ _convert(::Type{F}, x::Float16) where {T, f, F <: Fixed{T,f}} = F(Float32(x))

function _convert(::Type{F}, x::Union{Float32, Float64}) where {T, f, F <: Fixed{T,f}}
Tf = typeof(x)
if Tf(typemin(F) - @exp2(-f-1)) <= x < Tf(typemax(F) + @exp2(-f-1))
reinterpret(F, round(T, x * @exp2(f)))
if Tf(typemin(F) - exp2(-f-1)) <= x < Tf(typemax(F) + exp2(-f-1))
reinterpret(F, round(T, x * exp2(f)))
else
throw_converterror(F, x)
end
Expand All @@ -89,7 +89,7 @@ function _convert(::Type{F}, x::Rational) where {T, f, F <: Fixed{T,f}}
xmin = widemul(denominator(x), widen1(T)(typemin(T)) << 0x1 - 0x1)
xmax = widemul(denominator(x), oneunit(widen1(T)) << bitwidth(T) - 0x1)
if xmin <= (widen1(numerator(x)) << UInt8(f + 1)) < xmax
reinterpret(F, round(T, convert(floattype(T), x) * @exp2(f)))
reinterpret(F, round(T, convert(floattype(T), x) * exp2(f)))
else
throw_converterror(F, x)
end
Expand All @@ -98,7 +98,7 @@ end
_rem(x::F, ::Type{F}) where {F <: Fixed} = x
function _rem(x::Fixed, ::Type{F}) where {T, f, F <: Fixed{T,f}}
f2 = nbitsfrac(typeof(x))
y = round(@exp2(f - f2) * reinterpret(x))
y = round(exp2(f - f2) * reinterpret(x))
reinterpret(F, _unsafe_trunc(T, y))
end
_rem(x::Integer, ::Type{F}) where {T, f, F <: Fixed{T,f}} = F(_unsafe_trunc(T, x) << f, 0)
Expand All @@ -110,18 +110,18 @@ function _rem(x::Real, ::Type{F}) where {T, f, F <: Fixed{T,f}}
Ti = promote_type(Int64, T)
end
Tf = floattype(F)
y = _unsafe_trunc(Ti, round(x * Tf(@exp2(f))))
y = _unsafe_trunc(Ti, round(x * Tf(exp2(f))))
reinterpret(F, _unsafe_trunc(T, y))
end
function _rem(x::BigFloat, ::Type{F}) where {T, f, F <: Fixed{T,f}}
isfinite(x) || return zero(F)
reinterpret(F, _unsafe_trunc(T, round(x * @exp2(f))))
reinterpret(F, _unsafe_trunc(T, round(x * exp2(f))))
end

(::Type{Tf})(x::Fixed{T,f}) where {Tf <: AbstractFloat, T, f} = Tf(Tf(x.i) * Tf(@exp2(-f)))
(::Type{Tf})(x::Fixed{T,f}) where {Tf <: AbstractFloat, T, f} = Tf(Tf(x.i) * Tf(exp2(-f)))
Base.Float16(x::Fixed{T,f}) where {T, f} = Float16(Float32(x))
Base.Float32(x::Fixed{T,f}) where {T, f} = Float32(x.i) * Float32(@exp2(-f))
Base.Float64(x::Fixed{T,f}) where {T, f} = Float64(x.i) * @exp2(-f)
Base.Float32(x::Fixed{T,f}) where {T, f} = Float32(x.i) * Float32(exp2(-f))
Base.Float64(x::Fixed{T,f}) where {T, f} = Float64(x.i) * exp2(-f)

function Base.Rational(x::Fixed{T,f}) where {T, f}
f < bitwidth(T)-1 ? x.i//rawone(x) : x.i//(one(widen1(T))<<f)
Expand Down Expand Up @@ -205,10 +205,10 @@ function _round(x::Fixed{T,f}) where {T, f}
upper = intmask(x) >>> 0x1
lower = intmask(x) >> 0x1
if f == bitwidth(T) - 1
x.i > upper && throw_converterror(Fixed{T,f}, @exp2(bitwidth(T)-f-1))
x.i > upper && throw_converterror(Fixed{T,f}, exp2(bitwidth(T)-f-1))
return x.i < lower ? typemin(x) : zero(x)
end
x.i >= upper && throw_converterror(Fixed{T,f}, @exp2(bitwidth(T)-f-1))
x.i >= upper && throw_converterror(Fixed{T,f}, exp2(bitwidth(T)-f-1))
y = oneunit(T) << UInt8(f - 1) + x.i
m = oneunit(T) << UInt8(f + 1) - oneunit(T)
z = y & intmask(x)
Expand All @@ -223,7 +223,7 @@ function _round_digits(x::F, r::RoundingMode, d::Int) where {f, F <: Fixed{Int8,
else
xr = r isa RoundingMode{:Up} ? (xd + fracmask(x)) >> uf : div_2f(xd, Val(Int(f)))
if d < 3 && xr == (Int16(d === 1 ? 10 : 100) << (0x7 - uf))
throw_converterror(F, @exp2(7 - f))
throw_converterror(F, exp2(7 - f))
end
end
h8, h24 = Int16(0x80), Int32(1 << 23)
Expand Down
19 changes: 9 additions & 10 deletions src/normed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,7 @@ function Base.Float32(x::Normed{UInt8,f}) where f
f == 5 && return Float32(Int32(x.i) * 0x003) * @f32(0xb02c0bp-30)
f == 6 && return Float32(Int32(x.i) * 0x049) * @f32(0xe40039p-36)
f == 7 && return Float32(Int32(x.i) * 0x01f) * @f32(0x852b5fp-35)
f == 8 && return Float32(Int32(x.i) * 0x155) * @f32(0xc0f0fdp-40)
0.0f0
#= f == 8 =# return Float32(Int32(x.i) * 0x155) * @f32(0xc0f0fdp-40)
end
function Base.Float32(x::Normed{UInt16,f}) where f
f32 = Float32(x.i)
Expand All @@ -180,7 +179,7 @@ function Base.Float32(x::Normed{UInt32,f}) where f
return muladd(rh, @f32(0x1p-16), rl * @f32(0x1p-40))
elseif f >= 25
rh, rl = Float32(i32>>>0x10), Float32((i32&0xFFFF)<<0xE + i32>>>UInt8(f-14))
return muladd(rh, Float32(@exp2(16-f)), rl * Float32(@exp2(-14-f)))
return muladd(rh, Float32(exp2(16-f)), rl * Float32(exp2(-14-f)))
end
# FIXME: avoid the branch in native x86_64 (non-SIMD) codes
m = ifelse(i32 < 0, 0x1p32 * inv_rawone(x), 0.0)
Expand Down Expand Up @@ -226,24 +225,24 @@ end
function Base.Float64(x::Normed{UInt64,f}) where f
f == 1 && return Float64(x.i)
if f >= 53
rh = Float64(unsafe_trunc(Int64, x.i>>0x10)) * @exp2(16-f) # upper 48 bits
rl = Float64(unsafe_trunc(Int32, x.i&0xFFFF)) * @exp2(-f) # lower 16 bits
return rh + muladd(rh, @exp2(-f), rl)
rh = Float64(unsafe_trunc(Int64, x.i>>0x10)) * exp2(16-f) # upper 48 bits
rl = Float64(unsafe_trunc(Int32, x.i&0xFFFF)) * exp2(-f) # lower 16 bits
return rh + muladd(rh, exp2(-f), rl)
end
x.i / rawone(x)
end
function Base.Float64(x::Normed{UInt128,f}) where f
f == 1 && return Float64(x.i)
ih, il = unsafe_trunc(Int64, x.i>>0x40), unsafe_trunc(Int64, x.i)
rh = Float64(ih>>>0x10) * @exp2(f <= 53 ? 80 : 80 - f) # upper 48 bits
km = @exp2(f <= 53 ? 48 : 48 - f) # for middle 32 bits
rh = Float64(ih>>>0x10) * exp2(f <= 53 ? 80 : 80 - f) # upper 48 bits
km = exp2(f <= 53 ? 48 : 48 - f) # for middle 32 bits
rm = Float64(unsafe_trunc(Int32, ih&0xFFFF)) * (0x1p16 * km) +
Float64(unsafe_trunc(Int32, il>>>0x30)) * km
rl = Float64(il&0xFFFFFFFFFFFF) * @exp2(f <= 53 ? 0 : -f) # lower 48 bits
rl = Float64(il&0xFFFFFFFFFFFF) * exp2(f <= 53 ? 0 : -f) # lower 48 bits
if f <= 53
return (rh + (rm + rl)) / unsafe_trunc(Int64, rawone(x))
elseif f < 76
return rh + (rm + muladd(rh, @exp2(-f), rl))
return rh + (rm + muladd(rh, exp2(-f), rl))
else
return rh + (rm + rl)
end
Expand Down
4 changes: 0 additions & 4 deletions src/utilities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ const UShorterThanInt = typeintersect(ShorterThanInt, Unsigned)
macro f32(x::Float64) # just for hexadecimal floating-point literals
:(Float32($x))
end
macro exp2(n)
:(_exp2(Val($(esc(n)))))
end
_exp2(::Val{N}) where {N} = exp2(N)

# these are defined in julia/float.jl or julia/math.jl, but not exported
significand_bits(::Type{Float32}) = 23
Expand Down
41 changes: 35 additions & 6 deletions test/common.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ using FixedPointNumbers, Statistics, Random, StableRNGs, Test
using FixedPointNumbers: bitwidth, rawtype, nbitsfrac
using Base.Checked

SP = VERSION >= v"1.6.0-DEV.771" ? " " : "" # JuliaLang/julia #37085

"""
target(X::Type, Ss...; ex = :default)

Expand Down Expand Up @@ -153,11 +151,32 @@ function test_convert_from_nan(TX::Type)
end
end

function test_hash_rational(TX::Type)
@testset "hash(::$X)" for X in target(TX, :i8, :i16, :i32, :i64; ex = :thin)
for x in (zero(X), eps(X), typemin(X), typemax(X))
@test x == Rational(x)
@test hash(x) == hash(Rational(x))
end
end
end

# a `Real` type with only the methods that `x % X` needs
struct IntReal <: Real
i::Int
end
Base.:*(a::Integer, x::IntReal) = IntReal(a * x.i)
Base.:*(x::IntReal, a::AbstractFloat) = IntReal(Int(x.i * a))
Base.isfinite(::IntReal) = true
Base.round(x::IntReal) = x
Base.unsafe_trunc(::Type{T}, x::IntReal) where {T <: Integer} = x.i % T

function test_rem_type(TX::Type)
@testset "% $X" for X in target(TX, :i8, :i16; ex = :thin)
xs = typemin(X):0.1:typemax(X)
@test all(x -> x % X === X(x), xs)
@test wrapping_rem(2, X) === saturating_rem(2, X) === checked_rem(2, X) === 2 % X
@test IntReal(1) % X === 1 % X
@test IntReal(2) % X === 2 % X
end
end

Expand Down Expand Up @@ -378,10 +397,10 @@ end
function test_sign_related(TX::Type)
for X in target(TX, :i8; ex = :thin)
# `FixedPoint` is not a signed or unsigned integer type
@test_throws Exception signed(X)
@test_throws Exception signed(X(0.5))
@test_throws Exception unsigned(X)
@test_throws Exception unsigned(X(0.5))
@test_throws MethodError signed(X)
@test_throws MethodError signed(X(0.5))
@test_throws MethodError unsigned(X)
@test_throws MethodError unsigned(X(0.5))

@test sign(X(0.0)) === zero(X)
if typemax(X) >= 1.0
Expand All @@ -400,6 +419,16 @@ function test_isinteger(TX::Type)
end
end

function test_reductions_short(TX::Type)
@testset "sum/prod(::Vector{$X})" for X in target(TX; ex = :thin)
x = typemax(X)
@test sum(X[]) === 0.0
@test prod(X[]) === 1.0
@test sum([x]) === Float64(x)
@test prod([x]) === Float64(x)
end
end

function test_rand(TX::Type)
@testset "rand(::$X)" for X in target(TX; ex = :thin)
@test isa(rand(X), X)
Expand Down
Loading
Loading