From 8fbc64ecd2c31138a75e34ff9bc799672f07a2b3 Mon Sep 17 00:00:00 2001 From: kimikage Date: Mon, 23 Dec 2019 13:12:53 +0900 Subject: [PATCH 1/8] Add docstrings and Update comments --- src/FixedPointNumbers.jl | 10 ++++++++-- src/fixed.jl | 17 ++++++++++++++--- src/normed.jl | 17 +++++++++++++---- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 60ec762d..bf86cd9a 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -10,8 +10,14 @@ import Base: ==, <, <=, -, +, *, /, ~, isapprox, using Base: @pure -# T => BaseType -# f => Number of bits reserved for fractional part +""" + FixedPoint{T <: Integer, f} <: Real + +Supertype of the two fixed-point number types: `Fixed{T, f}` and `Normed{T, f}`. + +The parameter `T` is the underlying machine representation and `f` is the number +of fraction bits. +""" abstract type FixedPoint{T <: Integer, f} <: Real end diff --git a/src/fixed.jl b/src/fixed.jl index 3576a610..82a43114 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -1,5 +1,16 @@ -# 32-bit fixed point; parameter `f` is the number of fraction bits -struct Fixed{T <: Signed,f} <: FixedPoint{T, f} +""" + Fixed{T <: Signed, f} <: FixedPoint{T, f} + +`Fixed{T,f}` maps `Signed` integers from `-2^f` to `2^f` to the range +[-1.0, 1.0]. For example, `Fixed{Int8,7}` maps `-128` to `-1.0` and `127` to +`127/128 ≈ 0.992`. + +There are the typealiases for `Fixed` in the `QXfY` notation, where `Y` is +the number of fractional bits (i.e. `f`), and `X+Y+1` equals the number of +underlying bits used (`+1` means the sign bit). For example, `Q0f7` is aliased +to `Fixed{Int8,7}` and `Q3f12` is aliased to `Fixed{Int16,12}`. +""" +struct Fixed{T <: Signed, f} <: FixedPoint{T, f} i::T # constructor for manipulating the representation; @@ -37,7 +48,7 @@ abs(x::Fixed{T,f}) where {T,f} = Fixed{T,f}(abs(x.i),0) -(x::Fixed{T,f}, y::Fixed{T,f}) where {T,f} = Fixed{T,f}(x.i-y.i,0) # with truncation: -#*{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(Base.widemul(x.i,y.i)>>f,0) +#*(x::Fixed{T,f}, y::Fixed{T,f}) = Fixed{T,f}(Base.widemul(x.i,y.i)>>f,0) # with rounding up: *(x::Fixed{T,f}, y::Fixed{T,f}) where {T,f} = Fixed{T,f}((Base.widemul(x.i,y.i) + (one(widen(T)) << (f-1)))>>f,0) diff --git a/src/normed.jl b/src/normed.jl index f0fa8aad..40d55553 100644 --- a/src/normed.jl +++ b/src/normed.jl @@ -1,7 +1,16 @@ -# Normed{T,f} maps UInts from 0 to 2^f-1 to the range [0.0, 1.0] -# For example, Normed{UInt8,8} == N0f8 maps 0x00 to 0.0 and 0xff to 1.0 - -struct Normed{T<:Unsigned,f} <: FixedPoint{T,f} +""" + Normed{T <: Unsigned, f} <: FixedPoint{T, f} + +`Normed{T,f}` maps `Unsigned` integers from `0` to `2^f-1` to the range +[0.0, 1.0]. For example, `Normed{UInt8,8}` maps `0x00` to `0.0` and `0xff` to +`1.0`. + +There are the typealiases for `Normed` in the `NXfY` notation, where `Y` is +the number of fractional bits (i.e. `f`), and `X+Y` equals the number of +underlying bits used. For example, `N0f8` is aliased to `Normed{UInt8,8}` and +`N4f12` is aliased to `Normed{UInt16,12}`. +""" +struct Normed{T <: Unsigned, f} <: FixedPoint{T, f} i::T Normed{T, f}(i::Integer,_) where {T,f} = new{T, f}(i%T) # for setting by raw representation From dfc366947dcabdfff59cb69371a7caa722dc722c Mon Sep 17 00:00:00 2001 From: kimikage Date: Mon, 23 Dec 2019 13:24:01 +0900 Subject: [PATCH 2/8] Modify scope of variables --- src/FixedPointNumbers.jl | 7 ++----- src/fixed.jl | 3 ++- src/normed.jl | 3 ++- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index bf86cd9a..4fdd67f1 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -119,9 +119,6 @@ rawtype(::Type{FixedPoint{T,f}}) where {T <: Integer,f} = T rawtype(::Type{F}) where {F <: FixedPoint} = rawtype(supertype(F)) rawtype(x::FixedPoint) = rawtype(typeof(x)) -# This IOBuffer is used during module definition to generate typealias names -_iotypealias = IOBuffer() - # Printing. These are used to generate type-symbols, so we need them # before we include any files. function showtype(io::IO, ::Type{X}) where {X <: FixedPoint} @@ -132,10 +129,10 @@ function showtype(io::IO, ::Type{X}) where {X <: FixedPoint} io end function show(io::IO, x::FixedPoint{T,f}) where {T,f} - show(io, round(convert(Float64,x), digits=ceil(Int,f/_log2_10))) + log10_2 = 0.3010299956639812 + show(io, round(convert(Float64,x), digits=ceil(Int, f * log10_2))) get(io, :compact, false) || showtype(io, typeof(x)) end -const _log2_10 = 3.321928094887362 function Base.showarg(io::IO, a::Array{T}, toplevel) where {T<:FixedPoint} toplevel || print(io, "::") diff --git a/src/fixed.jl b/src/fixed.jl index 82a43114..edee8b8c 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -31,8 +31,9 @@ typechar(::Type{X}) where {X <: Fixed} = 'Q' signbits(::Type{X}) where {X <: Fixed} = 1 for T in (Int8, Int16, Int32, Int64) + io = IOBuffer() for f in 0:sizeof(T)*8-1 - sym = Symbol(String(take!(showtype(_iotypealias, Fixed{T,f})))) + sym = Symbol(String(take!(showtype(io, Fixed{T,f})))) @eval begin const $sym = Fixed{$T,$f} export $sym diff --git a/src/normed.jl b/src/normed.jl index 40d55553..34ba405d 100644 --- a/src/normed.jl +++ b/src/normed.jl @@ -25,8 +25,9 @@ typechar(::Type{X}) where {X <: Normed} = 'N' signbits(::Type{X}) where {X <: Normed} = 0 for T in (UInt8, UInt16, UInt32, UInt64) + io = IOBuffer() for f in 1:sizeof(T)*8 - sym = Symbol(String(take!(showtype(_iotypealias, Normed{T,f})))) + sym = Symbol(String(take!(showtype(io, Normed{T,f})))) @eval begin const $sym = Normed{$T,$f} export $sym From 745ce91914b7d6c5cafc09eeb568739c1ba58c2c Mon Sep 17 00:00:00 2001 From: kimikage Date: Mon, 23 Dec 2019 17:06:39 +0900 Subject: [PATCH 3/8] Put utility functions and macros into a new file "utilities.jl" --- src/FixedPointNumbers.jl | 31 +++++++------------- src/fixed.jl | 8 +++--- src/normed.jl | 62 +++++++++++++++++----------------------- src/utilities.jl | 31 ++++++++++++++++++++ test/fixed.jl | 3 +- test/normed.jl | 9 +++--- 6 files changed, 78 insertions(+), 66 deletions(-) create mode 100644 src/utilities.jl diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 4fdd67f1..1bf00714 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -31,6 +31,9 @@ export # Functions scaledual +include("utilities.jl") + +# reinterpretation reinterpret(x::FixedPoint) = x.i reinterpret(::Type{T}, x::FixedPoint{T,f}) where {T,f} = x.i @@ -64,20 +67,6 @@ typemin(::Type{T}) where {T <: FixedPoint} = T(typemin(rawtype(T)), 0) floatmin(::Type{T}) where {T <: FixedPoint} = eps(T) floatmax(::Type{T}) where {T <: FixedPoint} = typemax(T) -widen1(::Type{Int8}) = Int16 -widen1(::Type{UInt8}) = UInt16 -widen1(::Type{Int16}) = Int32 -widen1(::Type{UInt16}) = UInt32 -widen1(::Type{Int32}) = Int64 -widen1(::Type{UInt32}) = UInt64 -widen1(::Type{Int64}) = Int128 -widen1(::Type{UInt64}) = UInt128 -widen1(::Type{Int128}) = Int128 -widen1(::Type{UInt128}) = UInt128 -widen1(x::Integer) = x % widen1(typeof(x)) - -const ShortInts = Union{Int8,UInt8,Int16,UInt16} -const LongInts = Union{UInt64, UInt128, Int64, Int128, BigInt} """ floattype(::Type{T}) @@ -124,7 +113,7 @@ rawtype(x::FixedPoint) = rawtype(typeof(x)) function showtype(io::IO, ::Type{X}) where {X <: FixedPoint} print(io, typechar(X)) f = nbitsfrac(X) - m = sizeof(X)*8-f-signbits(X) + m = bitwidth(X)-f-signbits(X) print(io, m, 'f', f) io end @@ -188,13 +177,13 @@ scaledual(::Type{Tdual}, x::FixedPoint) where Tdual = convert(Tdual, 1/rawone(x) scaledual(::Type{Tdual}, x::AbstractArray{T}) where {Tdual, T <: FixedPoint} = convert(Tdual, 1/rawone(T)), reinterpret(rawtype(T), x) -@noinline function throw_converterror(::Type{T}, x) where {T <: FixedPoint} - n = 2^(8*sizeof(T)) - bitstring = sizeof(T) == 1 ? "an 8-bit" : "a $(8*sizeof(T))-bit" +@noinline function throw_converterror(::Type{X}, x) where {X <: FixedPoint} + n = 2^bitwidth(X) + bitstring = bitwidth(X) == 8 ? "an 8-bit" : "a $(bitwidth(X))-bit" io = IOBuffer() - show(IOContext(io, :compact=>true), typemin(T)); Tmin = String(take!(io)) - show(IOContext(io, :compact=>true), typemax(T)); Tmax = String(take!(io)) - throw(ArgumentError("$T is $bitstring type representing $n values from $Tmin to $Tmax; cannot represent $x")) + show(IOContext(io, :compact=>true), typemin(X)); Xmin = String(take!(io)) + show(IOContext(io, :compact=>true), typemax(X)); Xmax = String(take!(io)) + throw(ArgumentError("$X is $bitstring type representing $n values from $Xmin to $Xmax; cannot represent $x")) end rand(::Type{T}) where {T <: FixedPoint} = reinterpret(T, rand(rawtype(T))) diff --git a/src/fixed.jl b/src/fixed.jl index edee8b8c..f2e97d9d 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -32,7 +32,7 @@ signbits(::Type{X}) where {X <: Fixed} = 1 for T in (Int8, Int16, Int32, Int64) io = IOBuffer() - for f in 0:sizeof(T)*8-1 + for f in 0:bitwidth(T)-1 sym = Symbol(String(take!(showtype(io, Fixed{T,f})))) @eval begin const $sym = Fixed{$T,$f} @@ -96,13 +96,13 @@ promote_rule(::Type{Fixed{T,f}}, ::Type{Rational{TR}}) where {T,f,TR} = Rational f = max(f1, f2) # ensure we have enough precision T = promote_type(T1, T2) # make sure we have enough integer bits - i1, i2 = 8*sizeof(T1)-f1, 8*sizeof(T2)-f2 # number of integer bits for each - i = 8*sizeof(T)-f + i1, i2 = bitwidth(T1)-f1, bitwidth(T2)-f2 # number of integer bits for each + i = bitwidth(T)-f while i < max(i1, i2) Tw = widen1(T) T == Tw && break T = Tw - i = 8*sizeof(T)-f + i = bitwidth(T)-f end :(Fixed{$T,$f}) end diff --git a/src/normed.jl b/src/normed.jl index 34ba405d..6711da68 100644 --- a/src/normed.jl +++ b/src/normed.jl @@ -26,7 +26,7 @@ signbits(::Type{X}) where {X <: Normed} = 0 for T in (UInt8, UInt16, UInt32, UInt64) io = IOBuffer() - for f in 1:sizeof(T)*8 + for f in 1:bitwidth(T) sym = Symbol(String(take!(showtype(io, Normed{T,f})))) @eval begin const $sym = Normed{$T,$f} @@ -39,7 +39,7 @@ reinterpret(::Type{Normed{T,f}}, x::T) where {T <: Unsigned,f} = Normed{T,f}(x, zero(::Type{Normed{T,f}}) where {T,f} = Normed{T,f}(zero(T),0) function oneunit(::Type{T}) where {T <: Normed} - T(typemax(rawtype(T)) >> (8*sizeof(T)-nbitsfrac(T)), 0) + T(typemax(rawtype(T)) >> (bitwidth(T)-nbitsfrac(T)), 0) end one(::Type{T}) where {T <: Normed} = oneunit(T) zero(x::Normed) = zero(typeof(x)) @@ -76,36 +76,34 @@ function _convert(::Type{U}, x::Float16) where {T, f, U <: Normed{T,f}} end return _convert(U, Float32(x)) end -function _convert(::Type{U}, x::Tf) where {T, f, U <: Normed{T,f}, Tf <: Union{Float32, Float64}} - if T == UInt128 && f == 53 - 0 <= x <= Tf(3.777893186295717e22) || throw_converterror(U, x) +function _convert(::Type{N}, x::Tf) where {T, f, N <: Normed{T,f}, Tf <: Union{Float32, Float64}} + if T === UInt128 && f == 53 + 0 <= x <= Tf(3.777893186295717e22) || throw_converterror(N, x) else - 0 <= x <= Tf((typemax(T)-rawone(U))/rawone(U)+1) || throw_converterror(U, x) + 0 <= x <= Tf((typemax(T)-rawone(N))/rawone(N)+1) || throw_converterror(N, x) end - significand_bits = Tf == Float64 ? 52 : 23 - if f <= (significand_bits + 1) && sizeof(T) * 8 < significand_bits - return reinterpret(U, unsafe_trunc(T, round(rawone(U) * x))) + if f <= (significand_bits(Tf) + 1) && bitwidth(T) < significand_bits(Tf) + return reinterpret(N, unsafe_trunc(T, round(rawone(N) * x))) end # cf. the implementation of `frexp` - Tw = f < sizeof(T) * 8 ? T : widen1(T) - bits = sizeof(Tw) * 8 - 1 - xu = reinterpret(Tf == Float64 ? UInt64 : UInt32, x) - k = Int(xu >> significand_bits) - k == 0 && return zero(U) # neglect subnormal numbers - significand = xu | (one(xu) << significand_bits) - yh = unsafe_trunc(Tw, significand) << (bits - significand_bits) - exponent_bias = Tf == Float64 ? 1023 : 127 - ex = exponent_bias - k + bits - f + Tw = f < bitwidth(T) ? T : widen1(T) + bits = bitwidth(Tw) - 1 + xu = reinterpret(Unsigned, x) + k = Int(xu >> significand_bits(Tf)) + k == 0 && return zero(N) # neglect subnormal numbers + significand = xu | (oneunit(xu) << significand_bits(Tf)) + yh = unsafe_trunc(Tw, significand) << (bits - significand_bits(Tf)) + ex = exponent_bias(Tf) - k + bits - f yi = bits >= f ? yh - (yh >> f) : yh if ex <= 0 - ex == 0 && return reinterpret(U, unsafe_trunc(T, yi)) - ex != -1 || signbit(signed(yi)) && return typemax(U) - return reinterpret(U, unsafe_trunc(T, yi + yi)) + ex == 0 && return reinterpret(N, unsafe_trunc(T, yi)) + ex != -1 || signbit(signed(yi)) && return typemax(N) + return reinterpret(N, unsafe_trunc(T, yi + yi)) end - ex > bits && return reinterpret(U, ex == bits + 1 ? one(T) : zero(T)) - yi += one(Tw)<<((ex - 1) & bits) # RoundNearestTiesUp - return reinterpret(U, unsafe_trunc(T, yi >> (ex & bits))) + ex > bits && return reinterpret(N, ex == bits + 1 ? oneunit(T) : zero(T)) + yi += oneunit(Tw)<<((ex - 1) & bits) # RoundNearestTiesUp + return reinterpret(N, unsafe_trunc(T, yi >> (ex & bits))) end rem(x::T, ::Type{T}) where {T <: Normed} = x @@ -115,14 +113,6 @@ rem(x::Float16, ::Type{T}) where {T <: Normed} = rem(Float32(x), T) # avoid ove float(x::Normed) = convert(floattype(x), x) -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) - # 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) @@ -275,7 +265,7 @@ function round(x::Normed{T,f}) where {T,f} Normed{T,f}(y+oneunit(Normed{T,f})) : y end function ceil(x::Normed{T,f}) where {T,f} - k = 8*sizeof(T)-f + k = bitwidth(T)-f mask = (typemax(T)<>k y = trunc(x) return convert(T, reinterpret(x)-reinterpret(y)) & (mask)>0 ? @@ -324,13 +314,13 @@ end f = max(f1, f2) # ensure we have enough precision T = promote_type(T1, T2) # make sure we have enough integer bits - i1, i2 = 8*sizeof(T1)-f1, 8*sizeof(T2)-f2 # number of integer bits for each - i = 8*sizeof(T)-f + i1, i2 = bitwidth(T1)-f1, bitwidth(T2)-f2 # number of integer bits for each + i = bitwidth(T)-f while i < max(i1, i2) Tw = widen1(T) T == Tw && break T = Tw - i = 8*sizeof(T)-f + i = bitwidth(T)-f end :(Normed{$T,$f}) end diff --git a/src/utilities.jl b/src/utilities.jl new file mode 100644 index 00000000..5bc17388 --- /dev/null +++ b/src/utilities.jl @@ -0,0 +1,31 @@ +# utility functions and macros, which are independent of `FixedPoint` +bitwidth(T::Type) = 8sizeof(T) + +widen1(::Type{Int8}) = Int16 +widen1(::Type{UInt8}) = UInt16 +widen1(::Type{Int16}) = Int32 +widen1(::Type{UInt16}) = UInt32 +widen1(::Type{Int32}) = Int64 +widen1(::Type{UInt32}) = UInt64 +widen1(::Type{Int64}) = Int128 +widen1(::Type{UInt64}) = UInt128 +widen1(::Type{Int128}) = Int128 +widen1(::Type{UInt128}) = UInt128 +widen1(x::Integer) = x % widen1(typeof(x)) + +const ShortInts = Union{Int8, UInt8, Int16, UInt16} +const LongInts = Union{Int64, UInt64, Int128, UInt128, BigInt} + +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 +significand_bits(::Type{Float64}) = 52 +exponent_bias(::Type{Float32}) = 127 +exponent_bias(::Type{Float64}) = 1023 diff --git a/test/fixed.jl b/test/fixed.jl index cc8ea449..9fd26bbb 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -1,4 +1,5 @@ using FixedPointNumbers, Test +using FixedPointNumbers: bitwidth function test_op(fun::F, ::Type{T}, fx, fy, fxf, fyf, tol) where {F,T} # Make sure that the result is representable @@ -164,7 +165,7 @@ end (Int64, 63)) tmax = typemax(Fixed{T, f}) @test tmax == BigInt(typemax(T)) / BigInt(2)^f - tol = (tmax + BigFloat(1.0)) / (sizeof(T) * 8) + tol = (tmax + BigFloat(1.0)) / bitwidth(T) for x in range(-1, stop=BigFloat(tmax)-tol, length=50) @test abs(Fixed{T, f}(x) - x) <= tol end diff --git a/test/normed.jl b/test/normed.jl index 6fb4d125..c663aaef 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -1,4 +1,5 @@ using FixedPointNumbers, Test +using FixedPointNumbers: bitwidth @testset "reinterpret" begin @test reinterpret(N0f8, 0xa2).i === 0xa2 @@ -106,7 +107,7 @@ end # issue 102 for T in (UInt8, UInt16, UInt32, UInt64, UInt128) for Tf in (Float16, Float32, Float64) - @testset "Normed{$T,$f}(::$Tf)" for f = 1:sizeof(T)*8 + @testset "Normed{$T,$f}(::$Tf)" for f = 1:bitwidth(T) U = Normed{T,f} r = FixedPointNumbers.rawone(U) @@ -123,7 +124,7 @@ end isinf(input_upper) && continue # for Julia v0.7 @test reinterpret(U(input_upper)) == T(min(round(BigFloat(input_upper) * r), typemax(T))) - input_exp2 = Tf(exp2(sizeof(T) * 8 - f)) + input_exp2 = Tf(exp2(bitwidth(T) - f)) isinf(input_exp2) && continue @test reinterpret(U(input_exp2)) == T(input_exp2) * r end @@ -143,7 +144,7 @@ end for Tf in (Float16, Float32, Float64) @testset "$Tf(::Normed{$Ti})" for Ti in (UInt8, UInt16) - @testset "$Tf(::Normed{$Ti,$f})" for f = 1:(sizeof(Ti)*8) + @testset "$Tf(::Normed{$Ti,$f})" for f = 1:bitwidth(Ti) T = Normed{Ti,f} float_err = 0.0 for i = typemin(Ti):typemax(Ti) @@ -156,7 +157,7 @@ end end end @testset "$Tf(::Normed{$Ti})" for Ti in (UInt32, UInt64, UInt128) - @testset "$Tf(::Normed{$Ti,$f})" for f = 1:(sizeof(Ti)*8) + @testset "$Tf(::Normed{$Ti,$f})" for f = 1:bitwidth(Ti) T = Normed{Ti,f} error_count = 0 for i in vcat(Ti(0x00):Ti(0xFF), (typemax(Ti)-0xFF):typemax(Ti)) From 91554633dccaa6a3bb8919ffabc5547468803232 Mon Sep 17 00:00:00 2001 From: kimikage Date: Mon, 23 Dec 2019 20:58:26 +0900 Subject: [PATCH 4/8] Clean traits and identities up This changes the exception type of `one(Q0f7)` etc. from `InexactError` to `ArgumentError`. --- src/FixedPointNumbers.jl | 58 ++++++++++++++++++++++------------------ src/fixed.jl | 5 ++++ src/normed.jl | 12 ++------- test/fixed.jl | 8 ++++++ 4 files changed, 47 insertions(+), 36 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 1bf00714..f320c777 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -37,6 +37,10 @@ include("utilities.jl") reinterpret(x::FixedPoint) = x.i reinterpret(::Type{T}, x::FixedPoint{T,f}) where {T,f} = x.i +# static parameters +nbitsfrac(::Type{X}) where {T, f, X <: FixedPoint{T,f}} = f +rawtype(::Type{X}) where {T, X <: FixedPoint{T}} = T + # construction using the (approximate) intended value, i.e., N0f8 *(x::Real, ::Type{X}) where {X<:FixedPoint} = X(x) @@ -61,7 +65,17 @@ end # predicates isinteger(x::FixedPoint{T,f}) where {T,f} = (x.i&(1<= bitwidth(T)-1 && throw_converterror(Fixed{T,f}, 1) + oneunit(T) << f +end + # basic operators -(x::Fixed{T,f}) where {T,f} = Fixed{T,f}(-x.i,0) abs(x::Fixed{T,f}) where {T,f} = Fixed{T,f}(abs(x.i),0) diff --git a/src/normed.jl b/src/normed.jl index 6711da68..5d019507 100644 --- a/src/normed.jl +++ b/src/normed.jl @@ -37,15 +37,9 @@ end reinterpret(::Type{Normed{T,f}}, x::T) where {T <: Unsigned,f} = Normed{T,f}(x, 0) -zero(::Type{Normed{T,f}}) where {T,f} = Normed{T,f}(zero(T),0) -function oneunit(::Type{T}) where {T <: Normed} - T(typemax(rawtype(T)) >> (bitwidth(T)-nbitsfrac(T)), 0) +function rawone(::Type{Normed{T,f}}) where {T <: Unsigned, f} + typemax(T) >> (bitwidth(T) - f) end -one(::Type{T}) where {T <: Normed} = oneunit(T) -zero(x::Normed) = zero(typeof(x)) -oneunit(x::Normed) = one(typeof(x)) -one(x::Normed) = oneunit(x) -rawone(v) = reinterpret(one(v)) # Conversions function Normed{T,f}(x::Normed{T2}) where {T <: Unsigned,T2 <: Unsigned,f} @@ -113,8 +107,6 @@ rem(x::Float16, ::Type{T}) where {T <: Normed} = rem(Float32(x), T) # avoid ove float(x::Normed) = convert(floattype(x), 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) function (::Type{T})(x::Normed) where {T <: AbstractFloat} # The following optimization for constant division may cause rounding errors. diff --git a/test/fixed.jl b/test/fixed.jl index 9fd26bbb..290c04e0 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -50,6 +50,14 @@ function test_fixed(::Type{T}, f) where {T} end end +@testset "inexactness" begin + @test_throws InexactError Q0f7(-2) + # TODO: change back to InexactError when it allows message strings + @test_throws ArgumentError one(Q0f15) + @test_throws ArgumentError oneunit(Q0f31) + @test_throws ArgumentError one(Fixed{Int8,8}) +end + @testset "conversion" begin @test isapprox(convert(Fixed{Int8,7}, 0.8), 0.797, atol=0.001) @test isapprox(convert(Fixed{Int8,7}, 0.9), 0.898, atol=0.001) From e5be990052b807d5ac704d828fbd228970b5c714 Mon Sep 17 00:00:00 2001 From: kimikage Date: Mon, 23 Dec 2019 21:14:45 +0900 Subject: [PATCH 5/8] Commonize `reinterpret` and `float` between `Fixed` and `Normed` --- src/FixedPointNumbers.jl | 3 +++ src/fixed.jl | 3 --- src/normed.jl | 4 ---- test/fixed.jl | 10 ++++++++++ test/normed.jl | 2 ++ 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index f320c777..0042f4e1 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -36,6 +36,7 @@ include("utilities.jl") # reinterpretation reinterpret(x::FixedPoint) = x.i reinterpret(::Type{T}, x::FixedPoint{T,f}) where {T,f} = x.i +reinterpret(::Type{X}, x::T) where {T <: Integer, X <: FixedPoint{T}} = X(x, 0) # static parameters nbitsfrac(::Type{X}) where {T, f, X <: FixedPoint{T,f}} = f @@ -113,6 +114,8 @@ floattype(::Type{X}) where {T <: ShortInts, X <: FixedPoint{T}} = Float32 floattype(::Type{X}) where {T <: Integer, X <: FixedPoint{T}} = Float64 floattype(::Type{X}) where {T <: LongInts, X <: FixedPoint{T}} = BigFloat +float(x::FixedPoint) = convert(floattype(x), x) + for f in (:zero, :oneunit, :one, :eps, :rawone, :rawtype, :floattype) @eval begin $f(x::FixedPoint) = $f(typeof(x)) diff --git a/src/fixed.jl b/src/fixed.jl index 5333b899..60c97618 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -25,8 +25,6 @@ Fixed{T,f}(x::Integer) where {T,f} = Fixed{T,f}(round(T, convert(widen1(T),x)<>f) + BigFloat(x.i&(one(widen1(T))<> (bitwidth(T) - f) end @@ -105,8 +103,6 @@ rem(x::Normed, ::Type{T}) where {T <: Normed} = reinterpret(T, _unsafe_trunc(raw rem(x::Real, ::Type{T}) where {T <: Normed} = reinterpret(T, _unsafe_trunc(rawtype(T), round(rawone(T)*x))) rem(x::Float16, ::Type{T}) where {T <: Normed} = rem(Float32(x), T) # avoid overflow -float(x::Normed) = convert(floattype(x), x) - function (::Type{T})(x::Normed) where {T <: AbstractFloat} # The following optimization for constant division may cause rounding errors. diff --git a/test/fixed.jl b/test/fixed.jl index 290c04e0..adbc5dfb 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -50,6 +50,16 @@ function test_fixed(::Type{T}, f) where {T} end end +@testset "reinterpret" begin + @test reinterpret(Q0f7, signed(0xa2)) === -0.734375Q0f7 + @test reinterpret(Q5f10, signed(0x00a2)) === 0.158203125Q5f10 + + @test reinterpret(reinterpret(Q0f7, signed(0xa2))) === signed(0xa2) + @test reinterpret(reinterpret(Q5f10, signed(0x00a2))) === signed(0x00a2) + + @test reinterpret(Int8, 0.5Q0f7) === signed(0x40) +end + @testset "inexactness" begin @test_throws InexactError Q0f7(-2) # TODO: change back to InexactError when it allows message strings diff --git a/test/normed.jl b/test/normed.jl index c663aaef..4c0430ea 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -14,6 +14,8 @@ using FixedPointNumbers: bitwidth @test reinterpret(reinterpret(N2f14, 0x00a2)) === 0x00a2 @test reinterpret(reinterpret(N0f16, 0x00a2)) === 0x00a2 + @test reinterpret(UInt8, 1N0f8) === 0xff + @test 0.635N0f8 == N0f8(0.635) @test 0.635N6f10 == N6f10(0.635) @test 0.635N4f12 == N4f12(0.635) From bbf96ff3fd26e25e0d7e27c4858191cfa7833efe Mon Sep 17 00:00:00 2001 From: kimikage Date: Mon, 23 Dec 2019 21:41:42 +0900 Subject: [PATCH 6/8] Add support for `minmax` and `bswap` for `Fixed` Although `minmax` has been available, but this specializes it. --- src/FixedPointNumbers.jl | 7 +++++++ src/normed.jl | 8 -------- test/fixed.jl | 5 +++++ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 0042f4e1..60911c42 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -116,6 +116,13 @@ floattype(::Type{X}) where {T <: LongInts, X <: FixedPoint{T}} = BigFloat float(x::FixedPoint) = convert(floattype(x), x) +function minmax(x::X, y::X) where {X <: FixedPoint} + a, b = minmax(reinterpret(x), reinterpret(y)) + X(a,0), X(b,0) +end + +bswap(x::X) where {X <: FixedPoint} = sizeof(X) == 1 ? x : X(bswap(x.i), 0) + for f in (:zero, :oneunit, :one, :eps, :rawone, :rawtype, :floattype) @eval begin $f(x::FixedPoint) = $f(typeof(x)) diff --git a/src/normed.jl b/src/normed.jl index 3fc645f8..5acf0991 100644 --- a/src/normed.jl +++ b/src/normed.jl @@ -269,14 +269,6 @@ isfinite(x::Normed) = true isnan(x::Normed) = false isinf(x::Normed) = false -bswap(x::Normed{UInt8,f}) where {f} = x -bswap(x::Normed) = typeof(x)(bswap(reinterpret(x)),0) - -function minmax(x::T, y::T) where {T <: Normed} - a, b = minmax(reinterpret(x), reinterpret(y)) - T(a,0), T(b,0) -end - # Iteration # The main subtlety here is that iterating over N0f8(0):N0f8(1) will wrap around # unless we iterate using a wider type diff --git a/test/fixed.jl b/test/fixed.jl index adbc5dfb..96f7cc6a 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -190,6 +190,11 @@ end end end +@testset "low-level arithmetic" begin + @test bswap(Q0f7(0.5)) === Q0f7(0.5) + @test bswap(Q0f15(0.5)) === reinterpret(Q0f15, signed(0x0040)) +end + @testset "Promotion within Fixed" begin @test @inferred(promote(Q0f7(0.25), Q0f7(0.75))) === (Q0f7(0.25), Q0f7(0.75)) From 7ede11b332f2e5859fe8cadddaf4034626d3219e Mon Sep 17 00:00:00 2001 From: kimikage Date: Mon, 23 Dec 2019 21:23:28 +0900 Subject: [PATCH 7/8] Remove useless specialized comparison methods --- src/normed.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/normed.jl b/src/normed.jl index 5acf0991..327c972a 100644 --- a/src/normed.jl +++ b/src/normed.jl @@ -239,10 +239,6 @@ abs(x::Normed) = x *(x::T, y::T) where {T <: Normed} = convert(T,convert(floattype(T), x)*convert(floattype(T), y)) /(x::T, y::T) where {T <: Normed} = convert(T,convert(floattype(T), x)/convert(floattype(T), y)) -# Comparisons - <(x::T, y::T) where {T <: Normed} = reinterpret(x) < reinterpret(y) -<=(x::T, y::T) where {T <: Normed} = reinterpret(x) <= reinterpret(y) - # Functions trunc(x::T) where {T <: Normed} = T(div(reinterpret(x), rawone(T))*rawone(T),0) floor(x::T) where {T <: Normed} = trunc(x) From 763783ee026a3a7c048d9cd757de7ae23bb3e7be Mon Sep 17 00:00:00 2001 From: kimikage Date: Mon, 23 Dec 2019 23:07:21 +0900 Subject: [PATCH 8/8] Clean arithmetic methods up --- src/FixedPointNumbers.jl | 13 +++++++------ src/fixed.jl | 7 +------ src/normed.jl | 7 +------ 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 60911c42..c8ee8948 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -45,10 +45,6 @@ rawtype(::Type{X}) where {T, X <: FixedPoint{T}} = T # construction using the (approximate) intended value, i.e., N0f8 *(x::Real, ::Type{X}) where {X<:FixedPoint} = X(x) -# comparison -==(x::T, y::T) where {T <: FixedPoint} = x.i == y.i - <(x::T, y::T) where {T <: FixedPoint} = x.i < y.i -<=(x::T, y::T) where {T <: FixedPoint} = x.i <= y.i """ isapprox(x::FixedPoint, y::FixedPoint; rtol=0, atol=max(eps(x), eps(y))) @@ -128,12 +124,17 @@ for f in (:zero, :oneunit, :one, :eps, :rawone, :rawtype, :floattype) $f(x::FixedPoint) = $f(typeof(x)) end end -for f in (:div, :fld, :fld1) +for f in (:(==), :<, :<=, :div, :fld, :fld1) @eval begin $f(x::X, y::X) where {X <: FixedPoint} = $f(x.i, y.i) end end -for f in (:rem, :mod, :mod1, :min, :max) +for f in (:-, :~, :abs) + @eval begin + $f(x::X) where {X <: FixedPoint} = X($f(x.i), 0) + end +end +for f in (:+, :-, :rem, :mod, :mod1, :min, :max) @eval begin $f(x::X, y::X) where {X <: FixedPoint} = X($f(x.i, y.i), 0) end diff --git a/src/fixed.jl b/src/fixed.jl index 60c97618..d4164b67 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -44,12 +44,7 @@ function rawone(::Type{Fixed{T,f}}) where {T, f} oneunit(T) << f end -# basic operators --(x::Fixed{T,f}) where {T,f} = Fixed{T,f}(-x.i,0) -abs(x::Fixed{T,f}) where {T,f} = Fixed{T,f}(abs(x.i),0) - -+(x::Fixed{T,f}, y::Fixed{T,f}) where {T,f} = Fixed{T,f}(x.i+y.i,0) --(x::Fixed{T,f}, y::Fixed{T,f}) where {T,f} = Fixed{T,f}(x.i-y.i,0) +# unchecked arithmetic # with truncation: #*(x::Fixed{T,f}, y::Fixed{T,f}) = Fixed{T,f}(Base.widemul(x.i,y.i)>>f,0) diff --git a/src/normed.jl b/src/normed.jl index 327c972a..7e119bba 100644 --- a/src/normed.jl +++ b/src/normed.jl @@ -228,14 +228,9 @@ Base.Integer(x::Normed) = convert(Integer, x*1.0) Base.Rational{Ti}(x::Normed) where {Ti <: Integer} = convert(Ti, reinterpret(x))//convert(Ti, rawone(x)) Base.Rational(x::Normed) = reinterpret(x)//rawone(x) -# Traits abs(x::Normed) = x -(-)(x::T) where {T <: Normed} = T(-reinterpret(x), 0) -(~)(x::T) where {T <: Normed} = T(~reinterpret(x), 0) - -+(x::Normed{T,f}, y::Normed{T,f}) where {T,f} = Normed{T,f}(convert(T, x.i+y.i),0) --(x::Normed{T,f}, y::Normed{T,f}) where {T,f} = Normed{T,f}(convert(T, x.i-y.i),0) +# unchecked arithmetic *(x::T, y::T) where {T <: Normed} = convert(T,convert(floattype(T), x)*convert(floattype(T), y)) /(x::T, y::T) where {T <: Normed} = convert(T,convert(floattype(T), x)/convert(floattype(T), y))