From 8fbc64ecd2c31138a75e34ff9bc799672f07a2b3 Mon Sep 17 00:00:00 2001 From: kimikage Date: Mon, 23 Dec 2019 13:12:53 +0900 Subject: [PATCH 01/23] 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 02/23] 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 03/23] 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 04/23] 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 05/23] 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 06/23] 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 07/23] 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 08/23] 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)) From 3048c18b4a95e41ccf16b394e5ce547c3d1c1eec Mon Sep 17 00:00:00 2001 From: kimikage Date: Tue, 7 Jan 2020 21:45:31 +0900 Subject: [PATCH 09/23] Work around JuliaLang/julia#34121 (#160) --- src/FixedPointNumbers.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index c8ee8948..69de51d4 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -206,7 +206,9 @@ end rand(::Type{T}) where {T <: FixedPoint} = reinterpret(T, rand(rawtype(T))) rand(::Type{T}, sz::Dims) where {T <: FixedPoint} = reinterpret(T, rand(rawtype(T), sz)) -include("precompile.jl") -_precompile_() +if VERSION >= v"1.1" # work around https://github.com/JuliaLang/julia/issues/34121 + include("precompile.jl") + _precompile_() +end end # module From ca6e304a35d3d2dfda0f3fa3522c3574a2ea43b8 Mon Sep 17 00:00:00 2001 From: kimikage Date: Sat, 11 Jan 2020 21:09:00 +0900 Subject: [PATCH 10/23] Add depwarn in constructor of Fixed{T,f} where f == 8sizeof(T) (Fixes #155) (#159) This also adds the domain checks for `f` in the constructors. --- src/fixed.jl | 8 +++++++- src/normed.jl | 5 ++++- test/fixed.jl | 28 ++++++++++++++++++---------- test/normed.jl | 7 +++++++ 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/fixed.jl b/src/fixed.jl index d4164b67..67711f89 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -15,7 +15,13 @@ struct Fixed{T <: Signed, f} <: FixedPoint{T, f} # constructor for manipulating the representation; # selected by passing an extra dummy argument - Fixed{T, f}(i::Integer, _) where {T,f} = new{T, f}(i % T) + function Fixed{T, f}(i::Integer, _) where {T, f} + if f == bitwidth(T) + Base.depwarn("`Fixed` reserves one bit for the sign. Support for `f=$f` with raw type `T=$T` will be removed in a future release.", :Fixed) + end + 0 <= f <= bitwidth(T) || throw(DomainError(f, "f must be between 0 and $(bitwidth(T)-1) (i.e. the number of non-sign bits of `T=$T`)")) # TODO: change the upper limit + new{T, f}(i % T) + end end Fixed{T, f}(x::AbstractChar) where {T,f} = throw(ArgumentError("Fixed cannot be constructed from a Char")) diff --git a/src/normed.jl b/src/normed.jl index 7e119bba..8112d9b8 100644 --- a/src/normed.jl +++ b/src/normed.jl @@ -13,7 +13,10 @@ underlying bits used. For example, `N0f8` is aliased to `Normed{UInt8,8}` and 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 + function Normed{T, f}(i::Integer, _) where {T, f} + 1 <= f <= bitwidth(T) || throw(DomainError(f, "f must be between 1 and $(bitwidth(T)) (i.e. the number of bits of `T=$T`)")) + new{T, f}(i % T) + end end Normed{T, f}(x::AbstractChar) where {T,f} = throw(ArgumentError("Normed cannot be constructed from a Char")) diff --git a/test/fixed.jl b/test/fixed.jl index 96f7cc6a..21a8a930 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -50,6 +50,16 @@ function test_fixed(::Type{T}, f) where {T} end end +@testset "domain of f" begin + # TODO: change the upper limit + @test_logs (:warn, r"`f=8` with raw type `T=Int8` will be removed") zero(Fixed{Int8,8}) + @test_throws DomainError zero(Fixed{Int8,-1}) + # @test_throws DomainError zero(Fixed{Int8,8}) + @test_throws DomainError zero(Fixed{Int8,9}) + # @test_throws DomainError zero(Fixed{Int16,16}) + @test_throws DomainError zero(Fixed{Int16,17}) +end + @testset "reinterpret" begin @test reinterpret(Q0f7, signed(0xa2)) === -0.734375Q0f7 @test reinterpret(Q5f10, signed(0x00a2)) === 0.158203125Q5f10 @@ -65,7 +75,7 @@ end # 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}) + @test_throws ArgumentError one(Fixed{Int8,8}) # TODO: remove this at end of its support end @testset "conversion" begin @@ -79,7 +89,7 @@ end end @testset "test_fixed" begin - for (TI, f) in [(Int8, 8), (Int16, 8), (Int16, 10), (Int32, 16)] + for (TI, f) in [(Int8, 7), (Int16, 8), (Int16, 10), (Int32, 16)] T = Fixed{TI,f} # println(" Testing $T") test_fixed(T, f) @@ -112,8 +122,7 @@ end end @testset "reductions" begin - F8 = Fixed{Int8,8} - a = F8[0.498, 0.1] + a = Q0f7[0.75, 0.5] acmp = Float64(a[1]) + Float64(a[2]) @test sum(a) == acmp @test sum(a, dims=1) == [acmp] @@ -126,7 +135,7 @@ end end @testset "convert result type" begin - x = Fixed{Int8,8}(0.3) + x = Fixed{Int8,7}(0.75) for T in (Float16, Float32, Float64, BigFloat) y = convert(T, x) @test isa(y, T) @@ -154,11 +163,10 @@ end end @testset "rand" begin - for T in (Fixed{Int8,8}, Fixed{Int16,8}, Fixed{Int16,10}, Fixed{Int32,16}) - a = rand(T) - @test isa(a, T) - a = rand(T, (3, 5)) - @test ndims(a) == 2 && eltype(a) == T + for F in (Fixed{Int8,7}, Fixed{Int16,8}, Fixed{Int16,10}, Fixed{Int32,16}) + @test isa(rand(F), F) + a = rand(F, (3, 5)) + @test ndims(a) == 2 && eltype(a) == F @test size(a) == (3,5) end end diff --git a/test/normed.jl b/test/normed.jl index 4c0430ea..31d49060 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -1,6 +1,13 @@ using FixedPointNumbers, Test using FixedPointNumbers: bitwidth +@testset "domain of f" begin + @test_throws DomainError zero(Normed{UInt8,-1}) + @test_throws DomainError zero(Normed{UInt8,0}) + @test_throws DomainError zero(Normed{UInt8,9}) + @test_throws DomainError zero(Normed{UInt16,17}) +end + @testset "reinterpret" begin @test reinterpret(N0f8, 0xa2).i === 0xa2 @test reinterpret(N6f10, 0x1fa2).i === 0x1fa2 From 1e0c5f587b19930612ab53cf6d8d6de68c445656 Mon Sep 17 00:00:00 2001 From: kimikage Date: Tue, 24 Dec 2019 20:37:10 +0900 Subject: [PATCH 11/23] Fix overflow problems with `round` and `ceil` for `Normed` This also simplifies the test for rounding functions. --- src/normed.jl | 43 ++++++++++++++++++++++++---------------- test/normed.jl | 54 ++++++++++++++++++++------------------------------ 2 files changed, 47 insertions(+), 50 deletions(-) diff --git a/src/normed.jl b/src/normed.jl index 8112d9b8..b359059e 100644 --- a/src/normed.jl +++ b/src/normed.jl @@ -238,26 +238,35 @@ abs(x::Normed) = x /(x::T, y::T) where {T <: Normed} = convert(T,convert(floattype(T), x)/convert(floattype(T), 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) -function round(x::Normed{T,f}) where {T,f} - mask = convert(T, 1<<(f-1)) - y = trunc(x) - return convert(T, reinterpret(x)-reinterpret(y)) & mask>0 ? - Normed{T,f}(y+oneunit(Normed{T,f})) : y +trunc(x::N) where {N <: Normed} = floor(x) +floor(x::N) where {N <: Normed} = reinterpret(N, x.i - x.i % rawone(N)) +function ceil(x::Normed{T,f}) where {T, f} + f == 1 && return x + if typemax(T) % rawone(x) != 0 + upper = typemax(T) - typemax(T) % rawone(x) + x.i > upper && throw_converterror(Normed{T,f}, ceil(T, typemax(x))) + end + r = x.i % rawone(x) + reinterpret(Normed{T,f}, x.i - r + (r > 0 ? rawone(x) : zero(T))) end -function ceil(x::Normed{T,f}) where {T,f} - k = bitwidth(T)-f - mask = (typemax(T)<>k - y = trunc(x) - return convert(T, reinterpret(x)-reinterpret(y)) & (mask)>0 ? - Normed{T,f}(y+oneunit(Normed{T,f})) : y +function round(x::Normed{T,f}) where {T, f} + r = x.i % rawone(x) + q = rawone(x) - r + reinterpret(Normed{T,f}, r > q ? x.i + q : x.i - r) end -trunc(::Type{T}, x::Normed) where {T <: Integer} = convert(T, div(reinterpret(x), rawone(x))) -round(::Type{T}, x::Normed) where {T <: Integer} = round(T, reinterpret(x)/rawone(x)) -floor(::Type{T}, x::Normed) where {T <: Integer} = trunc(T, x) - ceil(::Type{T}, x::Normed) where {T <: Integer} = ceil(T, reinterpret(x)/rawone(x)) +trunc(::Type{Ti}, x::Normed) where {Ti <: Integer} = floor(Ti, x) +function floor(::Type{Ti}, x::Normed) where {Ti <: Integer} + convert(Ti, reinterpret(x) ÷ rawone(x)) +end +function ceil(::Type{Ti}, x::Normed) where {Ti <: Integer} + d, r = divrem(x.i, rawone(x)) + convert(Ti, r > 0 ? d + oneunit(rawtype(x)) : d) +end +function round(::Type{Ti}, x::Normed) where {Ti <: Integer} + d, r = divrem(x.i, rawone(x)) + convert(Ti, r > (rawone(x) >> 0x1) ? d + oneunit(rawtype(x)) : d) +end isfinite(x::Normed) = true isnan(x::Normed) = false diff --git a/test/normed.jl b/test/normed.jl index 31d49060..bdbceeca 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -240,41 +240,29 @@ end end end -function testtrunc(inc::T) where {T} - incf = convert(Float64, inc) - tm = reinterpret(typemax(T))/reinterpret(one(T)) - local x = zero(T) - for i = 0 : min(1e6, reinterpret(typemax(T))-1) - xf = incf*i - try - @test typeof(trunc(x)) == T - @test trunc(x) == trunc(xf) - @test typeof(round(x)) == T - @test round(x) == round(xf) - cxf = ceil(xf) - if cxf < tm - @test typeof(ceil(x)) == T - @test ceil(x) == ceil(xf) - end - @test typeof(floor(x)) == T - @test floor(x) == floor(xf) - @test trunc(Int,x) == trunc(Int,xf) - @test round(Int,x) == round(Int,xf) - @test floor(Int,x) == floor(Int,xf) - if cxf < tm - @test ceil(Int,x) == ceil(Int,xf) - end - catch err - println("Failed on x = ", x, ", xf = ", xf) - rethrow(err) +@testset "rounding" begin + for T in (UInt8, UInt16, UInt32, UInt64) + rs = vcat([ oneunit(T) << b - oneunit(T) << 1 for b = 1:bitwidth(T)], + [ oneunit(T) << b - oneunit(T) for b = 1:bitwidth(T)], + [ oneunit(T) << b for b = 2:bitwidth(T)-1]) + @testset "rounding Normed{$T,$f}" for f = 1:bitwidth(T) + N = Normed{T,f} + xs = (reinterpret(N, r) for r in rs) + @test all(x -> trunc(x) == trunc(float(x)), xs) + @test all(x -> floor(x) == floor(float(x)), xs) + # force `Normed` comparison avoiding rounding errors + @test all(x -> ceil(float(x)) > typemax(N) || ceil(x) == N(ceil(float(x))), xs) + @test all(x -> round(x) == round(float(x)), xs) + @test all(x -> trunc(UInt64, x) === trunc(UInt64, float(x)), xs) + @test all(x -> floor(UInt64, x) === floor(UInt64, float(x)), xs) + @test all(x -> ceil(UInt64, x) === ceil(UInt64, float(x)), xs) + @test all(x -> round(UInt64, x) === round(UInt64, float(x)), xs) end - x = convert(T, x+inc) end -end - -@testset "trunc" begin - for T in (FixedPointNumbers.UF..., UF2...) - testtrunc(eps(T)) + @testset "rounding Normed{Int16,$f} with overflow" for f in filter(x->!ispow2(x), 1:16) + N = Normed{UInt16,f} + @test_throws ArgumentError ceil(typemax(N)) + @test_throws ArgumentError ceil(floor(typemax(N)) + eps(N)) end end From 08d9ead862676cc5787e4342f43c34f4854e31fd Mon Sep 17 00:00:00 2001 From: kimikage Date: Tue, 24 Dec 2019 20:38:29 +0900 Subject: [PATCH 12/23] Add rounding functions for `Fixed` The functions support `Fixed{Int8,8}` and so on, for now. --- src/fixed.jl | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++ test/fixed.jl | 52 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/src/fixed.jl b/src/fixed.jl index 67711f89..38c9af92 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -50,6 +50,9 @@ function rawone(::Type{Fixed{T,f}}) where {T, f} oneunit(T) << f end +intmask(::Fixed{T,f}) where {T, f} = -oneunit(T) << f # Signed +fracmask(x::Fixed{T,f}) where {T, f} = ~intmask(x) # Signed + # unchecked arithmetic # with truncation: @@ -91,6 +94,73 @@ end (::Type{TR})(x::Fixed{T,f}) where {TR <: Rational,T,f} = TR(x.i>>f + (x.i&(1< upper && throw_converterror(Fixed{T,f}, ceil(float(x))) + reinterpret(Fixed{T,f}, (x.i + fracmask(x)) & intmask(x)) +end +function round(x::Fixed{T,f}) where {T, f} + f == 0 && return x + f == bitwidth(T) && return zero(x) # TODO: remove this line + 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)) + return x.i < lower ? typemin(x) : zero(x) + end + 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) + reinterpret(Fixed{T,f}, z - T(y & m == rawone(x)) << f) +end + +function trunc(::Type{Ti}, x::Fixed{T,f}) where {Ti <: Integer, T, f} + f == 0 && return convert(Ti, x.i) + f == bitwidth(T) && return zero(Ti) # TODO: remove this line + f == bitwidth(T) - 1 && return x.i == typemin(T) ? convert(Ti, -1) : zero(Ti) + t = x.i >> f + r = x.i & fracmask(x) + convert(Ti, (x.i < 0) & (r != 0) ? t + oneunit(T) : t) +end +function floor(::Type{Ti}, x::Fixed{T,f}) where {Ti <: Integer, T, f} + f == bitwidth(T) && return x.i < 0 ? convert(Ti, -1) : zero(Ti) # TODO: remove this line + convert(Ti, x.i >> f) +end +function ceil(::Type{Ti}, x::Fixed{T,f}) where {Ti <: Integer, T, f} + f == bitwidth(T) && return x.i > 0 ? oneunit(Ti) : zero(Ti) # TODO: remove this line + y = x.i + fracmask(x) + convert(Ti, x.i >= 0 ? y >>> f : y >> f) +end +function round(::Type{Ti}, x::Fixed{T,f}) where {Ti <: Integer, T, f} + f == 0 && return convert(Ti, x.i) + f == bitwidth(T) && return zero(Ti) # TODO: remove this line + upper = intmask(x) >>> 0x1 + lower = intmask(x) >> 0x1 + if f == bitwidth(T) - 1 + x.i < lower && return convert(Ti, -1) + return x.i > upper ? oneunit(Ti) : zero(Ti) + end + y = oneunit(T) << UInt8(f - 1) + x.i + m = oneunit(T) << UInt8(f + 1) - oneunit(T) + z = x.i >= 0 ? y >>> f : y >> f + convert(Ti, z - Ti(y & m == rawone(x))) +end + promote_rule(ft::Type{Fixed{T,f}}, ::Type{TI}) where {T,f,TI <: Integer} = Fixed{T,f} promote_rule(::Type{Fixed{T,f}}, ::Type{TF}) where {T,f,TF <: AbstractFloat} = TF promote_rule(::Type{Fixed{T,f}}, ::Type{Rational{TR}}) where {T,f,TR} = Rational{TR} diff --git a/test/fixed.jl b/test/fixed.jl index 21a8a930..13e21252 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -70,6 +70,18 @@ end @test reinterpret(Int8, 0.5Q0f7) === signed(0x40) end +@testset "masks" begin + @test FixedPointNumbers.intmask(0Q7f0) === signed(0xFF) + @test FixedPointNumbers.intmask(0Q6f1) === signed(0xFE) + @test FixedPointNumbers.intmask(0Q1f6) === signed(0xC0) + @test FixedPointNumbers.intmask(0Q0f7) === signed(0x80) + + @test FixedPointNumbers.fracmask(0Q7f0) === signed(0x00) + @test FixedPointNumbers.fracmask(0Q6f1) === signed(0x01) + @test FixedPointNumbers.fracmask(0Q1f6) === signed(0x3F) + @test FixedPointNumbers.fracmask(0Q0f7) === signed(0x7F) +end + @testset "inexactness" begin @test_throws InexactError Q0f7(-2) # TODO: change back to InexactError when it allows message strings @@ -96,6 +108,46 @@ end end end +@testset "rounding" begin + for T in (Int8, Int16, Int32, Int64) + rs = vcat([ oneunit(T) << b - oneunit(T) for b = 0:bitwidth(T)-1], + [ oneunit(T) << b for b = 1:bitwidth(T)-2], + [ oneunit(T) << b + oneunit(T) for b = 2:bitwidth(T)-2], + [-oneunit(T) << b - oneunit(T) for b = 2:bitwidth(T)-2], + [-oneunit(T) << b for b = 1:bitwidth(T)-1], + [-oneunit(T) << b + oneunit(T) for b = 1:bitwidth(T)-1]) + @testset "rounding Fixed{$T,$f}" for f = 0:bitwidth(T)-1 + F = Fixed{T,f} + xs = (reinterpret(F, r) for r in rs) + @test all(x -> trunc(x) == trunc(float(x)), xs) + @test all(x -> floor(float(x)) < typemin(F) || floor(x) == floor(float(x)), xs) + @test all(x -> ceil(float(x)) > typemax(F) || ceil(x) == ceil(float(x)), xs) + @test all(x -> round(float(x)) > typemax(F) || round(x) == round(float(x)), xs) + @test all(x -> trunc(Int64, x) === trunc(Int64, float(x)), xs) + @test all(x -> floor(Int64, x) === floor(Int64, float(x)), xs) + @test all(x -> ceil(Int64, x) === ceil(Int64, float(x)), xs) + @test all(x -> round(Int64, x) === round(Int64, float(x)), xs) + end + end + @testset "rounding Fixed{Int16,$f} with overflow" for f = 1:16 # TODO: drop 16 + F = Fixed{Int16,f} + @test_throws ArgumentError ceil(typemax(F)) + if f == 16 + @test_throws ArgumentError ceil(eps(F)) + elseif f == 15 + @test_throws ArgumentError ceil(eps(F)) + @test_throws ArgumentError round(typemax(F)) + @test_throws ArgumentError round(F(0.5) + eps(F)) + else + @test_throws ArgumentError ceil(typemin(F) - oneunit(F) + eps(F)) + @test_throws ArgumentError round(typemax(F)) + @test_throws ArgumentError round(typemax(F) - F(0.5) + eps(F)) + end + end + @test_throws InexactError trunc(UInt, typemin(Q0f7)) + @test_throws InexactError floor(UInt, -eps(Q0f7)) +end + @testset "modulus" begin T = Fixed{Int8,7} for i = -1.0:0.1:typemax(T) From 265d0afaa716252b02bc7a89804a67545a20cd37 Mon Sep 17 00:00:00 2001 From: kimikage Date: Sun, 12 Jan 2020 02:41:19 +0900 Subject: [PATCH 13/23] Add support for specifying `RoundingMode` for `round` --- src/FixedPointNumbers.jl | 9 +++++++++ test/fixed.jl | 10 ++++++++++ test/normed.jl | 10 ++++++++++ 3 files changed, 29 insertions(+) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 69de51d4..8dea702b 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -139,6 +139,15 @@ for f in (:+, :-, :rem, :mod, :mod1, :min, :max) $f(x::X, y::X) where {X <: FixedPoint} = X($f(x.i, y.i), 0) end end +for (m, f) in ((:(:Nearest), :round), + (:(:ToZero), :trunc), + (:(:Up), :ceil), + (:(:Down), :floor)) + @eval begin + round(x::FixedPoint, ::RoundingMode{$m}) = $f(x) + round(::Type{Ti}, x::FixedPoint, ::RoundingMode{$m}) where {Ti <: Integer} = $f(Ti, x) + end +end # Printing. These are used to generate type-symbols, so we need them # before we include any files. diff --git a/test/fixed.jl b/test/fixed.jl index 13e21252..13b6a79d 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -144,6 +144,16 @@ end @test_throws ArgumentError round(typemax(F) - F(0.5) + eps(F)) end end + @testset "rounding mode" begin + @test round(-1.5Q1f6, RoundNearest) === -2Q1f6 + @test round(-1.5Q1f6, RoundToZero) === -1Q1f6 + @test round(-1.5Q1f6, RoundUp) === -1Q1f6 + @test round(-1.5Q1f6, RoundDown) === -2Q1f6 + @test round(Int, -1.5Q1f6, RoundNearest) === -2 + @test round(Int, -1.5Q1f6, RoundToZero) === -1 + @test round(Int, -1.5Q1f6, RoundUp) === -1 + @test round(Int, -1.5Q1f6, RoundDown) === -2 + end @test_throws InexactError trunc(UInt, typemin(Q0f7)) @test_throws InexactError floor(UInt, -eps(Q0f7)) end diff --git a/test/normed.jl b/test/normed.jl index bdbceeca..c73b9c8e 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -264,6 +264,16 @@ end @test_throws ArgumentError ceil(typemax(N)) @test_throws ArgumentError ceil(floor(typemax(N)) + eps(N)) end + @testset "rounding mode" begin + @test round(1.504N1f7, RoundNearest) === 2N1f7 + @test round(1.504N1f7, RoundToZero) === 1N1f7 + @test round(1.504N1f7, RoundUp) === 2N1f7 + @test round(1.504N1f7, RoundDown) === 1N1f7 + @test round(Int, 1.504N1f7, RoundNearest) === 2 + @test round(Int, 1.504N1f7, RoundToZero) === 1 + @test round(Int, 1.504N1f7, RoundUp) === 2 + @test round(Int, 1.504N1f7, RoundDown) === 1 + end end @testset "approx" begin From bae654229979c185b1f3d3946adc5eef08492800 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 13 Jan 2020 18:55:17 -0600 Subject: [PATCH 14/23] Fix range construction and `length` (#163) --- src/FixedPointNumbers.jl | 17 ++++++++++++++++- src/fixed.jl | 10 ++++++++++ src/normed.jl | 6 ++++++ src/utilities.jl | 5 +++++ test/fixed.jl | 28 ++++++++++++++++++++++++++++ test/normed.jl | 29 +++++++++++++++++++++++++---- 6 files changed, 90 insertions(+), 5 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 8dea702b..f59013a5 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -6,7 +6,9 @@ import Base: ==, <, <=, -, +, *, /, ~, isapprox, zero, oneunit, one, typemin, typemax, floatmin, floatmax, eps, sizeof, reinterpret, float, trunc, round, floor, ceil, bswap, div, fld, rem, mod, mod1, fld1, min, max, minmax, - rand + rand, length + +using Base.Checked: checked_add, checked_sub, checked_div using Base: @pure @@ -149,6 +151,19 @@ for (m, f) in ((:(:Nearest), :round), end end +function length(r::StepRange{X,X}) where {X <: FixedPoint{<:ShorterThanInt}} + start, step, stop = Int(reinterpret(r.start)), Int(reinterpret(r.step)), Int(reinterpret(r.stop)) + return div((stop - start) + step, step) +end +function length(r::StepRange{X,X}) where {X <: FixedPoint} + start, step, stop = reinterpret(r.start), reinterpret(r.step), reinterpret(r.stop) + return checked_div(checked_add(checked_sub(stop, start), step), step) +end +function length(r::StepRange{<:FixedPoint}) + start, step, stop = float(r.start), r.step, float(r.stop) + return div((stop - start) + step, step) +end + # 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} diff --git a/src/fixed.jl b/src/fixed.jl index 38c9af92..a4c9b65f 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -161,6 +161,16 @@ function round(::Type{Ti}, x::Fixed{T,f}) where {Ti <: Integer, T, f} convert(Ti, z - Ti(y & m == rawone(x))) end +# Range construction +Base.unitrange_last(start::F, stop::F) where {F<:Fixed} = + stop >= start ? convert(F, start+floor(stop-start)) : convert(F, start+F(-1)) + +# Range lengths +length(r::AbstractUnitRange{F}) where {F <: Fixed{<:SShorterThanInt,f}} where {f} = + ((Int(reinterpret(last(r))) - Int(reinterpret(first(r)))) >> f) + 1 +length(r::AbstractUnitRange{F}) where {F <: Fixed{T}} where {T <: Signed} = + checked_add(checked_sub(floor(T, last(r)), floor(T, first(r))), oneunit(T)) + promote_rule(ft::Type{Fixed{T,f}}, ::Type{TI}) where {T,f,TI <: Integer} = Fixed{T,f} promote_rule(::Type{Fixed{T,f}}, ::Type{TF}) where {T,f,TF <: AbstractFloat} = TF promote_rule(::Type{Fixed{T,f}}, ::Type{Rational{TR}}) where {T,f,TR} = Rational{TR} diff --git a/src/normed.jl b/src/normed.jl index b359059e..4b89d3e3 100644 --- a/src/normed.jl +++ b/src/normed.jl @@ -287,6 +287,12 @@ function decompose(x::Normed) div(reinterpret(x),g), 0, div(rawone(x),g) end +# Range lengths +length(r::AbstractUnitRange{N}) where {N <: Normed{<:UShorterThanInt}} = + floor(Int, last(r)) - floor(Int, first(r)) + 1 +length(r::AbstractUnitRange{N}) where {N <: Normed{T}} where {T<:Unsigned} = + r.start > r.stop ? T(0) : checked_add(floor(T, last(r)) - floor(T, first(r)), oneunit(T)) + # Promotions promote_rule(::Type{T}, ::Type{Tf}) where {T <: Normed,Tf <: AbstractFloat} = promote_type(floattype(T), Tf) promote_rule(::Type{T}, ::Type{R}) where {T <: Normed,R <: Rational} = R diff --git a/src/utilities.jl b/src/utilities.jl index 5bc17388..8d1b84b5 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -16,6 +16,11 @@ widen1(x::Integer) = x % widen1(typeof(x)) const ShortInts = Union{Int8, UInt8, Int16, UInt16} const LongInts = Union{Int64, UInt64, Int128, UInt128, BigInt} +const ShorterThanInt = Int === Int32 ? ShortInts : Union{ShortInts, Int32, UInt32} +const NotBiggerThanInt = Union{ShorterThanInt, Int, UInt} +const SShorterThanInt = typeintersect(ShorterThanInt, Signed) +const UShorterThanInt = typeintersect(ShorterThanInt, Unsigned) + macro f32(x::Float64) # just for hexadecimal floating-point literals :(Float32($x)) end diff --git a/test/fixed.jl b/test/fixed.jl index 13b6a79d..e0038088 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -183,6 +183,34 @@ end end end +@testset "unit range" begin + @test length(Q1f6(-1):Q1f6(0)) == 2 + @test length(Q1f6(0):Q1f6(-1)) == 0 + @test collect(Q1f6(-1):Q1f6(0)) == Q1f6[-1, 0] + @test length(Q6f1(-64):Q6f1(63)) == 128 + QIntW = Fixed{Int,bitwidth(Int)-1} + @test length(QIntW(-1):QIntW(0)) == 2 + QInt1 = Fixed{Int,1} + @test length(typemin(QInt1):typemax(QInt1)-oneunit(QInt1)) == typemax(Int) + @test_throws OverflowError length(typemin(QInt1):typemax(QInt1)) + @test length(-127Q7f0:127Q7f0) == 255 + @test length(Q1f62(0):Q1f62(-2)) == 0 +end + +@testset "step range" begin + r = typemin(Q0f7):eps(Q0f7):typemax(Q0f7) + counter = 0 + for x in r + counter += 1 + end + @test counter == 256 + @test length(r) == 256 + QInt1 = Fixed{Int,1} + @test length(QInt1(0):eps(QInt1):typemax(QInt1)-eps(QInt1)) == typemax(Int) + @test Base.unsafe_length(typemin(QInt1):eps(QInt1):typemax(QInt1)-eps(QInt1)) == -1 + @test_throws OverflowError length(QInt1(-1):eps(QInt1):typemax(QInt1)-eps(QInt1)) +end + @testset "reductions" begin a = Q0f7[0.75, 0.5] acmp = Float64(a[1]) + Float64(a[2]) diff --git a/test/normed.jl b/test/normed.jl index c73b9c8e..80c5949c 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -300,17 +300,38 @@ end @test bswap(N0f8(0.5)) === N0f8(0.5) @test bswap(N0f16(0.5)) === reinterpret(N0f16, 0x0080) @test minmax(N0f8(0.8), N0f8(0.2)) === (N0f8(0.2), N0f8(0.8)) +end - r = reinterpret(N0f8, 0x01):reinterpret(N0f8, 0x01):reinterpret(N0f8, convert(UInt8, 48)) - @test length(r) == 48 - end +@testset "unit range" begin + @test length(N0f8(0):N0f8(1)) == 2 + @test length(N0f8(1):N0f8(0)) == 0 + @test isempty(N0f8(1):N0f8(0)) + @test collect(N0f8(0):N0f8(1)) == N0f8[0, 1] + @test length(0.5N1f7:1.504N1f7) == 2 + @test length(N7f1(0):N7f1(255)) == 256 + NIntW = Normed{UInt,bitwidth(UInt)} + @test length(NIntW(0):NIntW(1)) == 2 + NInt1 = Normed{UInt,1} + @test length(NInt1(0):typemax(NInt1)-oneunit(NInt1)) == typemax(UInt) + @test_throws OverflowError length(NInt1(0):typemax(NInt1)) + @test Base.unsafe_length(NInt1(0):typemax(NInt1)) == 0 # overflow + N64f64 = Normed{UInt128,64} + @test_broken length(N64f64(0):typemax(N64f64)) == UInt128(typemax(UInt64)) + 1 + @test length(N1f63(2):N1f63(0)) == 0 +end - @testset "step range" begin +@testset "step range" begin counter = 0 for x in N0f8(0):eps(N0f8):N0f8(1) counter += 1 end @test counter == 256 + @test length(N0f8(0):eps(N0f8):N0f8(1)) == 256 + r = reinterpret(N0f8, 0x01):reinterpret(N0f8, 0x01):reinterpret(N0f8, UInt8(48)) + @test length(r) == 48 + NInt1 = Normed{UInt,1} + @test length(NInt1(0):NInt1(1):typemax(NInt1)-oneunit(NInt1)) == typemax(UInt) + @test_throws OverflowError length(NInt1(0):NInt1(1):typemax(NInt1)) end @testset "Promotion within Normed" begin From 930f19ac4f52ced400dd28d159daf72cf3fb03e3 Mon Sep 17 00:00:00 2001 From: kimikage Date: Tue, 14 Jan 2020 11:45:20 +0900 Subject: [PATCH 15/23] Fix `isinteger` and Commonize predicates (e.g. `isnan`) (Fixes #120) (#165) --- src/FixedPointNumbers.jl | 5 ++++- src/normed.jl | 4 ---- test/fixed.jl | 33 ++++++++++++++++++++++++++++++++- test/normed.jl | 29 ++++++++++++++++++++++++++--- 4 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index f59013a5..ffb9962f 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -62,7 +62,10 @@ function isapprox(x::FixedPoint, y::FixedPoint; rtol=0, atol=max(eps(x), eps(y)) end # predicates -isinteger(x::FixedPoint{T,f}) where {T,f} = (x.i&(1< (rawone(x) >> 0x1) ? d + oneunit(rawtype(x)) : d) end -isfinite(x::Normed) = true -isnan(x::Normed) = false -isinf(x::Normed) = false - # 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 e0038088..63fb60cb 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -17,7 +17,6 @@ function test_fixed(::Type{T}, f) where {T} if !(typemin(T) < x <= typemax(T)) continue end - # isinteger(x) && @show x fx = convert(T,x) @test convert(T,convert(Float64, fx)) == fx @test convert(T,convert(Float64, -fx)) == -fx @@ -243,6 +242,38 @@ end @test isa(float(one(Fixed{Int32,25})), Float64) end +@testset "predicates" begin + @test isfinite(1Q7f8) + @test !isnan(1Q7f8) + @test !isinf(1Q7f8) + + @testset "isinteger" begin + for T in (Int8, Int16) + @testset "isinteger(::Fixed{$T,$f})" for f = 0:bitwidth(T)-1 + F = Fixed{T,f} + xs = typemin(F):eps(F):typemax(F) + @test all(x -> isinteger(x) == isinteger(float(x)), xs) + end + end + for T in (Int32, Int64) + @testset "isinteger(::Fixed{$T,$f})" for f = 0:bitwidth(T)-1 + F = Fixed{T,f} + fzero, fmax, fmin = zero(F), typemax(F), typemin(F) + if f == 0 + @test isinteger(fzero) & isinteger(fmax) & isinteger(fmin) + else + @test isinteger(fzero) & !isinteger(fmax) & isinteger(fmin) + end + end + end + @testset "isinteger(::Fixed{Int8,8})" begin # TODO: remove this testset + @test !isinteger(Fixed{Int8,8}(-0.5)) + @test isinteger(Fixed{Int8,8}(0.0)) + @test !isinteger(Fixed{Int8,8}(127/256)) + end + end +end + @testset "Show" begin x = Fixed{Int32,5}(0.25) iob = IOBuffer() diff --git a/test/normed.jl b/test/normed.jl index 80c5949c..0151ba14 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -80,9 +80,6 @@ end @testset "conversion" begin x = N0f8(0.5) @test convert(N0f8, x) === x - @test isfinite(x) == true - @test isnan(x) == false - @test isinf(x) == false @test convert(N0f8, 1.1/typemax(UInt8)) == eps(N0f8) @test convert(N6f10, 1.1/typemax(UInt16)*64) == eps(N6f10) @@ -334,6 +331,32 @@ end @test_throws OverflowError length(NInt1(0):NInt1(1):typemax(NInt1)) end +@testset "predicates" begin + @test isfinite(1N8f8) + @test !isnan(1N8f8) + @test !isinf(1N8f8) + + @testset "isinteger" begin + for T in (UInt8, UInt16) + @testset "isinteger(::Normed{$T,$f})" for f = 1:bitwidth(T) + N = Normed{T,f} + xs = typemin(N):eps(N):typemax(N) + @test all(x -> isinteger(x) == isinteger(float(x)), xs) + end + end + for T in (UInt32, UInt64) + @testset "isinteger(::Normed{$T,$f})" for f = 1:bitwidth(T) + N = Normed{T,f} + if f == 1 + @test isinteger(zero(N)) & isinteger(oneunit(N)) + else + @test !isinteger(oneunit(N) - eps(N)) & isinteger(oneunit(N)) + end + end + end + end +end + @testset "Promotion within Normed" begin @test @inferred(promote(N0f8(0.2), N0f8(0.8))) === (N0f8(0.2), N0f8(0.8)) From 0ae5b82b5cca43b89992a346383888971667c50a Mon Sep 17 00:00:00 2001 From: kimikage Date: Wed, 15 Jan 2020 18:39:59 +0900 Subject: [PATCH 16/23] Improve accuracy of `rem` with `Normed` types (e.g. `::Float32 % N0f32`) (Fixes #150) (#166) --- src/normed.jl | 22 ++++++++++++++++++---- test/normed.jl | 4 ++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/normed.jl b/src/normed.jl index 51924ab1..5665d0c7 100644 --- a/src/normed.jl +++ b/src/normed.jl @@ -101,10 +101,24 @@ function _convert(::Type{N}, x::Tf) where {T, f, N <: Normed{T,f}, Tf <: Union{F return reinterpret(N, unsafe_trunc(T, yi >> (ex & bits))) end -rem(x::T, ::Type{T}) where {T <: Normed} = x -rem(x::Normed, ::Type{T}) where {T <: Normed} = reinterpret(T, _unsafe_trunc(rawtype(T), round((rawone(T)/rawone(x))*reinterpret(x)))) -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 +rem(x::N, ::Type{N}) where {N <: Normed} = x +rem(x::Normed, ::Type{N}) where {T, N <: Normed{T}} = reinterpret(N, _unsafe_trunc(T, round((rawone(N)/rawone(x))*reinterpret(x)))) +rem(x::Real, ::Type{N}) where {T, N <: Normed{T}} = reinterpret(N, _unsafe_trunc(T, round(rawone(N)*x))) +rem(x::Float16, ::Type{N}) where {N <: Normed} = rem(Float32(x), N) # avoid overflow +# Float32 and Float64 cannot exactly represent `rawone(N)` with `f` greater than +# the number of their significand bits, resulting in rounding errors (issue #150). +# So, we use another strategy for the large `f`s explained in: +# https://github.com/JuliaMath/FixedPointNumbers.jl/pull/166#issuecomment-574135643 +function rem(x::Float32, ::Type{N}) where {f, N <: Normed{UInt32,f}} + f <= 24 && return reinterpret(N, _unsafe_trunc(UInt32, round(rawone(N) * x))) + r = _unsafe_trunc(UInt32, round(x * @f32(0x1p24))) + reinterpret(N, r << UInt8(f - 24) - unsigned(signed(r) >> 0x18)) +end +function rem(x::Float64, ::Type{N}) where {f, N <: Normed{UInt64,f}} + f <= 53 && return reinterpret(N, _unsafe_trunc(UInt64, round(rawone(N) * x))) + r = _unsafe_trunc(UInt64, round(x * 0x1p53)) + reinterpret(N, r << UInt8(f - 53) - unsigned(signed(r) >> 0x35)) +end function (::Type{T})(x::Normed) where {T <: AbstractFloat} diff --git a/test/normed.jl b/test/normed.jl index 0151ba14..084c18d3 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -200,6 +200,10 @@ end @test 1 % N0f8 == 1 @test 2 % N0f8 == N0f8(0.996) + + # issue #150 + @test all(f -> 1.0f0 % Normed{UInt32,f} == oneunit(Normed{UInt32,f}), 1:32) + @test all(f -> 1.0e0 % Normed{UInt64,f} == oneunit(Normed{UInt64,f}), 1:64) end @testset "bitwise" begin From f79606aea565b4e9834e528dcdf95b9532a768cc Mon Sep 17 00:00:00 2001 From: kimikage Date: Wed, 25 Dec 2019 20:54:04 +0900 Subject: [PATCH 17/23] Commonize `Bool` and `Integer` conversions This includes following breaking changes: - `Bool` throws the InexactError for inputs other than zero/one. - The return type of `Integer(::Normed{T})` is now `T`. - `Integer(::Fixed)` throws the InexactError instead of MethodError. --- src/FixedPointNumbers.jl | 9 +++++++++ src/fixed.jl | 10 ---------- src/normed.jl | 3 --- test/fixed.jl | 11 +++++++++++ test/normed.jl | 10 +++++++++- 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index ffb9962f..092930d8 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -47,6 +47,15 @@ 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) +# conversions +function Base.Bool(x::FixedPoint) + x == zero(x) ? false : x == oneunit(x) ? true : throw(InexactError(:Bool, Bool, x)) +end +function (::Type{Ti})(x::FixedPoint) where {Ti <: Integer} + isinteger(x) || throw(InexactError(:Integer, typeof(x), x)) + floor(Ti, x) +end + """ isapprox(x::FixedPoint, y::FixedPoint; rtol=0, atol=max(eps(x), eps(y))) diff --git a/src/fixed.jl b/src/fixed.jl index a4c9b65f..829ef020 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -81,16 +81,6 @@ Base.BigFloat(x::Fixed{T,f}) where {T,f} = (::Type{TF})(x::Fixed{T,f}) where {TF <: AbstractFloat,T,f} = TF(x.i>>f) + TF(x.i&(one(widen1(T))<>f) -end -function (::Type{TI})(x::Fixed{T,f}) where {TI <: Integer,T,f} - isinteger(x) || throw(InexactError()) - TI(x.i>>f) -end - (::Type{TR})(x::Fixed{T,f}) where {TR <: Rational,T,f} = TR(x.i>>f + (x.i&(1< Date: Thu, 26 Dec 2019 01:32:24 +0900 Subject: [PATCH 18/23] Commonize `Rational` conversions This includes a breaking change in the return type of `Rational(::Fixed)`. --- src/FixedPointNumbers.jl | 1 + src/fixed.jl | 5 +++-- src/normed.jl | 1 - test/fixed.jl | 11 +++++++++++ test/normed.jl | 4 ++++ 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 092930d8..cab126f4 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -55,6 +55,7 @@ function (::Type{Ti})(x::FixedPoint) where {Ti <: Integer} isinteger(x) || throw(InexactError(:Integer, typeof(x), x)) floor(Ti, x) end +Base.Rational{Ti}(x::FixedPoint) where {Ti <: Integer} = Rational{Ti}(Rational(x)) """ isapprox(x::FixedPoint, y::FixedPoint; rtol=0, atol=max(eps(x), eps(y))) diff --git a/src/fixed.jl b/src/fixed.jl index 829ef020..9c50c3ec 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -81,8 +81,9 @@ Base.BigFloat(x::Fixed{T,f}) where {T,f} = (::Type{TF})(x::Fixed{T,f}) where {TF <: AbstractFloat,T,f} = TF(x.i>>f) + TF(x.i&(one(widen1(T))<>f + (x.i&(1< Date: Sat, 28 Dec 2019 12:37:57 +0900 Subject: [PATCH 19/23] Commonize constructor-style conversions --- src/FixedPointNumbers.jl | 13 ++++++++++- src/fixed.jl | 35 ++++++++++++++++-------------- src/normed.jl | 47 ++++++++++++++++++++-------------------- test/fixed.jl | 5 +++++ test/normed.jl | 42 ++++++++++++++++++----------------- 5 files changed, 81 insertions(+), 61 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index cab126f4..160c1331 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -45,7 +45,18 @@ 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) +*(x::Real, ::Type{X}) where {X <: FixedPoint} = _convert(X, x) + +# constructor-style conversions +(::Type{X})(x::Real) where {X <: FixedPoint} = _convert(X, x) + +function (::Type{<:FixedPoint})(x::AbstractChar) + throw(ArgumentError("FixedPoint (Fixed or Normed) cannot be constructed from a Char")) +end +(::Type{X})(x::Complex) where {X <: FixedPoint} = X(convert(real(typeof(x)), x)) +function (::Type{X})(x::Base.TwicePrecision) where {X <: FixedPoint} + floattype(X) === BigFloat ? X(big(x)) : X(convert(floattype(X), x)) +end # conversions function Base.Bool(x::FixedPoint) diff --git a/src/fixed.jl b/src/fixed.jl index 9c50c3ec..24c1ac55 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -24,13 +24,6 @@ struct Fixed{T <: Signed, f} <: FixedPoint{T, f} end end -Fixed{T, f}(x::AbstractChar) where {T,f} = throw(ArgumentError("Fixed cannot be constructed from a Char")) -Fixed{T, f}(x::Complex) where {T,f} = Fixed{T, f}(convert(real(typeof(x)), x)) -Fixed{T, f}(x::Base.TwicePrecision) where {T,f} = Fixed{T, f}(convert(Float64, x)) -Fixed{T,f}(x::Integer) where {T,f} = Fixed{T,f}(round(T, convert(widen1(T),x)<> (bitwidth(T) - f) end -# Conversions -function Normed{T,f}(x::Normed{T2}) where {T <: Unsigned,T2 <: Unsigned,f} - U = Normed{T,f} - y = round((rawone(U)/rawone(x))*reinterpret(x)) - (0 <= y) & (y <= typemax(T)) || throw_converterror(U, x) - reinterpret(U, _unsafe_trunc(T, y)) -end -N0f16(x::N0f8) = reinterpret(N0f16, convert(UInt16, 0x0101*reinterpret(x))) +# constructor-style conversions +function _convert(::Type{N}, x::Normed{T2,f}) where {T, T2, f, N <: Normed{T,f}} + reinterpret(N, convert(T, x.i)) # TODO: input range checking +end -(::Type{U})(x::Real) where {U <: Normed} = _convert(U, x) +function _convert(::Type{N}, x::Normed{T2,f2}) where {T, T2, f, f2, N <: Normed{T,f}} + y = round((rawone(N)/rawone(x))*reinterpret(x)) + (0 <= y) & (y <= typemax(T)) || throw_converterror(N, x) + reinterpret(N, _unsafe_trunc(T, y)) +end + +function _convert(::Type{N}, x::Normed{UInt8,8}) where {N <: Normed{UInt16,16}} # TODO: generalization + reinterpret(N0f16, convert(UInt16, 0x0101*reinterpret(x))) +end -function _convert(::Type{U}, x) where {T, f, U <: Normed{T,f}} +function _convert(::Type{N}, x::Real) where {T, f, N <: Normed{T,f}} if T == UInt128 # for UInt128, we can't widen # the upper limit is not exact - (0 <= x) & (x <= (typemax(T)/rawone(U))) || throw_converterror(U, x) - y = round(rawone(U)*x) + (0 <= x) & (x <= (typemax(T)/rawone(N))) || throw_converterror(N, x) + y = round(rawone(N)*x) else - y = round(widen1(rawone(U))*x) - (0 <= y) & (y <= typemax(T)) || throw_converterror(U, x) + y = round(widen1(rawone(N))*x) + (0 <= y) & (y <= typemax(T)) || throw_converterror(N, x) end - reinterpret(U, _unsafe_trunc(T, y)) + reinterpret(N, _unsafe_trunc(T, y)) end # Prevent overflow (https://discourse.julialang.org/t/saving-greater-than-8-bit-images/6057) -function _convert(::Type{U}, x::Float16) where {T, f, U <: Normed{T,f}} - if Float16(typemax(T)/rawone(U)) > Float32(typemax(T)/rawone(U)) - x == Float16(typemax(T)/rawone(U)) && return typemax(U) +function _convert(::Type{N}, x::Float16) where {T, f, N <: Normed{T,f}} + if Float16(typemax(T)/rawone(N)) > Float32(typemax(T)/rawone(N)) + x == Float16(typemax(T)/rawone(N)) && return typemax(N) end - return _convert(U, Float32(x)) + return _convert(N, Float32(x)) end function _convert(::Type{N}, x::Tf) where {T, f, N <: Normed{T,f}, Tf <: Union{Float32, Float64}} if T === UInt128 && f == 53 diff --git a/test/fixed.jl b/test/fixed.jl index 21f5cec6..033909e4 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -102,6 +102,11 @@ end @test_broken convert(Q1f6, Rational{Int8}(-3//4)) === -0.75Q1f6 @test_broken convert(Q0f7, Rational{Int16}(-3//4)) === -0.75Q0f7 @test_broken convert(Q0f7, Rational{UInt8}(3//4)) === 0.75Q0f7 + + @test convert(Q0f7, Base.TwicePrecision(0.5)) === 0.5Q0f7 + @test_throws InexactError convert(Q7f8, Base.TwicePrecision(0x80, 0x01)) + tp = Base.TwicePrecision(0xFFFFFFFFp-32, 0xFFFFFFFEp-64) + @test convert(Q0f63, tp) === reinterpret(Q0f63, typemax(Int64)) end @testset "test_fixed" begin diff --git a/test/normed.jl b/test/normed.jl index 6f9bdfd5..090685c7 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -96,6 +96,8 @@ end @test_broken convert(N0f8, Rational{Int8}(3//5)) === N0f8(3/5) @test_broken convert(N0f8, Rational{UInt8}(3//5)) === N0f8(3/5) + @test convert(N0f8, Base.TwicePrecision(1.0)) === 1N0f8 + @test convert(Float64, eps(N0f8)) == 1/typemax(UInt8) @test convert(Float32, eps(N0f8)) == 1.0f0/typemax(UInt8) @test convert(BigFloat, eps(N0f8)) == BigFloat(1)/typemax(UInt8) @@ -126,25 +128,25 @@ end for T in (UInt8, UInt16, UInt32, UInt64, UInt128) for Tf in (Float16, Float32, Float64) @testset "Normed{$T,$f}(::$Tf)" for f = 1:bitwidth(T) - U = Normed{T,f} - r = FixedPointNumbers.rawone(U) + N = Normed{T,f} + r = FixedPointNumbers.rawone(N) - @test reinterpret(U(zero(Tf))) == 0x0 + @test reinterpret(N(zero(Tf))) == 0x0 - input_typemax = Tf(typemax(U)) + input_typemax = Tf(typemax(N)) if isinf(input_typemax) - @test reinterpret(U(floatmax(Tf))) >= round(T, floatmax(Tf)) + @test reinterpret(N(floatmax(Tf))) >= round(T, floatmax(Tf)) else - @test reinterpret(U(input_typemax)) >= (typemax(T)>>1) # overflow check + @test reinterpret(N(input_typemax)) >= (typemax(T)>>1) # overflow check end input_upper = Tf(BigFloat(typemax(T)) / r, RoundDown) isinf(input_upper) && continue # for Julia v0.7 - @test reinterpret(U(input_upper)) == T(min(round(BigFloat(input_upper) * r), typemax(T))) + @test reinterpret(N(input_upper)) == T(min(round(BigFloat(input_upper) * r), typemax(T))) input_exp2 = Tf(exp2(bitwidth(T) - f)) isinf(input_exp2) && continue - @test reinterpret(U(input_exp2)) == T(input_exp2) * r + @test reinterpret(N(input_exp2)) == T(input_exp2) * r end end end @@ -161,27 +163,27 @@ end end for Tf in (Float16, Float32, Float64) - @testset "$Tf(::Normed{$Ti})" for Ti in (UInt8, UInt16) - @testset "$Tf(::Normed{$Ti,$f})" for f = 1:bitwidth(Ti) - T = Normed{Ti,f} + @testset "$Tf(::Normed{$T})" for T in (UInt8, UInt16) + @testset "$Tf(::Normed{$T,$f})" for f = 1:bitwidth(T) + N = Normed{T,f} float_err = 0.0 - for i = typemin(Ti):typemax(Ti) - f_expected = Tf(i / BigFloat(FixedPointNumbers.rawone(T))) + for i = typemin(T):typemax(T) + f_expected = Tf(i / BigFloat(FixedPointNumbers.rawone(N))) isinf(f_expected) && break # for Float16(::Normed{UInt16,1}) - f_actual = Tf(reinterpret(T, i)) + f_actual = Tf(reinterpret(N, i)) float_err += abs(f_actual - f_expected) end @test float_err == 0.0 end end - @testset "$Tf(::Normed{$Ti})" for Ti in (UInt32, UInt64, UInt128) - @testset "$Tf(::Normed{$Ti,$f})" for f = 1:bitwidth(Ti) - T = Normed{Ti,f} + @testset "$Tf(::Normed{$T})" for T in (UInt32, UInt64, UInt128) + @testset "$Tf(::Normed{$T,$f})" for f = 1:bitwidth(T) + N = Normed{T,f} error_count = 0 - for i in vcat(Ti(0x00):Ti(0xFF), (typemax(Ti)-0xFF):typemax(Ti)) - f_expected = Tf(i / BigFloat(FixedPointNumbers.rawone(T))) + for i in vcat(T(0x00):T(0xFF), (typemax(T)-0xFF):typemax(T)) + f_expected = Tf(i / BigFloat(FixedPointNumbers.rawone(N))) isinf(f_expected) && break # for Float16() and Float32() - f_actual = Tf(reinterpret(T, i)) + f_actual = Tf(reinterpret(N, i)) f_actual == f_expected && continue f_actual == prevfloat(f_expected) && continue f_actual == nextfloat(f_expected) && continue From cb29336982309076894d63a0748f9ead13c672bc Mon Sep 17 00:00:00 2001 From: kimikage Date: Tue, 21 Jan 2020 20:46:08 +0900 Subject: [PATCH 20/23] Add support for `Normed` construction from `Rational` (Fixes #157) (#169) This also fixes the overflow problem with `Fixed` construction from `Rational`. --- src/fixed.jl | 8 +++++++- src/normed.jl | 8 ++++++++ test/fixed.jl | 7 ++++--- test/normed.jl | 7 ++++--- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/fixed.jl b/src/fixed.jl index 24c1ac55..a989d890 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -62,7 +62,13 @@ function _convert(::Type{F}, x::AbstractFloat) where {T, f, F <: Fixed{T,f}} end function _convert(::Type{F}, x::Rational) where {T, f, F <: Fixed{T,f}} - F(x.num)/F(x.den) # TODO: optimization and input range checking + 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))) + else + throw_converterror(F, x) + end end # unchecked arithmetic diff --git a/src/normed.jl b/src/normed.jl index a1c0daf1..0593567a 100644 --- a/src/normed.jl +++ b/src/normed.jl @@ -100,6 +100,14 @@ function _convert(::Type{N}, x::Tf) where {T, f, N <: Normed{T,f}, Tf <: Union{F return reinterpret(N, unsafe_trunc(T, yi >> (ex & bits))) end +function _convert(::Type{N}, x::Rational) where {T, f, N <: Normed{T,f}} + if 0 <= x <= Rational(typemax(N)) + reinterpret(N, round(T, convert(floattype(T), x) * rawone(N))) + else + throw_converterror(N, x) + end +end + rem(x::N, ::Type{N}) where {N <: Normed} = x rem(x::Normed, ::Type{N}) where {T, N <: Normed{T}} = reinterpret(N, _unsafe_trunc(T, round((rawone(N)/rawone(x))*reinterpret(x)))) rem(x::Real, ::Type{N}) where {T, N <: Normed{T}} = reinterpret(N, _unsafe_trunc(T, round(rawone(N)*x))) diff --git a/test/fixed.jl b/test/fixed.jl index 033909e4..b3da7e59 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -99,9 +99,10 @@ end @test_throws InexactError convert(Fixed{Int8, 7}, 128) @test convert(Q2f5, -1//2) === -0.5Q2f5 - @test_broken convert(Q1f6, Rational{Int8}(-3//4)) === -0.75Q1f6 - @test_broken convert(Q0f7, Rational{Int16}(-3//4)) === -0.75Q0f7 - @test_broken convert(Q0f7, Rational{UInt8}(3//4)) === 0.75Q0f7 + @test convert(Q1f6, Rational{Int8}(-3//4)) === -0.75Q1f6 + @test convert(Q0f7, Rational{Int16}(-3//4)) === -0.75Q0f7 + @test convert(Q0f7, Rational{UInt8}(3//4)) === 0.75Q0f7 + @test_throws ArgumentError convert(Q0f7, typemax(Rational{Int8})) @test convert(Q0f7, Base.TwicePrecision(0.5)) === 0.5Q0f7 @test_throws InexactError convert(Q7f8, Base.TwicePrecision(0x80, 0x01)) diff --git a/test/normed.jl b/test/normed.jl index 090685c7..56831d55 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -92,9 +92,10 @@ end @test convert(N0f8, 1.1f0/typemax(UInt8)) == eps(N0f8) - @test_broken convert(N0f8, 1//255) === eps(N0f8) - @test_broken convert(N0f8, Rational{Int8}(3//5)) === N0f8(3/5) - @test_broken convert(N0f8, Rational{UInt8}(3//5)) === N0f8(3/5) + @test convert(N0f8, 1//255) === eps(N0f8) + @test convert(N0f8, Rational{Int8}(3//5)) === N0f8(3/5) + @test convert(N0f8, Rational{UInt8}(3//5)) === N0f8(3/5) + @test_throws ArgumentError convert(N0f8, typemax(Rational{UInt8})) @test convert(N0f8, Base.TwicePrecision(1.0)) === 1N0f8 From 62cef1cfef647932ae443ec98ebee1d0d6607abd Mon Sep 17 00:00:00 2001 From: kimikage Date: Mon, 3 Feb 2020 00:31:06 +0900 Subject: [PATCH 21/23] Optimize `Fixed` --> `Float` conversions (#172) This closes #171. --- src/fixed.jl | 8 ++++---- test/fixed.jl | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/fixed.jl b/src/fixed.jl index a989d890..df747140 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -85,10 +85,10 @@ rem(x::Integer, ::Type{Fixed{T,f}}) where {T,f} = Fixed{T,f}(rem(x,T)<>f) + BigFloat(x.i&(one(widen1(T))<>f) + TF(x.i&(one(widen1(T))< Date: Mon, 3 Feb 2020 02:18:31 +0900 Subject: [PATCH 22/23] Modify Fixed constructors (#170) This adds explicit input range checks. As a result, the constructors throw `ArgumentError` instead of `InexactError` for out-of-range inputs. --- src/fixed.jl | 28 +++++++++++++++++++++++++--- test/fixed.jl | 22 +++++++++++++++------- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/fixed.jl b/src/fixed.jl index df747140..d185e58c 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -48,17 +48,39 @@ 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(((1<> f) <= x) & (x <= (typemax(T) >> f)) + reinterpret(F, unsafe_trunc(T, x) << f) + else + throw_converterror(F, x) + end end function _convert(::Type{F}, x::AbstractFloat) where {T, f, F <: Fixed{T,f}} - reinterpret(F, round(T, trunc(widen1(T),x)< Date: Mon, 3 Feb 2020 23:26:45 +0900 Subject: [PATCH 23/23] Version 0.8.0 (#175) --- .travis.yml | 2 +- Project.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5c8dca12..03b472c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ os: - osx julia: - 1.0 - - 1.2 + - 1 - nightly notifications: email: false diff --git a/Project.toml b/Project.toml index 4aecfcf2..92043e1d 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "FixedPointNumbers" uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" -version = "0.7.0" +version = "0.8.0" [compat] julia = "1"