From 652fac39cfbee130a50dccac51765cef34a07991 Mon Sep 17 00:00:00 2001 From: kimikage Date: Tue, 30 Apr 2024 19:45:33 +0900 Subject: [PATCH 01/40] Backport partially #189 --- src/FixedPointNumbers.jl | 32 ++++++++++++++++++++++++++------ src/fixed.jl | 3 +++ src/utilities.jl | 1 + test/fixed.jl | 7 +++++-- test/normed.jl | 5 ++++- 5 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 7e4ab7d3..00edd93e 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -49,6 +49,7 @@ rawtype(::Type{X}) where {T, X <: FixedPoint{T}} = T # traits based on static parameters signbits(::Type{X}) where {T, X <: FixedPoint{T}} = T <: Unsigned ? 0 : 1 +nbitsint(::Type{X}) where {X <: FixedPoint} = bitwidth(X) - nbitsfrac(X) - signbits(X) # construction using the (approximate) intended value, i.e., N0f8 *(x::Real, ::Type{X}) where {X <: FixedPoint} = _convert(X, x) @@ -260,19 +261,38 @@ function length(r::StepRange{<:FixedPoint}) return div((stop - start) + step, step) end +hasalias(::Type) = false +hasalias(::Type{X}) where {T<:NotBiggerThanInt64, f, X<:FixedPoint{T,f}} = f isa Int + # 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} - print(io, typechar(X)) - f = nbitsfrac(X) - m = bitwidth(X)-f-signbits(X) - print(io, m, 'f', f) + if hasalias(X) + f = nbitsfrac(X) + m = nbitsint(X) + write(io, typechar(X)) + m > 9 && write(io, Char(m ÷ 10 + 0x30)) + write(io, Char(m % 10 + 0x30), 'f') + f > 9 && write(io, Char(f ÷ 10 + 0x30)) + write(io, Char(f % 10 + 0x30)) + else + print(io, X) + end io end + function show(io::IO, x::FixedPoint{T,f}) where {T,f} + compact = get(io, :compact, false)::Bool log10_2 = 0.3010299956639812 - show(io, round(convert(Float64,x), digits=ceil(Int, f * log10_2))) - get(io, :compact, false) || showtype(io, typeof(x)) + val = round(convert(Float64, x), digits=ceil(Int, f * log10_2)) + if compact + show(io, val) + elseif hasalias(typeof(x)) + show(io, val) + showtype(io, typeof(x)) + else + print(io, typeof(x), '(', val, ')') + end end function Base.showarg(io::IO, a::Array{T}, toplevel) where {T<:FixedPoint} diff --git a/src/fixed.jl b/src/fixed.jl index 5beb98df..4596b34a 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -24,6 +24,9 @@ struct Fixed{T <: Signed, f} <: FixedPoint{T, f} end end +# TODO: remove this +hasalias(::Type{F}) where {F <: Union{Fixed{Int8,8},Fixed{Int16,16},Fixed{Int32,32},Fixed{Int64,64}}} = false + typechar(::Type{X}) where {X <: Fixed} = 'Q' for T in (Int8, Int16, Int32, Int64) diff --git a/src/utilities.jl b/src/utilities.jl index 2c685de8..0976dd08 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -20,6 +20,7 @@ const LongInts = Union{Int64, UInt64, Int128, UInt128, BigInt} const ShorterThanInt = Int === Int32 ? ShortInts : Union{ShortInts, Int32, UInt32} const NotBiggerThanInt = Union{ShorterThanInt, Int, UInt} +const NotBiggerThanInt64 = Union{ShortInts, Int32, UInt32, Int64, UInt64} const SShorterThanInt = typeintersect(ShorterThanInt, Signed) const UShorterThanInt = typeintersect(ShorterThanInt, Unsigned) diff --git a/test/fixed.jl b/test/fixed.jl index 1c958a01..d7881368 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -1,6 +1,9 @@ using FixedPointNumbers, Statistics, Test using FixedPointNumbers: bitwidth +# FIXME: Remove this (borrowed from PR #224) +SP = VERSION >= v"1.6.0-DEV.771" ? " " : "" # JuliaLang/julia #37085 + function test_op(fun::F, ::Type{T}, fx, fy, fxf, fyf, tol) where {F,T} # Make sure that the result is representable (typemin(T) <= fun(fxf, fyf) <= typemax(T)) || return nothing @@ -492,11 +495,11 @@ end @test String(take!(iob)) == "-21845.33334Q15f16" show(iob, Fixed{Int128,64}(-1.2345e6)) - @test_broken String(take!(iob)) == "Fixed{Int128,64}(-1.2345e6)" # "Q63f64" is not defined + @test String(take!(iob)) == "Fixed{Int128,$(SP)64}(-1.2345e6)" # TODO: remove this test show(iob, reinterpret(Fixed{Int8,8}, signed(0xaa))) - @test_broken String(take!(iob)) == "Fixed{Int8,8}(-0.336)" # "Q-1f8" is invalid + @test String(take!(iob)) == "Fixed{Int8,$(SP)8}(-0.336)" end @testset "summary" begin diff --git a/test/normed.jl b/test/normed.jl index 4ed423cd..c5840a00 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -1,6 +1,9 @@ using FixedPointNumbers, Statistics, Test using FixedPointNumbers: bitwidth +# FIXME: Remove this (borrowed from PR #224) +SP = VERSION >= v"1.6.0-DEV.771" ? " " : "" # JuliaLang/julia #37085 + @testset "domain of f" begin @test_throws DomainError zero(Normed{UInt8,-1}) @test_throws DomainError zero(Normed{UInt8,0}) @@ -479,7 +482,7 @@ end @test String(take!(iob)) == "43691.33333N16f16" show(iob, Normed{UInt128,64}(1.2345e6)) - @test_broken String(take!(iob)) == "Normed{UInt128,64}(1.2345e6)" # "N64f64" is not defined + @test String(take!(iob)) == "Normed{UInt128,$(SP)64}(1.2345e6)" end @testset "summary" begin From d8ac44820074381d86ecf8ab91316194fc81d264 Mon Sep 17 00:00:00 2001 From: kimikage Date: Fri, 24 Jul 2020 20:30:39 +0900 Subject: [PATCH 02/40] Improve `throw_converterror` (#205) This uses the type aliases for the message and avoids showing huge numbers. This also reduces the compilation time, i.e. the CI time. --- src/FixedPointNumbers.jl | 16 ++++++++++------ test/fixed.jl | 19 ++++++++++++------- test/normed.jl | 9 +++++++-- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 00edd93e..4751ca3a 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -337,13 +337,17 @@ 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{X}, x) where {X <: FixedPoint} - n = 2^bitwidth(X) - bitstring = bitwidth(X) == 8 ? "an 8-bit" : "a $(bitwidth(X))-bit" +@noinline function throw_converterror(::Type{X}, @nospecialize(x)) where X <: FixedPoint + nbits = bitwidth(rawtype(X)) io = IOBuffer() - 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")) + showtype(io, X) + print(io, " is ") + print(io, nbits == 8 ? "an " : "a ", nbits, "-bit type representing ") + print(io, nbits <= 16 ? string(2^nbits) : "2^$nbits", " values from ") + print(IOContext(io, :compact=>true), typemin(X), " to ") + print(IOContext(io, :compact=>true), typemax(X), "; ") + print(io, "cannot represent ", x) + throw(ArgumentError(String(take!(io)))) end rand(::Type{T}) where {T <: FixedPoint} = reinterpret(T, rand(rawtype(T))) diff --git a/test/fixed.jl b/test/fixed.jl index d7881368..ee95c41c 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -93,20 +93,25 @@ end @test_throws ArgumentError one(Q0f15) @test_throws ArgumentError oneunit(Q0f31) @test_throws ArgumentError one(Fixed{Int8,8}) # TODO: remove this at end of its support + + @test_throws ArgumentError convert(Q0f7, 0.999) + @test_throws ArgumentError convert(Q0f7, 1.0) + @test_throws ArgumentError convert(Q0f7, 1) + @test_throws ArgumentError convert(Q0f7, 2) + + ret = @test_throws ArgumentError Q0f7(127) + msg = ret.value.msg + @test occursin("Q0f7 is an 8-bit type representing 256 values from -1.0 to 0.992;", msg) + ret = @test_throws ArgumentError convert(Fixed{Int128,100}, 10.0^9) + msg = ret.value.msg + @test occursin("Fixed{Int128,$(SP)100} is a 128-bit type representing 2^128 values", msg) 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) - @test_throws ArgumentError convert(Fixed{Int8, 7}, 0.999) - @test_throws ArgumentError convert(Fixed{Int8, 7}, 1.0) - @test_throws ArgumentError convert(Fixed{Int8, 7}, 1) - @test_throws ArgumentError convert(Fixed{Int8, 7}, 2) - @test_throws ArgumentError convert(Fixed{Int8, 7}, 128) - @test_throws ArgumentError convert(Fixed{Int8, 7}, 1.0) @test convert(Q0f7, -128.5/128) == -1 - @test convert(Q0f7, -0.75f0) == -0.75 @test convert(Q0f7, Float16(-0.75)) == -0.75 @test convert(Q0f7, BigFloat(-0.75)) == -0.75 diff --git a/test/normed.jl b/test/normed.jl index c5840a00..bfc6e0df 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -72,7 +72,6 @@ end @testset "inexactness" begin # TODO: change back to InexactError when it allows message strings @test_throws ArgumentError N0f8(2) - @test_throws ArgumentError N0f8(255) @test_throws ArgumentError N0f8(0xff) @test_throws ArgumentError N0f16(2) @test_throws ArgumentError N0f16(0xff) @@ -80,7 +79,13 @@ end @test_throws ArgumentError convert(N0f8, typemax(N6f10)) @test_throws ArgumentError convert(N0f16, typemax(N6f10)) @test_throws ArgumentError convert(Normed{UInt128,100}, 10^9) - @test_throws ArgumentError convert(Normed{UInt128,100}, 10.0^9) + + ret = @test_throws ArgumentError N0f8(255) + msg = ret.value.msg + @test occursin("N0f8 is an 8-bit type representing 256 values from 0.0 to 1.0;", msg) + ret = @test_throws ArgumentError convert(Normed{UInt128,100}, 10.0^9) + msg = ret.value.msg + @test occursin("Normed{UInt128,$(SP)100} is a 128-bit type representing 2^128 values", msg) end @testset "conversion" begin From efa40a62c513acc46174d35a8aeecb753a98905a Mon Sep 17 00:00:00 2001 From: kimikage Date: Thu, 6 Aug 2020 22:42:07 +0900 Subject: [PATCH 03/40] Systemize fixed point types to be tested (#208) This abstracts the selection of the types to be tested and makes it more systematic. As a result, we can reduce impractical test cases (e.g. for the 128-bit types) without significantly compromising the test quality. This also reorders some test sets. --- test/common.jl | 133 ++++++++++++++ test/fixed.jl | 464 ++++++++++++++++++++++++------------------------- test/normed.jl | 426 ++++++++++++++++++++++----------------------- test/traits.jl | 18 +- 4 files changed, 573 insertions(+), 468 deletions(-) create mode 100644 test/common.jl diff --git a/test/common.jl b/test/common.jl new file mode 100644 index 00000000..cfd0e63d --- /dev/null +++ b/test/common.jl @@ -0,0 +1,133 @@ +using FixedPointNumbers, Statistics, Test +using FixedPointNumbers: bitwidth, rawtype, nbitsfrac + +# FIXME: Remove this comment (SP is borrowed from PR #224) +SP = VERSION >= v"1.6.0-DEV.771" ? " " : "" # JuliaLang/julia #37085 + +""" + target(X::Type, Ss...; ex = :default) + +Return a generator which enumerates the target types for testing. + +# Arguments +- `X`: target base type +- `Ss`: symbols for specifying the target raw types + - `:i*` : a `Signed` type if `X === Fixed`, or an `Unsigned` type if `X === Normed` + - `:s*` : a `Signed` type (not yet supported) + - `:u*` : an `Unsigned` type (not yet supported) +- `ex`: exhaustivity of `f`s (see also the [`target_f`](@ref) function) + - `:heavy`: all supported `f`s + - `:default`: same as `:heavy` for 8-/16-bit types, and same as `:light` otherwise + - `:light`: important `f`s for byte boundaries and floating point types + - `:thin`: maximum and half `f`s per type + +# Example +```julia +julia> collect(target(Normed, :i8, :i32; ex = :default)) +21-element Array{DataType,1}: + Normed{UInt8,1} + Normed{UInt8,2} + Normed{UInt8,3} + Normed{UInt8,4} + Normed{UInt8,5} + Normed{UInt8,6} + Normed{UInt8,7} + Normed{UInt8,8} + Normed{UInt32,1} + Normed{UInt32,7} + Normed{UInt32,8} + Normed{UInt32,9} + Normed{UInt32,10} + Normed{UInt32,11} + Normed{UInt32,15} + Normed{UInt32,16} + Normed{UInt32,17} + Normed{UInt32,23} + Normed{UInt32,24} + Normed{UInt32,31} + Normed{UInt32,32} +``` +""" +function target(X::Type, Ss...; ex = :default) + Ts = symbol_to_inttype.(X, Ss) + (X{T,f} for T in Ts for f in target_f(X, T; ex = ex)) +end +target(X::Type; ex = :default) = target(X, :i8, :i16, :i32, :i64, :i128; ex = ex) + +""" + target_f(X::Type, T::Type; ex = :default) + +Return a tuple or range of the number of fractional bits `f` to be tested. + +# Arguments +The `X` specifies the target base type, i.e. `Fixed` or `Normed`, and the `T` +specifies the target raw type. + +## `ex` keyword +The `ex` specifies the exhaustivity of `f`s. +The following are examples of `target_f(Normed, T)`. The marker `x` means the +target and the marker `-` means not the target. + +### `:heavy` -- all supported `f`s +``` + | 3 2 1 | + f |2 1 0 9 8 7 6 5:4 3 2 1 0 9 8 7:6 5 4 3 2 1 0 9:8 7 6 5 4 3 2 1| +T == UInt8 | : : :x x x x x x x x| +T == UInt16 | : :x x x x x x x x:x x x x x x x x| +T == UInt32 |x x x x x x x x:x x x x x x x x:x x x x x x x x:x x x x x x x x| +``` +### `:default` -- same as `:heavy` for 8-/16-bit types, and same as `:light` otherwise +``` + | 3 2 1 | + f |2 1 0 9 8 7 6 5:4 3 2 1 0 9 8 7:6 5 4 3 2 1 0 9:8 7 6 5 4 3 2 1| +T == UInt8 | : : :x x x x x x x x| +T == UInt16 | : :x x x x x x x x:x x x x x x x x| +T == UInt32 |x x - - - - - -:x x - - - - - x:x x - - - x x x:x x - - - - - x| +``` + +### `:light` -- important `f`s for byte boundaries and floating point types +``` + | 3 2 1 | + f |2 1 0 9 8 7 6 5:4 3 2 1 0 9 8 7:6 5 4 3 2 1 0 9:8 7 6 5 4 3 2 1| +T == UInt8 | : : :x x - - - - - x| +T == UInt16 | : :x x - - - x x x:x x - - - - - x| +T == UInt32 |x x - - - - - -:x x - - - - - x:x x - - - x x x:x x - - - - - x| + | | + +--precision(Float32) +--precision(Float16) +``` + +### `:thin` -- maximum and half `f`s per type +``` + | 3 2 1 | + f |2 1 0 9 8 7 6 5:4 3 2 1 0 9 8 7:6 5 4 3 2 1 0 9:8 7 6 5 4 3 2 1| +T == UInt8 | : : :x - - - x - - -| +T == UInt16 | : :x - - - - - - -:x - - - - - - -| +T == UInt32 |x - - - - - - -:- - - - - - - -:x - - - - - - -:- - - - - - - -| +``` +""" +function target_f(X::Type, T::Type{<:Integer}; ex = :default) + f_min = X === Fixed ? 0 : 1 + f_max = bitwidth(T) - (T <: Signed) - 1 + f_min + ex === :heavy && return f_min:f_max + ex === :default && bitwidth(T) <= 16 && return f_min:f_max + ex === :thin && return ((f_max + 1) ÷ 2, f_max) + if ex === :light || ex === :default + itr = Iterators.filter(x -> x <= f_max, target_f_series(X, T)) + return (itr...,) + end + error() +end + +target_f_series(::Type{Fixed}, T::Type{<:Integer}) = + (0, 1, 7, 8, 9, + 10, 11, 15, 16, 17, + 23, 24, 31, 32, 33, + 52, 53, 63, 64, 65, + 112, 113, 127) + +target_f_series(::Type{Normed}, T::Type{<:Integer}) = + (1, 7, 8, 9, + 10, 11, 15, 16, 17, + 23, 24, 31, 32, 33, + 52, 53, 63, 64, 65, + 112, 113, 127, 128) diff --git a/test/fixed.jl b/test/fixed.jl index ee95c41c..6f8a33bd 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -1,57 +1,59 @@ -using FixedPointNumbers, Statistics, Test -using FixedPointNumbers: bitwidth +include("common.jl") -# FIXME: Remove this (borrowed from PR #224) -SP = VERSION >= v"1.6.0-DEV.771" ? " " : "" # JuliaLang/julia #37085 - -function test_op(fun::F, ::Type{T}, fx, fy, fxf, fyf, tol) where {F,T} - # Make sure that the result is representable - (typemin(T) <= fun(fxf, fyf) <= typemax(T)) || return nothing - @assert abs(fun(fx, fy) - convert(T, fun(fxf, fyf))) <= tol - @assert abs(convert(Float64, fun(fx, fy)) - fun(fxf, fyf)) <= tol +function symbol_to_inttype(::Type{Fixed}, s::Symbol) + d = Dict(:i8 => Int8, :i16 => Int16, :i32 => Int32, :i64 => Int64, :i128 => Int128) + d[s] end -function test_fixed(::Type{T}, f) where {T} - values = [-10:0.01:10; -180:.01:-160; 160:.01:180] - tol = 2.0^-f +function test_op(fun::Fun, fx::F, fy::F, fxf, fyf, tol) where {Fun, F} + # Make sure that the result is representable + zf = fun(fxf, fyf) + typemin(F) <= zf <= typemax(F) || return nothing + z = fun(fx, fy) + @assert abs(z - convert(F, zf)) <= tol + @assert abs(convert(Float64, z) - zf) <= tol +end + +function test_fixed(::Type{F}) where {F} + tol = Float64(eps(F)) + v = [-10:0.01:10; -180:.01:-160; 160:.01:180] + # Ignore values outside the representable range + values = filter(x -> typemin(F) < x <= typemax(F), v) for x in values - # Ignore values outside the representable range - # typemin <, otherwise for -(-0.5) > typemax - if !(typemin(T) < x <= typemax(T)) - continue - end - fx = convert(T,x) - @test convert(T,convert(Float64, fx)) == fx - @test convert(T,convert(Float64, -fx)) == -fx - @test convert(Float64, -fx) == -convert(Float64, fx) - + fx = convert(F, x) fxf = convert(Float64, fx) - rx = convert(Rational{BigInt},fx) - @assert isequal(fx,rx) == isequal(hash(fx),hash(rx)) + @test convert(F, convert(Float64, fx)) === fx + @test convert(F, convert(Float64, -fx)) === -fx + @test convert(Float64, -fx) == -convert(Float64, fx) - for y in values - if !(typemin(T) < y <= typemax(T)) - continue - end + rx = convert(Rational{BigInt}, fx) + @assert isequal(fx, rx) == isequal(hash(fx), hash(rx)) - fy = convert(T,y) + for y in values + fy = convert(F, y) fyf = convert(Float64, fy) @assert fx==fy || x!=y - @assert fx=y + @assert fx=y @assert fx<=fy || x>y - test_op(+, T, fx, fy, fxf, fyf, tol) - test_op(-, T, fx, fy, fxf, fyf, tol) - test_op(*, T, fx, fy, fxf, fyf, tol) - fy != 0 && test_op(/, T, fx, fy, fxf, fyf, tol) + test_op(+, fx, fy, fxf, fyf, tol) + test_op(-, fx, fy, fxf, fyf, tol) + test_op(*, fx, fy, fxf, fyf, tol) + fy != 0 && test_op(/, fx, fy, fxf, fyf, tol) - @assert isequal(fx,fy) == isequal(hash(fx),hash(fy)) + @assert isequal(fx, fy) === isequal(hash(fx), hash(fy)) end end end +@testset "test_fixed" begin + for F in target(Fixed, :i8, :i16, :i32; ex = :thin) + test_fixed(F) + 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}) @@ -87,6 +89,21 @@ end @test FixedPointNumbers.fracmask(0Q0f7) === signed(0x7F) end +@testset "limits and identities" begin + @testset "$F" for F in target(Fixed) + T, f = rawtype(F), nbitsfrac(F) + @test zero(F) == 0 + f < bitwidth(T) - 1 && @test one(F) == 1 + f < bitwidth(T) - 1 && @test one(F) * oneunit(F) == oneunit(F) + @test typemin(F) == typemin(T) >> f + @test typemax(F) == typemax(T)//big"2"^f + @test floatmin(F) === eps(F) == 2.0^-f # issue #79 + @test floatmax(F) === typemax(F) + @test eps(zero(F)) === eps(typemax(F)) + @test sizeof(F) == sizeof(T) + end +end + @testset "inexactness" begin # TODO: change back to InexactError when it allows message strings @test_throws ArgumentError Q0f7(-2) @@ -107,6 +124,13 @@ end @test occursin("Fixed{Int128,$(SP)100} is a 128-bit type representing 2^128 values", msg) end +@testset "disambiguation constructors" begin + @test_throws ArgumentError Fixed{Int32,16}('a') + @test_throws InexactError Fixed{Int32,16}(complex(1.0, 1.0)) + @test Fixed{Int32,16}(complex(1.0, 0.0)) == 1 + @test Fixed{Int32,16}(Base.TwicePrecision(1.0, 0.0)) == 1 +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) @@ -129,24 +153,120 @@ end @test convert(Q0f63, tp) === reinterpret(Q0f63, typemax(Int64)) end -@testset "test_fixed" begin - for (TI, f) in [(Int8, 7), (Int16, 8), (Int16, 10), (Int32, 16)] - T = Fixed{TI,f} - # println(" Testing $T") - test_fixed(T, f) +@testset "bool conversions" begin + @test convert(Bool, 0.0Q1f6) === false + @test convert(Bool, 1.0Q1f6) === true + @test_throws InexactError convert(Bool, 0.5Q1f6) + @test_throws InexactError convert(Bool, -1Q1f6) + @test_broken convert(Bool, Fixed{Int8,8}(0.2)) # TODO: remove this +end + +@testset "integer conversions" begin + @test convert(Int, Q1f6(1)) === 1 + @test convert(Integer, Q1f6(1)) === Int8(1) + @test convert(UInt, 1Q1f6) === UInt(1) + @test_throws InexactError convert(Integer, 0.5Q1f6) + @test_throws InexactError convert(Int8, 256Q9f6) +end + +@testset "rational conversions" begin + @test convert(Rational, -0.75Q1f6) === Rational{Int8}(-3//4) + @test convert(Rational, -0.75Q0f7) === Rational{Int16}(-3//4) + @test convert(Rational{Int}, -0.75Q0f7) === Rational{Int}(-3//4) + + @test rationalize(-0.75Q3f4) === Rational{Int}(-3//4) + @test rationalize(Int16, 0.81Q3f4) === Rational{Int16}(13//16) + @test rationalize(-0.81Q3f4, tol=0.02) === Rational{Int}(-13//16) + @test rationalize(Int8, -0.81Q3f4, tol=0.07) === Rational{Int8}(-3//4) +end + +@testset "BigFloat conversions" begin + @test convert(BigFloat, -0.75Q0f7)::BigFloat == big"-0.75" + + @test big(Q7f0) === BigFloat # !== BigInt + @test big(0.75Q3f4)::BigFloat == big"0.75" +end + +@testset "float/floattype" begin + @test float(0.75Q3f4) === 0.75f0 + @test float(0.75Q19f12) === 0.75 + @test float(0.75Q7f24) === 0.75 + @test float(0.75Q10f53)::BigFloat == big"0.75" + + @testset "floattype($F)" for F in target(Fixed, :i8, :i16, :i32, :i64; ex = :heavy) + @test typemax(F) <= maxintfloat(floattype(F)) + end +end + +@testset "conversions to float" begin + for T in (Float16, Float32, Float64) + @test isa(convert(T, Q0f7(0.3)), T) + end + + for Tf in (Float16, Float32, Float64) + @testset "$Tf(::$F)" for F in target(Fixed, :i8, :i16) + T, f = rawtype(F), nbitsfrac(F) + float_err = 0.0 + for i = typemin(T):typemax(T) + f_expected = Tf(i * BigFloat(2)^-f) + f_actual = Tf(reinterpret(F, i)) + float_err += abs(f_actual - f_expected) + end + @test float_err == 0.0 + end + @testset "$Tf(::$F)" for F in target(Fixed, :i32, :i64, :i128) + T, f = rawtype(F), nbitsfrac(F) + error_count = 0 + for i in vcat(typemin(T):(typemin(T)+0xFF), + -T(0xFF):T(0xFF), + (typemax(T)-0xFF):typemax(T)) + f_expected = Tf(i * BigFloat(2)^-f) + isinf(f_expected) && break # for Float16() and Float32() + f_actual = Tf(reinterpret(F, i)) + f_actual == f_expected && continue + error_count += 1 + end + @test error_count == 0 + end end end +@testset "fractional fixed-point numbers" begin + # test all-fractional fixed-point numbers (issue #104) + for F in (Q0f7, Q0f15, Q0f31, Q0f63) + tmax = typemax(F) + tol = (tmax + BigFloat(1.0)) / bitwidth(F) + r = range(-1, stop=BigFloat(tmax)-tol, length=50) + @test all(x -> abs(F(x) - x) <= tol, r) + end +end + +@testset "type modulus" begin + T = Fixed{Int8,7} + for i = -1.0:0.1:typemax(T) + @test i % T === T(i) + end + @test ( 1.5 % T).i == round(Int, 1.5*128) % Int8 + @test (-0.3 % T).i == round(Int, -0.3*128) % Int8 + + T = Fixed{Int16,9} + for i = -64.0:0.1:typemax(T) + @test i % T === T(i) + end + @test ( 65.2 % T).i == round(Int, 65.2*512) % Int16 + @test (-67.2 % T).i == round(Int, -67.2*512) % Int16 +end + @testset "rounding" begin - for T in (Int8, Int16, Int32, Int64) + for sym in (:i8, :i16, :i32, :i64) + T = symbol_to_inttype(Fixed, sym) 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} + @testset "rounding $F" for F in target(Fixed, sym) 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) @@ -187,28 +307,73 @@ end @test_throws InexactError floor(UInt, -eps(Q0f7)) end -@testset "modulus" begin - T = Fixed{Int8,7} - for i = -1.0:0.1:typemax(T) - @test i % T === T(i) +@testset "approx" begin + @testset "approx $F" for F in target(Fixed, :i8, :i16; ex = :light) + xs = typemin(F):eps(F):typemax(F)-eps(F) + @test all(x -> x ≈ x + eps(F), xs) + @test all(x -> x + eps(F) ≈ x, xs) + @test !any(x -> x - eps(F) ≈ x + eps(F), xs) end - @test ( 1.5 % T).i == round(Int, 1.5*128) % Int8 - @test (-0.3 % T).i == round(Int, -0.3*128) % Int8 +end - T = Fixed{Int16,9} - for i = -64.0:0.1:typemax(T) - @test i % T === T(i) - end - @test ( 65.2 % T).i == round(Int, 65.2*512) % Int16 - @test (-67.2 % T).i == round(Int, -67.2*512) % Int16 +@testset "clamp" begin + @test clamp(0.5Q0f7, -0.8Q0f7, 0.8Q0f7) === 0.5Q0f7 + @test clamp(0.5Q0f7, 0.75Q0f7, 0.8Q0f7) === 0.75Q0f7 + @test clamp(0.5Q0f7, -0.8Q0f7, 0.25Q0f7) === 0.25Q0f7 + @test clamp(0.5, -0.8Q0f7, 0.8Q0f7) === 0.5 + @test clamp(0.5f0, 0.75Q0f7, 0.8Q0f7) === 0.75f0 + @test clamp(0.5Q0f15, -0.8Q0f7, 0.25Q0f7) === 0.25Q0f15 + @test clamp(0.5Q0f7, -Inf, Inf) === 0.5 + @test clamp(0.5, Q0f7) === 0.5Q0f7 + @test clamp(-1.5f0, Q0f7) === -1.0Q0f7 + @test clamp(1.5Q1f6, Q0f7) === 0.992Q0f7 end -@testset "testapprox" begin - @testset "approx $T" for T in [Fixed{Int8,7}, Fixed{Int16,8}, Fixed{Int16,10}] - xs = typemin(T):eps(T):typemax(T)-eps(T) - @test all(x -> x ≈ x + eps(T), xs) - @test all(x -> x + eps(T) ≈ x, xs) - @test !any(x -> x - eps(T) ≈ x + eps(T), xs) +@testset "sign-related functions" begin + @test_throws Exception signed(Q0f7) + @test_throws Exception signed(0.5Q0f7) + @test_throws Exception unsigned(Q0f7) + @test_throws Exception unsigned(0.5Q0f7) + @test copysign(0.5Q0f7, 0x1) === 0.5Q0f7 + @test copysign(0.5Q0f7, -1) === -0.5Q0f7 + @test flipsign(0.5Q0f7, 0x1) === 0.5Q0f7 + @test flipsign(0.5Q0f7, -1) === -0.5Q0f7 + @test_throws ArgumentError sign(0Q0f7) + @test sign(0Q1f6) === 0Q1f6 + @test sign(0.5Q1f6) === 1Q1f6 + @test sign(-0.5Q1f6) === -1Q1f6 + @test signbit(0.5Q0f7) === false + @test signbit(-0.5Q0f7) === true +end + +@testset "bitwise" begin + @test bswap(Q0f7(0.5)) === Q0f7(0.5) + @test bswap(Q0f15(0.5)) === reinterpret(Q0f15, signed(0x0040)) +end + +@testset "predicates" begin + @test isfinite(1Q7f8) + @test !isnan(1Q7f8) + @test !isinf(1Q7f8) + + @testset "isinteger" begin + @testset "isinteger(::$F)" for F in target(Fixed, :i8, :i16) + xs = typemin(F):eps(F):typemax(F) + @test all(x -> isinteger(x) == isinteger(float(x)), xs) + end + @testset "isinteger(::$F)" for F in target(Fixed, :i32, :i64, :i128) + fzero, fmax, fmin = zero(F), typemax(F), typemin(F) + if nbitsfrac(F) == 0 + @test isinteger(fzero) & isinteger(fmax) & isinteger(fmin) + else + @test isinteger(fzero) & !isinteger(fmax) & isinteger(fmin) + 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 @@ -264,186 +429,15 @@ end @test varm(a, m) === varm(af, m) end -@testset "bool conversions" begin - @test convert(Bool, 0.0Q1f6) === false - @test convert(Bool, 1.0Q1f6) === true - @test_throws InexactError convert(Bool, 0.5Q1f6) - @test_throws InexactError convert(Bool, -1Q1f6) - @test_broken convert(Bool, Fixed{Int8,8}(0.2)) # TODO: remove this -end - -@testset "integer conversions" begin - @test convert(Int, Q1f6(1)) === 1 - @test convert(Integer, Q1f6(1)) === Int8(1) - @test convert(UInt, 1Q1f6) === UInt(1) - @test_throws InexactError convert(Integer, 0.5Q1f6) - @test_throws InexactError convert(Int8, 256Q9f6) -end - -@testset "rational conversions" begin - @test convert(Rational, -0.75Q1f6) === Rational{Int8}(-3//4) - @test convert(Rational, -0.75Q0f7) === Rational{Int16}(-3//4) - @test convert(Rational{Int}, -0.75Q0f7) === Rational{Int}(-3//4) - - @test rationalize(-0.75Q3f4) === Rational{Int}(-3//4) - @test rationalize(Int16, 0.81Q3f4) === Rational{Int16}(13//16) - @test rationalize(-0.81Q3f4, tol=0.02) === Rational{Int}(-13//16) - @test rationalize(Int8, -0.81Q3f4, tol=0.07) === Rational{Int8}(-3//4) -end - -@testset "BigFloat conversions" begin - @test convert(BigFloat, -0.75Q0f7)::BigFloat == big"-0.75" - - @test big(Q7f0) === BigFloat # !== BigInt - @test big(0.75Q3f4)::BigFloat == big"0.75" -end - -@testset "Floating-point conversions" begin - @test isa(float(one(Fixed{Int8,6})), Float32) - @test isa(float(one(Fixed{Int32,18})), Float64) - @test isa(float(one(Fixed{Int32,25})), Float64) -end - -@testset "conversions to float" begin - for T in (Float16, Float32, Float64) - @test isa(convert(T, Q0f7(0.3)), T) - end - - for Tf in (Float16, Float32, Float64) - @testset "$Tf(::Fixed{$T})" for T in (Int8, Int16) - @testset "$Tf(::Fixed{$T,$f})" for f = 0:bitwidth(T)-1 - F = Fixed{T,f} - float_err = 0.0 - for i = typemin(T):typemax(T) - f_expected = Tf(i * BigFloat(2)^-f) - f_actual = Tf(reinterpret(F, i)) - float_err += abs(f_actual - f_expected) - end - @test float_err == 0.0 - end - end - @testset "$Tf(::Fixed{$T})" for T in (Int32, Int64, Int128) - @testset "$Tf(::Fixed{$T,$f})" for f = 0:bitwidth(T)-1 - F = Fixed{T,f} - error_count = 0 - for i in vcat(typemin(T):(typemin(T)+0xFF), - -T(0xFF):T(0xFF), - (typemax(T)-0xFF):typemax(T)) - f_expected = Tf(i * BigFloat(2)^-f) - isinf(f_expected) && break # for Float16() and Float32() - f_actual = Tf(reinterpret(F, i)) - f_actual == f_expected && continue - error_count += 1 - end - @test error_count == 0 - end - end - end -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 "rand" begin - for F in (Fixed{Int8,7}, Fixed{Int16,8}, Fixed{Int16,10}, Fixed{Int32,16}) + @testset "rand(::$F)" for F in target(Fixed; ex = :thin) @test isa(rand(F), F) a = rand(F, (3, 5)) - @test ndims(a) == 2 && eltype(a) == F + @test ndims(a) == 2 && eltype(a) === F @test size(a) == (3,5) end end -@testset "floatmin" begin - # issue #79 - @test floatmin(Q11f4) == Q11f4(0.06) -end - -@testset "Disambiguation constructors" begin - @test_throws ArgumentError Fixed{Int32,16}('a') - @test_throws InexactError Fixed{Int32,16}(complex(1.0, 1.0)) - @test Fixed{Int32,16}(complex(1.0, 0.0)) == 1 - @test Fixed{Int32,16}(Base.TwicePrecision(1.0, 0.0)) == 1 -end - -@testset "fractional fixed-point numbers" begin - # test all-fractional fixed-point numbers (issue #104) - for (T, f) in ((Int8, 7), - (Int16, 15), - (Int32, 31), - (Int64, 63)) - tmax = typemax(Fixed{T, f}) - @test tmax == BigInt(typemax(T)) / BigInt(2)^f - 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 - 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 "clamp" begin - @test clamp(0.5Q0f7, -0.8Q0f7, 0.8Q0f7) === 0.5Q0f7 - @test clamp(0.5Q0f7, 0.75Q0f7, 0.8Q0f7) === 0.75Q0f7 - @test clamp(0.5Q0f7, -0.8Q0f7, 0.25Q0f7) === 0.25Q0f7 - @test clamp(0.5, -0.8Q0f7, 0.8Q0f7) === 0.5 - @test clamp(0.5f0, 0.75Q0f7, 0.8Q0f7) === 0.75f0 - @test clamp(0.5Q0f15, -0.8Q0f7, 0.25Q0f7) === 0.25Q0f15 - @test clamp(0.5Q0f7, -Inf, Inf) === 0.5 - @test clamp(0.5, Q0f7) === 0.5Q0f7 - @test clamp(-1.5f0, Q0f7) === -1.0Q0f7 - @test clamp(1.5Q1f6, Q0f7) === 0.992Q0f7 -end - -@testset "sign-related functions" begin - @test_throws Exception signed(Q0f7) - @test_throws Exception signed(0.5Q0f7) - @test_throws Exception unsigned(Q0f7) - @test_throws Exception unsigned(0.5Q0f7) - @test copysign(0.5Q0f7, 0x1) === 0.5Q0f7 - @test copysign(0.5Q0f7, -1) === -0.5Q0f7 - @test flipsign(0.5Q0f7, 0x1) === 0.5Q0f7 - @test flipsign(0.5Q0f7, -1) === -0.5Q0f7 - @test_throws ArgumentError sign(0Q0f7) - @test sign(0Q1f6) === 0Q1f6 - @test sign(0.5Q1f6) === 1Q1f6 - @test sign(-0.5Q1f6) === -1Q1f6 - @test signbit(0.5Q0f7) === false - @test signbit(-0.5Q0f7) === true -end - @testset "Promotion within Fixed" begin @test @inferred(promote(Q0f7(0.25), Q0f7(0.75))) === (Q0f7(0.25), Q0f7(0.75)) diff --git a/test/normed.jl b/test/normed.jl index bfc6e0df..31e8b312 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -1,8 +1,9 @@ -using FixedPointNumbers, Statistics, Test -using FixedPointNumbers: bitwidth +include("common.jl") -# FIXME: Remove this (borrowed from PR #224) -SP = VERSION >= v"1.6.0-DEV.771" ? " " : "" # JuliaLang/julia #37085 +function symbol_to_inttype(::Type{Normed}, s::Symbol) + d = Dict(:i8 => UInt8, :i16 => UInt16, :i32 => UInt32, :i64 => UInt64, :i128 => UInt128) + d[s] +end @testset "domain of f" begin @test_throws DomainError zero(Normed{UInt8,-1}) @@ -43,30 +44,19 @@ end @test isa(v, Vector{N4f12}) end -UF2 = (Normed{UInt32,16}, Normed{UInt64,3}, Normed{UInt64,51}, Normed{UInt128,7}, Normed{UInt128,51}) - @testset "limits and identities" begin - for T in (FixedPointNumbers.UF..., UF2...) - @test zero(T) == 0 - @test one(T) == 1 - @test one(T) * one(T) == one(T) - @test typemin(T) == 0 - @test floatmin(T) == eps(T) - @test eps(zero(T)) == eps(typemax(T)) - @test sizeof(T) == sizeof(FixedPointNumbers.rawtype(T)) + @testset "$N" for N in target(Normed) + T, f = rawtype(N), nbitsfrac(N) + @test zero(N) == 0 + @test one(N) == 1 + @test one(N) * oneunit(N) == oneunit(N) + @test typemin(N) == 0 + @test typemax(N) == typemax(T)//(big"2"^f - 1) + @test floatmin(N) === eps(N) == 1//(big"2"^f - 1) + @test floatmax(N) === typemax(N) + @test eps(zero(N)) === eps(typemax(N)) + @test sizeof(N) == sizeof(T) end - @test typemax(N0f8) == 1 - @test typemax(N6f10) == typemax(UInt16)//(2^10-1) - @test typemax(N4f12) == typemax(UInt16)//(2^12-1) - @test typemax(N2f14) == typemax(UInt16)//(2^14-1) - @test typemax(N0f16) == 1 - @test typemax(N6f10) == typemax(UInt16) // (2^10-1) - @test typemax(N4f12) == typemax(UInt16) // (2^12-1) - @test typemax(N2f14) == typemax(UInt16) // (2^14-1) - @test typemax(Normed{UInt32,16}) == typemax(UInt32) // (2^16-1) - @test typemax(Normed{UInt64,3}) == typemax(UInt64) // (2^3-1) - @test typemax(Normed{UInt128,7}) == typemax(UInt128) // (2^7-1) - @test typemax(Normed{UInt128,100}) == typemax(UInt128) // (UInt128(2)^100-1) end @testset "inexactness" begin @@ -88,53 +78,72 @@ end @test occursin("Normed{UInt128,$(SP)100} is a 128-bit type representing 2^128 values", msg) end +@testset "disambiguation constructors" begin + @test_throws ArgumentError Normed{UInt32,16}('a') + @test_throws InexactError Normed{UInt32,16}(complex(1.0, 1.0)) + @test Normed{UInt32,16}(complex(1.0, 0.0)) == 1 + @test Normed{UInt32,16}(Base.TwicePrecision(1.0, 0.0)) == 1 +end + @testset "conversion" begin x = N0f8(0.5) @test convert(N0f8, x) === x - @test convert(N0f8, 1.1/typemax(UInt8)) == eps(N0f8) - @test convert(N6f10, 1.1/typemax(UInt16)*64) == eps(N6f10) - @test convert(N4f12, 1.1/typemax(UInt16)*16) == eps(N4f12) - @test convert(N2f14, 1.1/typemax(UInt16)*4) == eps(N2f14) - @test convert(N0f16, 1.1/typemax(UInt16)) == eps(N0f16) - @test convert(Normed{UInt32,16}, 1.1/typemax(UInt32)*2^16) == eps(Normed{UInt32,16}) - @test convert(Normed{UInt64,3}, 1.1/typemax(UInt64)*UInt64(2)^61) == eps(Normed{UInt64,3}) - @test convert(Normed{UInt128,7}, 1.1/typemax(UInt128)*UInt128(2)^121) == eps(Normed{UInt128,7}) + @test convert(N0f8, 1.1/typemax(UInt8)) === eps(N0f8) + @test convert(N0f8, 1.1f0/typemax(UInt8)) === eps(N0f8) + @test convert(N6f10, 1.1/typemax(UInt16)*64) === eps(N6f10) + @test convert(N4f12, 1.1/typemax(UInt16)*16) === eps(N4f12) + @test convert(N2f14, 1.1/typemax(UInt16)*4) === eps(N2f14) + @test convert(N0f16, 1.1/typemax(UInt16)) === eps(N0f16) + @test convert(N16f16, 1.1/typemax(UInt32)*2^16) === eps(N16f16) + @test convert(N61f3, 1.1/typemax(UInt64)*UInt64(2)^61) === eps(N61f3) + @test convert(Normed{UInt128,7}, 1.1/typemax(UInt128)*UInt128(2)^121) === eps(Normed{UInt128,7}) - @test convert(N0f8, 1.1f0/typemax(UInt8)) == eps(N0f8) + @test convert(N0f8, Base.TwicePrecision(1.0)) === 1N0f8 - @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(N0f16, one(N0f8)) === one(N0f16) + @test convert(N0f16, N0f8(0.5)) === reinterpret(N0f16, 0x8080) + @test convert(N9f7, N1f7(0.504)) === N9f7(0.504) - @test convert(N0f8, Base.TwicePrecision(1.0)) === 1N0f8 + # avoiding overflow with Float16 + @test N0f16(Float16(1.0)) === N0f16(1.0) + @test Float16(1.0) % N0f16 === N0f16(1.0) +end - @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) - for T in (FixedPointNumbers.UF..., UF2...) - @test convert(Bool, zero(T)) == false - @test convert(Bool, one(T)) == true - @test_throws InexactError convert(Bool, convert(T, 0.2)) - @test convert(Int, one(T)) == 1 - @test convert(Integer, one(T)) == 1 - @test convert(Rational, one(T)) == 1 +@testset "bool conversions" begin + @testset "$N to/from Bool" for N in target(Normed) + @test convert(Bool, zero(N)) === false + @test convert(Bool, oneunit(N)) === true + eps(N) < 1 && @test_throws InexactError convert(Bool, convert(N, 0.2)) + @test convert(N, true) === oneunit(N) + @test convert(N, false) === zero(N) end - @test convert(N0f16, one(N0f8)) === one(N0f16) - @test convert(N0f16, N0f8(0.5)).i === 0x8080 - @test convert(Normed{UInt16,7}, Normed{UInt8,7}(0.504)) === Normed{UInt16,7}(0.504) + @test Bool(1N0f8) === true end @testset "integer conversions" begin + @testset "$N to/from integer" for N in target(Normed) + @test convert(Int, oneunit(N)) === 1 + @test convert(Integer, oneunit(N)) === oneunit(rawtype(N)) + @test convert(N, 1) === oneunit(N) + @test convert(N, 0x0) === zero(N) + end @test convert(UInt, 1N1f7) === UInt(1) - @test convert(Integer, 1N1f7) === 0x01 - @test convert(Int, 1N1f7) === 1 @test_throws InexactError convert(Integer, 0.5N1f7) @test_throws InexactError convert(Int8, 256N8f8) end @testset "rational conversions" begin + @testset "$N to/from rational" for N in target(Normed) + @test convert(Rational, oneunit(N)) == 1//1 + @test convert(Rational{Int}, zero(N)) === 0//1 + @test convert(N, 1//1) === oneunit(N) + end + @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(Rational, 0.5N0f8) === Rational{UInt8}(0x80//0xff) @test convert(Rational, 0.5N4f12) === Rational{UInt16}(0x800//0xfff) @test convert(Rational{Int}, 0.5N0f8) === Rational{Int}(0x80//0xff) @@ -147,37 +156,47 @@ end @testset "BigFloat conversions" begin @test convert(BigFloat, 0.5N0f8)::BigFloat == 128 / big"255" + @test convert(BigFloat, eps(N0f8))::BigFloat == 1 / big"255" @test big(N7f1) === BigFloat # !== BigInt @test big(0.5N4f4)::BigFloat == 8 / big"15" end +@testset "float/floattype" begin + @test float(0.8N4f4) === 0.8f0 + @test float(0.8N20f12) === 0.8 + @test float(0.8N8f24) === 0.8 + @test float(1N11f53)::BigFloat == big"1.0" + + @testset "floattype($N)" for N in target(Normed, :i8, :i16, :i32, :i64; ex = :heavy) + @test typemax(N) <= maxintfloat(floattype(N)) + end +end + @testset "conversion from float" begin # issue 102 - for T in (UInt8, UInt16, UInt32, UInt64, UInt128) - for Tf in (Float16, Float32, Float64) - @testset "Normed{$T,$f}(::$Tf)" for f = 1:bitwidth(T) - N = Normed{T,f} - r = FixedPointNumbers.rawone(N) - - @test reinterpret(N(zero(Tf))) == 0x0 - - input_typemax = Tf(typemax(N)) - if isinf(input_typemax) - @test reinterpret(N(floatmax(Tf))) >= round(T, floatmax(Tf)) - else - @test reinterpret(N(input_typemax)) > (typemax(T)>>1) # overflow check - @test N(input_typemax) >= N(prevfloat(input_typemax)) - end - - input_upper = Tf(BigFloat(typemax(T)) / r, RoundDown) - isinf(input_upper) && continue # for Julia v0.7 - @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(N(input_exp2)) == T(input_exp2) * r - end + for Tf in (Float16, Float32, Float64) + @testset "$N(::$Tf)" for N in target(Normed) + T, f = rawtype(N), nbitsfrac(N) + r = FixedPointNumbers.rawone(N) + + @test reinterpret(N(zero(Tf))) == 0x0 + + input_typemax = Tf(typemax(N)) + if isinf(input_typemax) + @test reinterpret(N(floatmax(Tf))) >= round(T, floatmax(Tf)) + else + @test reinterpret(N(input_typemax)) > (typemax(T)>>1) # overflow check + @test N(input_typemax) >= N(prevfloat(input_typemax)) + end + + input_upper = Tf(BigFloat(typemax(T)) / r, RoundDown) + isinf(input_upper) && continue # for Julia v0.7 + @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(N(input_exp2)) == T(input_exp2) * r end end @test N0f32(Float32(0x0.7FFFFFp-32)) == zero(N0f32) @@ -193,39 +212,35 @@ end end for Tf in (Float16, Float32, Float64) - @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(T):typemax(T) - f_expected = Tf(i / BigFloat(FixedPointNumbers.rawone(N))) - isinf(f_expected) && break # for Float16(::Normed{UInt16,1}) - f_actual = Tf(reinterpret(N, i)) - float_err += abs(f_actual - f_expected) - end - @test float_err == 0.0 + @testset "$Tf(::$N)" for N in target(Normed, :i8, :i16) + T = rawtype(N) + float_err = 0.0 + 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(N, i)) + float_err += abs(f_actual - f_expected) end + @test float_err == 0.0 end - @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(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(N, i)) - f_actual == f_expected && continue - f_actual == prevfloat(f_expected) && continue - f_actual == nextfloat(f_expected) && continue - error_count += 1 - end - @test error_count == 0 + @testset "$Tf(::$N)" for N in target(Normed, :i32, :i64, :i128) + T = rawtype(N) + error_count = 0 + 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(N, i)) + f_actual == f_expected && continue + f_actual == prevfloat(f_expected) && continue + f_actual == nextfloat(f_expected) && continue + error_count += 1 end + @test error_count == 0 end end end -@testset "modulus" begin +@testset "type modulus" begin @test N0f8(0.2) % N0f8 === N0f8(0.2) @test N2f14(1.2) % N0f16 === N0f16(0.20002) @test N2f14(1.2) % N0f8 === N0f8(0.196) @@ -250,48 +265,46 @@ end @test all(f -> 1.0e0 % Normed{UInt64,f} == oneunit(Normed{UInt64,f}), 1:64) end -@testset "bitwise" begin - x = N0f8(0b01010001, 0) - @test ~x == N0f8(0b10101110, 0) - @test -x == reinterpret(N0f8, 0xaf) -end - -@testset "float" begin - @test isa(float(one(Normed{UInt8,7})), Float32) - @test isa(float(one(Normed{UInt32,18})), Float64) - @test isa(float(one(Normed{UInt32,25})), Float64) -end - @testset "arithmetic" begin - for T in (FixedPointNumbers.UF..., UF2...) - x = T(0x10,0) - y = T(0x25,0) + @testset "$N arithmetic" for N in target(Normed; ex = :light) + x = N(0x10,0) + y = N(0x25,0) fx = float(x) fy = float(y) @test y > x @test y != x - @test typeof(x+y) == T - @test typeof((x+y)-y) == T - @test typeof(x*y) == T - @test typeof(x/y) == T - @test (x+y) ≈ T(0x35,0) - @test ((x+y)-x) ≈ fy - @test ((x-y)+y) ≈ fx - @test (x*y) ≈ convert(T, fx*fy) - @test (x/y) ≈ convert(T, fx/fy) - @test (x^2) ≈ convert(T, fx^2) + @test x+y === N(0x35,0) + @test ((x+y)-y) === x + @test ((x-y)+y) === x # wraparound + fx*fy <= typemax(N) && @test (x*y)::N ≈ convert(N, fx*fy) + @test (x/y)::N ≈ convert(N, fx/fy) + fx^2 <= typemax(N) && @test (x^2)::N ≈ convert(N, fx^2) @test (x^2.1f0) ≈ fx^2.1f0 @test (x^2.1) ≈ convert(Float64, x)^2.1 end end +@testset "div/fld1" begin + @test div(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == 8 + @test div(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == 7 + @test fld1(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == 8 + @test fld1(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == 8 +end + +@testset "rem/mod" begin + @test mod(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == rem(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == 0 + @test mod(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == rem(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == reinterpret(N0f8, 0x01) + @test mod1(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == reinterpret(N0f8, 0x02) + @test mod1(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == reinterpret(N0f8, 0x01) +end + @testset "rounding" begin - for T in (UInt8, UInt16, UInt32, UInt64) + for sym in (:i8, :i16, :i32, :i64) + T = symbol_to_inttype(Normed, sym) 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} + @testset "rounding $N" for N in target(Normed, sym) 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) @@ -322,28 +335,18 @@ end end @testset "approx" begin - @testset "approx $T" for T in FixedPointNumbers.UF - xs = typemin(T):eps(T):typemax(T)-eps(T) - @test all(x -> x ≈ x + eps(T), xs) - @test all(x -> x + eps(T) ≈ x, xs) - @test !any(x -> x - eps(T) ≈ x + eps(T), xs) + @testset "approx $N" for N in target(Normed, :i8, :i16; ex = :light) + xs = typemin(N):eps(N):typemax(N)-eps(N) + @test all(x -> x ≈ x + eps(N), xs) + @test all(x -> x + eps(N) ≈ x, xs) + @test !any(x -> x - eps(N) ≈ x + eps(N), xs) end end -@testset "low-level arithmetic" begin +@testset "comparison" begin @test !(N0f8(0.5) < N0f8(0.5)) @test N0f8(0.5) <= N0f8(0.5) - @test div(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == 8 - @test div(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == 7 - @test Base.fld1(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == 8 - @test Base.fld1(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == 8 - @test mod(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == rem(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == 0 - @test mod(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == rem(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == reinterpret(N0f8, 0x01) - @test mod1(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == reinterpret(N0f8, 0x02) - @test mod1(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == reinterpret(N0f8, 0x01) - @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 @@ -373,6 +376,35 @@ end @test signbit(1N0f8) === false end +@testset "bitwise" begin + x = N0f8(0b01010001, 0) + @test ~x == N0f8(0b10101110, 0) + @test -x == reinterpret(N0f8, 0xaf) + + @test bswap(N0f8(0.5)) === N0f8(0.5) + @test bswap(N0f16(0.5)) === reinterpret(N0f16, 0x0080) +end + +@testset "predicates" begin + @test isfinite(1N8f8) + @test !isnan(1N8f8) + @test !isinf(1N8f8) + + @testset "isinteger" begin + @testset "isinteger(::$N)" for N in target(Normed, :i8, :i16) + xs = typemin(N):eps(N):typemax(N) + @test all(x -> isinteger(x) == isinteger(float(x)), xs) + end + @testset "isinteger(::$N)" for N in target(Normed, :i32, :i64, :i128) + if nbitsfrac(N) == 1 + @test isinteger(zero(N)) & isinteger(oneunit(N)) + else + @test !isinteger(oneunit(N) - eps(N)) & isinteger(oneunit(N)) + end + end + end +end + @testset "unit range" begin @test length(N0f8(0):N0f8(1)) == 2 @test length(N0f8(1):N0f8(0)) == 0 @@ -405,29 +437,34 @@ 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 "reductions" begin + a = N0f8[reinterpret(N0f8, 0xff), reinterpret(N0f8, 0xff)] + @test sum(a) == 2.0 + @test sum(a, dims=1) == [2.0] - @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 + a = N2f14[3.2, 2.4] + acmp = Float64(a[1])*Float64(a[2]) + @test prod(a) == acmp + @test prod(a, dims=1) == [acmp] +end + +@testset "reductions, Statistics" begin + a = N0f8[reinterpret(N0f8, 0x80), reinterpret(N0f8, 0x40)] + af = FixedPointNumbers.Treduce.(a) + @test mean(a) === mean(af) + @test std(a) === std(af) + @test var(a) === var(af) + m = mean(a) + @test stdm(a, m) === stdm(af, m) + @test varm(a, m) === varm(af, m) +end + +@testset "rand" begin + @testset "rand(::$N)" for N in target(Normed; ex = :thin) + @test isa(rand(N), N) + a = rand(N, (3, 5)) + @test ndims(a) == 2 && eltype(a) === N + @test size(a) == (3,5) end end @@ -531,50 +568,3 @@ end bd, ad = scaledual(Float64, a) @test 1.0*a == bd*ad end - -@testset "reductions" begin - a = N0f8[reinterpret(N0f8, 0xff), reinterpret(N0f8, 0xff)] - @test sum(a) == 2.0 - @test sum(a, dims=1) == [2.0] - - a = N2f14[3.2, 2.4] - acmp = Float64(a[1])*Float64(a[2]) - @test prod(a) == acmp - @test prod(a, dims=1) == [acmp] -end - -@testset "reductions, Statistics" begin - a = N0f8[reinterpret(N0f8, 0x80), reinterpret(N0f8, 0x40)] - af = FixedPointNumbers.Treduce.(a) - @test mean(a) === mean(af) - @test std(a) === std(af) - @test var(a) === var(af) - m = mean(a) - @test stdm(a, m) === stdm(af, m) - @test varm(a, m) === varm(af, m) -end - -@testset "rand" begin - for T in (Normed{UInt8,8}, Normed{UInt8,6}, - Normed{UInt16,16}, Normed{UInt16,14}, - Normed{UInt32,32}, Normed{UInt32,30}, - Normed{UInt64,64}, Normed{UInt64,62}) - a = rand(T) - @test isa(a, T) - a = rand(T, (3, 5)) - @test ndims(a) == 2 && eltype(a) == T - @test size(a) == (3,5) - end -end - -@testset "Overflow with Float16" begin - @test N0f16(Float16(1.0)) === N0f16(1.0) - @test Float16(1.0) % N0f16 === N0f16(1.0) -end - -@testset "disambiguation constructors" begin - @test_throws ArgumentError Normed{UInt32,16}('a') - @test_throws InexactError Normed{UInt32,16}(complex(1.0, 1.0)) - @test Normed{UInt32,16}(complex(1.0, 0.0)) == 1 - @test Normed{UInt32,16}(Base.TwicePrecision(1.0, 0.0)) == 1 -end diff --git a/test/traits.jl b/test/traits.jl index 9aee60b0..c248b4c0 100644 --- a/test/traits.jl +++ b/test/traits.jl @@ -1,23 +1,11 @@ using FixedPointNumbers, Test +using FixedPointNumbers: bitwidth struct MyReal <: Real end @testset "floattype" begin - function _is_fixed_type(x::Symbol) - try - @eval $(x) isa Type && $(x) <: FixedPoint && return true - catch - return false - end - end - - fixed_types = setdiff(filter(_is_fixed_type, names(FixedPointNumbers)), [:Fixed, :Normed, :FixedPoint]) - fixed_types = [@eval $(x) for x in fixed_types] - - exact_types = vcat([UInt8, UInt16, UInt32, UInt64, UInt128, Bool, - Int8, Int16, Int32, Int64, Int128], - fixed_types) - for T in exact_types + for T in (UInt8, UInt16, UInt32, UInt64, UInt128, Bool, + Int8, Int16, Int32, Int64, Int128) @test typemax(T) <= maxintfloat(floattype(T)) end @test floattype(Rational{Int}) === Float64 From c1a642fe59a0b3cdc55c8f82a26c756924cbc05b Mon Sep 17 00:00:00 2001 From: kimikage Date: Tue, 30 Apr 2024 19:52:19 +0900 Subject: [PATCH 04/40] Backport partially #207 --- src/utilities.jl | 3 +-- test/fixed.jl | 25 ++++++++++++++++++------- test/normed.jl | 25 ++++++++++++++++++------- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/utilities.jl b/src/utilities.jl index 0976dd08..b66ae744 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -1,6 +1,7 @@ # utility functions and macros, which are independent of `FixedPoint` bitwidth(T::Type) = 8sizeof(T) +widen1(T::Type) = T # fallback widen1(::Type{Int8}) = Int16 widen1(::Type{UInt8}) = UInt16 widen1(::Type{Int16}) = Int32 @@ -9,8 +10,6 @@ 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)) signedtype(::Type{T}) where {T <: Integer} = typeof(signed(zero(T))) diff --git a/test/fixed.jl b/test/fixed.jl index 6f8a33bd..1912e1d0 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -455,13 +455,24 @@ end @test Fixed{Int16,3}(-1) == Fixed{Int8,5}(-1) @test Fixed{Int16,3}(0.25) == Fixed{Int8,5}(0.25) - @test promote_type(Q0f7,Float32,Int) == Float32 - @test promote_type(Q0f7,Int,Float32) == Float32 - @test promote_type(Int,Q0f7,Float32) == Float32 - @test promote_type(Int,Float32,Q0f7) == Float32 - @test promote_type(Float32,Int,Q0f7) == Float32 - @test promote_type(Float32,Q0f7,Int) == Float32 - @test promote_type(Q0f7,Q1f6,Q2f5,Q3f4,Q4f3,Q5f2) == Fixed{Int128,7} + @test @inferred(promote_type(Q0f7, Float64)) === Float64 + @test @inferred(promote_type(Float32, Q7f24)) === Float32 # Float64 on v0.9 (#207) + + @test @inferred(promote_type(Q0f7, Int8)) === Q0f7 # Float32 on v0.9 (#207) + @test @inferred(promote_type(Int128, Q7f24)) === Q7f24 # Float64 on v0.9 (#207) + + @test @inferred(promote_type(Q0f15, Rational{UInt8})) === Rational{UInt8} + + @test @inferred(promote_type(Q0f7, Float32, Int)) === Float32 + @test @inferred(promote_type(Q0f7, Int, Float32)) === Float32 + @test @inferred(promote_type(Int, Q0f7, Float32)) === Float32 + @test @inferred(promote_type(Int, Float32, Q0f7)) === Float32 + @test @inferred(promote_type(Float32, Int, Q0f7)) === Float32 + @test @inferred(promote_type(Float32, Q0f7, Int)) === Float32 + + @test @inferred(promote_type(Q0f7,Q1f6,Q2f5,Q3f4,Q4f3,Q5f2)) == Fixed{Int128,7} + + @test @inferred(promote_type(Q0f7, N0f32)) === FixedPoint # Float64 on v0.9 (#207) end @testset "show" begin diff --git a/test/normed.jl b/test/normed.jl index 31e8b312..c22b828d 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -485,13 +485,24 @@ end @test Normed{UInt16,4}(1) == Normed{UInt8,6}(1) @test Normed{UInt16,4}(0.2) == Normed{UInt8,6}(0.2) - @test promote_type(N0f8,Float32,Int) == Float32 - @test promote_type(N0f8,Int,Float32) == Float32 - @test promote_type(Int,N0f8,Float32) == Float32 - @test promote_type(Int,Float32,N0f8) == Float32 - @test promote_type(Float32,Int,N0f8) == Float32 - @test promote_type(Float32,N0f8,Int) == Float32 - @test promote_type(N0f8,N1f7,N2f6,N3f5,N4f4,N5f3) == Normed{UInt128,8} + @test @inferred(promote_type(N0f8, Float64)) === Float64 + @test @inferred(promote_type(Float32, N8f24)) === Float64 + + @test @inferred(promote_type(N0f8, Int8)) === Float32 + @test @inferred(promote_type(Int128, N8f24)) === Float64 + + @test @inferred(promote_type(N0f16, Rational{Int8})) === Rational{Int8} + + @test @inferred(promote_type(N0f8, Float32, Int)) === Float32 + @test @inferred(promote_type(N0f8, Int, Float32)) === Float32 + @test @inferred(promote_type(Int, N0f8, Float32)) === Float32 + @test @inferred(promote_type(Int, Float32, N0f8)) === Float32 + @test @inferred(promote_type(Float32, Int, N0f8)) === Float32 + @test @inferred(promote_type(Float32, N0f8, Int)) === Float32 + + @test @inferred(promote_type(N0f8,N1f7,N2f6,N3f5,N4f4,N5f3)) === Normed{UInt128,8} + + @test @inferred(promote_type(N0f8, Q0f31)) === FixedPoint # Float64 on v0.9 (#207) end @testset "show" begin From a5e77823e860baa2f95b8f6294fe56996bf5ca74 Mon Sep 17 00:00:00 2001 From: kimikage Date: Wed, 1 May 2024 15:50:09 +0900 Subject: [PATCH 05/40] Add workaround for `clamp` with poor promotion --- src/FixedPointNumbers.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 4751ca3a..12bbc83c 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -194,6 +194,12 @@ clamp(x::X, lo::X, hi::X) where {X <: FixedPoint} = X(clamp(x.i, lo.i, hi.i), 0) clamp(x, ::Type{X}) where {X <: FixedPoint} = clamp(x, typemin(X), typemax(X)) % X +# Workaround for poor promotion due to lack of PR #207 +function clamp(x::AbstractFloat, ::Type{X}) where {X <: FixedPoint} + Tf = promote_type(typeof(x), floattype(X)) + clamp(Tf(x), typemin(X), typemax(X)) % X +end + # Since `FixedPoint` is not an integer type, it is not clear in what type # `signed` and `unsigned` for `FixedPoint` should return values. They should # currently throw errors in case we support "unsigned Fixed" or "signed Normed" From 3e5b8581502d9986e8294c6d225a5ff29b3efbc0 Mon Sep 17 00:00:00 2001 From: kimikage Date: Sat, 8 Aug 2020 09:32:56 +0900 Subject: [PATCH 06/40] Use sampler-based Random API (#206) This prevents `rand` from returning a `ReinterpretArray` to avoid the performance problem with `ReinterpretArray` . This also supports specifying the RNG option. --- Project.toml | 1 + src/FixedPointNumbers.jl | 14 +++++++++++--- test/common.jl | 2 +- test/fixed.jl | 1 + test/normed.jl | 1 + 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Project.toml b/Project.toml index 31c836cc..5f3ec55b 100644 --- a/Project.toml +++ b/Project.toml @@ -3,6 +3,7 @@ uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" version = "0.8.4" [deps] +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [compat] diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 12bbc83c..409c09ca 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -7,9 +7,10 @@ import Base: ==, <, <=, -, +, *, /, ~, isapprox, big, rationalize, float, trunc, round, floor, ceil, bswap, clamp, div, fld, rem, mod, mod1, fld1, min, max, minmax, signed, unsigned, copysign, flipsign, signbit, - rand, length + length import Statistics # for _mean_promote +import Random: Random, AbstractRNG, SamplerType, rand! using Base.Checked: checked_add, checked_sub, checked_div @@ -356,8 +357,15 @@ scaledual(::Type{Tdual}, x::AbstractArray{T}) where {Tdual, T <: FixedPoint} = throw(ArgumentError(String(take!(io)))) 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)) +function Random.rand(r::AbstractRNG, ::SamplerType{X}) where X <: FixedPoint + X(rand(r, rawtype(X)), 0) +end + +function rand!(r::AbstractRNG, A::Array{X}, ::SamplerType{X}) where {T, X <: FixedPoint{T}} + At = unsafe_wrap(Array, reinterpret(Ptr{T}, pointer(A)), size(A)) + Random.rand!(r, At, SamplerType{T}()) + A +end if VERSION >= v"1.1" # work around https://github.com/JuliaLang/julia/issues/34121 include("precompile.jl") diff --git a/test/common.jl b/test/common.jl index cfd0e63d..78bf2cc2 100644 --- a/test/common.jl +++ b/test/common.jl @@ -1,4 +1,4 @@ -using FixedPointNumbers, Statistics, Test +using FixedPointNumbers, Statistics, Random, Test using FixedPointNumbers: bitwidth, rawtype, nbitsfrac # FIXME: Remove this comment (SP is borrowed from PR #224) diff --git a/test/fixed.jl b/test/fixed.jl index 1912e1d0..2e4ddb29 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -436,6 +436,7 @@ end @test ndims(a) == 2 && eltype(a) === F @test size(a) == (3,5) end + @test rand(MersenneTwister(1234), Q0f7) === -0.156Q0f7 end @testset "Promotion within Fixed" begin diff --git a/test/normed.jl b/test/normed.jl index c22b828d..d310dd16 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -466,6 +466,7 @@ end @test ndims(a) == 2 && eltype(a) === N @test size(a) == (3,5) end + @test rand(MersenneTwister(1234), N0f8) === 0.925N0f8 end @testset "Promotion within Normed" begin From 40f7177908c1d5143e45e5ed395f5d15676b3c53 Mon Sep 17 00:00:00 2001 From: kimikage Date: Sun, 16 Aug 2020 00:21:43 +0900 Subject: [PATCH 07/40] Backport partially "Improve overflow error message (#214)" This displays the fixed point type of the input in the message, not the rawtype. --- src/FixedPointNumbers.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 409c09ca..7a045f08 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -357,6 +357,13 @@ scaledual(::Type{Tdual}, x::AbstractArray{T}) where {Tdual, T <: FixedPoint} = throw(ArgumentError(String(take!(io)))) end +@noinline function throw_overflowerror(op::Symbol, @nospecialize(x), @nospecialize(y)) + io = IOBuffer() + print(io, x, ' ', op, ' ', y, " overflowed for type ") + showtype(io, typeof(x)) + throw(OverflowError(String(take!(io)))) +end + function Random.rand(r::AbstractRNG, ::SamplerType{X}) where X <: FixedPoint X(rand(r, rawtype(X)), 0) end From 83c63cc94c3dc742bb8f35ff640da006967da717 Mon Sep 17 00:00:00 2001 From: kimikage Date: Sun, 16 Aug 2020 00:23:57 +0900 Subject: [PATCH 08/40] Avoid inconsistent behavior of `unsafe_trunc` for `BigFloat` (#212) --- src/utilities.jl | 7 ++++--- test/normed.jl | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/utilities.jl b/src/utilities.jl index b66ae744..f52a55ae 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -38,12 +38,13 @@ exponent_bias(::Type{Float32}) = 127 exponent_bias(::Type{Float64}) = 1023 _unsafe_trunc(::Type{T}, x::Integer) where {T} = x % T -_unsafe_trunc(::Type{T}, x) where {T} = unsafe_trunc(T, x) +_unsafe_trunc(::Type{T}, x) where {T} = unsafe_trunc(T, x) +# issue #202, #211 +_unsafe_trunc(::Type{T}, x::BigFloat) where {T <: Integer} = trunc(BigInt, x) % T + if !signbit(signed(unsafe_trunc(UInt, -12.345))) # a workaround for ARM (issue #134) function _unsafe_trunc(::Type{T}, x::AbstractFloat) where {T <: Integer} unsafe_trunc(T, unsafe_trunc(signedtype(T), x)) end - # exclude BigFloat (issue #202) - _unsafe_trunc(::Type{T}, x::BigFloat) where {T <: Integer} = unsafe_trunc(T, x) end diff --git a/test/normed.jl b/test/normed.jl index d310dd16..cb8b768e 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -263,6 +263,10 @@ end # 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) + + # issu #211 + @test big"1.2" % N0f8 === 0.196N0f8 + @test reinterpret(BigFloat(0x0_01234567_89abcdef) % N63f1) === 0x01234567_89abcdef end @testset "arithmetic" begin From e3b58a1fdadea14b15f838f0949ba6f0b3535491 Mon Sep 17 00:00:00 2001 From: kimikage Date: Wed, 19 Aug 2020 18:45:36 +0900 Subject: [PATCH 09/40] Change test targets of `Fixed` (#218) --- test/common.jl | 2 +- test/fixed.jl | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/test/common.jl b/test/common.jl index 78bf2cc2..454cc673 100644 --- a/test/common.jl +++ b/test/common.jl @@ -107,7 +107,7 @@ T == UInt32 |x - - - - - - -:- - - - - - - -:x - - - - - - -:- - - - - - - -| """ function target_f(X::Type, T::Type{<:Integer}; ex = :default) f_min = X === Fixed ? 0 : 1 - f_max = bitwidth(T) - (T <: Signed) - 1 + f_min + f_max = bitwidth(T) - (T <: Signed) ex === :heavy && return f_min:f_max ex === :default && bitwidth(T) <= 16 && return f_min:f_max ex === :thin && return ((f_max + 1) ÷ 2, f_max) diff --git a/test/fixed.jl b/test/fixed.jl index 2e4ddb29..1491326f 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -24,8 +24,8 @@ function test_fixed(::Type{F}) where {F} fxf = convert(Float64, fx) @test convert(F, convert(Float64, fx)) === fx - @test convert(F, convert(Float64, -fx)) === -fx - @test convert(Float64, -fx) == -convert(Float64, fx) + fx != typemin(F) && @test convert(F, convert(Float64, -fx)) === -fx + fx != typemin(F) && @test convert(Float64, -fx) == -convert(Float64, fx) rx = convert(Rational{BigInt}, fx) @assert isequal(fx, rx) == isequal(hash(fx), hash(rx)) @@ -96,7 +96,11 @@ end f < bitwidth(T) - 1 && @test one(F) == 1 f < bitwidth(T) - 1 && @test one(F) * oneunit(F) == oneunit(F) @test typemin(F) == typemin(T) >> f - @test typemax(F) == typemax(T)//big"2"^f + if T === Int128 + @test typemax(F) * big"2.0"^f == typemax(T) # force promotion to BigFloat due to lack of PR #207 + else + @test typemax(F) == typemax(T)//big"2"^f + end @test floatmin(F) === eps(F) == 2.0^-f # issue #79 @test floatmax(F) === typemax(F) @test eps(zero(F)) === eps(typemax(F)) From 25d44114ec0fa8cc6d17e464a1e7650ee9145925 Mon Sep 17 00:00:00 2001 From: kimikage Date: Mon, 24 Aug 2020 14:39:19 +0900 Subject: [PATCH 10/40] Backport partially "Optimize multiplication for Normed (#213)" This adds `wrapping_mul` and `checked_mul` binary operations for `Normed`. This replaces most of Normed's implementation of multiplication with integer operations. This improves the speed in many cases and the accuracy in some cases. --- src/FixedPointNumbers.jl | 3 +++ src/normed.jl | 34 +++++++++++++++++++++++++++++++--- test/fixed.jl | 22 ++++++++++++++++++++++ test/normed.jl | 20 ++++++++++++++++++++ 4 files changed, 76 insertions(+), 3 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 7a045f08..0e3aa8ac 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -180,6 +180,9 @@ floattype(::Type{Base.TwicePrecision{T}}) where T<:Union{Float16,Float32} = wide float(x::FixedPoint) = convert(floattype(x), x) +wrapping_mul(x::X, y::X) where {X <: FixedPoint} = (float(x) * float(y)) % X +*(x::X, y::X) where {X <: FixedPoint} = wrapping_mul(x, y) + function minmax(x::X, y::X) where {X <: FixedPoint} a, b = minmax(reinterpret(x), reinterpret(y)) X(a,0), X(b,0) diff --git a/src/normed.jl b/src/normed.jl index 5c2f6c4f..084a926e 100644 --- a/src/normed.jl +++ b/src/normed.jl @@ -127,7 +127,6 @@ function rem(x::Float64, ::Type{N}) where {f, N <: Normed{UInt64,f}} reinterpret(N, r << UInt8(f - 53) - unsigned(signed(r) >> 0x35)) end - function (::Type{T})(x::Normed) where {T <: AbstractFloat} # The following optimization for constant division may cause rounding errors. # y = reinterpret(x)*(one(rawtype(x))/convert(T, rawone(x))) @@ -248,8 +247,37 @@ Base.BigFloat(x::Normed) = reinterpret(x) / BigFloat(rawone(x)) Base.Rational(x::Normed) = reinterpret(x)//rawone(x) -# unchecked arithmetic -*(x::T, y::T) where {T <: Normed} = convert(T,convert(floattype(T), x)*convert(floattype(T), y)) +# Division by `2^f-1` with RoundNearest. The result would be in the lower half bits. +div_2fm1(x::T, ::Val{f}) where {T, f} = (x + (T(1)<<(f - 1) - 0x1)) ÷ (T(1) << f - 0x1) +div_2fm1(x::T, ::Val{1}) where T = x +div_2fm1(x::UInt16, ::Val{8}) = (((x + 0x80) >> 0x8) + x + 0x80) >> 0x8 +div_2fm1(x::UInt32, ::Val{16}) = (((x + 0x8000) >> 0x10) + x + 0x8000) >> 0x10 +div_2fm1(x::UInt64, ::Val{32}) = (((x + 0x80000000) >> 0x20) + x + 0x80000000) >> 0x20 +div_2fm1(x::UInt128, ::Val{64}) = (((x + 0x8000000000000000) >> 0x40) + x + 0x8000000000000000) >> 0x40 + +# wrapping arithmetic +function wrapping_mul(x::N, y::N) where {T <: Union{UInt8,UInt16,UInt32,UInt64}, f, N <: Normed{T,f}} + z = widemul(x.i, y.i) + N(div_2fm1(z, Val(Int(f))) % T, 0) +end + +# checked arithmetic +function checked_mul(x::N, y::N) where {N <: Normed} + z = float(x) * float(y) + z < typemax(N) + eps(N)/2 || throw_overflowerror(:*, x, y) + z % N +end +function checked_mul(x::N, y::N) where {T <: Union{UInt8,UInt16,UInt32,UInt64}, f, N <: Normed{T,f}} + f == bitwidth(T) && return wrapping_mul(x, y) + z = widemul(x.i, y.i) + m = widemul(typemax(N).i, rawone(N)) + (rawone(N) >> 0x1) + z < m || throw_overflowerror(:*, x, y) + N(div_2fm1(z, Val(Int(f))) % T, 0) +end + +# Override the default arithmetic with `checked` for backward compatibility +*(x::N, y::N) where {N <: Normed} = checked_mul(x, y) + /(x::T, y::T) where {T <: Normed} = convert(T,convert(floattype(T), x)/convert(floattype(T), y)) # Functions diff --git a/test/fixed.jl b/test/fixed.jl index 1491326f..062ec972 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -261,6 +261,28 @@ end @test (-67.2 % T).i == round(Int, -67.2*512) % Int16 end +@testset "mul" begin + wrapping_mul = FixedPointNumbers.wrapping_mul + for F in target(Fixed; ex = :thin) + @test wrapping_mul(typemax(F), zero(F)) === zero(F) + + # FIXME: Both the rhs and lhs of the following tests may be inaccurate due to `rem` + F === Fixed{Int128,127} && continue + + @test wrapping_mul(F(-1), typemax(F)) === -typemax(F) + + @test wrapping_mul(typemin(F), typemax(F)) === big(typemin(F)) * big(typemax(F)) % F + + @test wrapping_mul(typemin(F), typemin(F)) === big(typemin(F))^2 % F + end + for F in target(Fixed, :i8; ex = :thin) + xs = typemin(F):eps(F):typemax(F) + xys = ((x, y) for x in xs, y in xs) + fmul(x, y) = float(x) * float(y) # note that precision(Float32) < 32 + @test all(((x, y),) -> wrapping_mul(x, y) === fmul(x, y) % F, xys) + end +end + @testset "rounding" begin for sym in (:i8, :i16, :i32, :i64) T = symbol_to_inttype(Fixed, sym) diff --git a/test/normed.jl b/test/normed.jl index cb8b768e..04265a29 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -288,6 +288,26 @@ end end end +@testset "mul" begin + checked_mul = FixedPointNumbers.checked_mul + for N in target(Normed; ex = :thin) + @test checked_mul(typemax(N), zero(N)) === zero(N) + + @test checked_mul(one(N), typemax(N)) === typemax(N) + + if typemax(N) != 1 + @test_throws OverflowError checked_mul(typemax(N), typemax(N)) + end + end + for N in target(Normed, :i8; ex = :thin) + xs = typemin(N):eps(N):typemax(N) + xys = ((x, y) for x in xs, y in xs) + fmul(x, y) = float(x) * float(y) # note that precision(Float32) < 32 + @test all(((x, y),) -> !(typemin(N) <= fmul(x, y) <= typemax(N)) || + (fmul(x, y) % N) === checked_mul(x, y), xys) + end +end + @testset "div/fld1" begin @test div(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == 8 @test div(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == 7 From 4bc8cfcb33a1904796d138646388c35ec30d71b6 Mon Sep 17 00:00:00 2001 From: kimikage Date: Wed, 26 Aug 2020 08:26:35 +0900 Subject: [PATCH 11/40] Backport partially "Specialize multiplication for `Fixed` (#220)" This specializes most of the multiplication for `Fixed` and avoids floating point operations. A major change is that the rounding mode is changed from `RoundNearestTiesUp` to `RoundNearest`. The existing `RoundNearestTiesUp` and `RoundDown` modes are now supported by the new unexported function `mul_with_rounding`. This also improves `rem`. Unlike multiplication for `Normed`, the wrapping arithmetic is the default for `Fixed`. --- src/fixed.jl | 56 ++++++++++++++++++++++++++++++++++++++------------ test/fixed.jl | 17 ++++++++++++--- test/normed.jl | 4 ++-- 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/fixed.jl b/src/fixed.jl index 4596b34a..fa74a26f 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -95,19 +95,20 @@ function _convert(::Type{F}, x::Rational) where {T, f, F <: Fixed{T,f}} end end -# unchecked arithmetic - -# with truncation: -#*(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) - -/(x::Fixed{T,f}, y::Fixed{T,f}) where {T,f} = Fixed{T,f}(div(convert(widen(T), x.i) << f, y.i), 0) - - -rem(x::Integer, ::Type{Fixed{T,f}}) where {T,f} = Fixed{T,f}(rem(x,T)<>> (bitwidth(T) - f - 1)) + half = oneunit(T) << (f - 1) + c = half - (xf === half) + (x + c) >> f +end +div_2f(x::T, ::Val{0}) where {T} = x + +# wrapping arithmetic +function wrapping_mul(x::F, y::F) where {T <: Union{Int8,Int16,Int32,Int64}, f, F <: Fixed{T,f}} + z = widemul(x.i, y.i) + F(div_2f(z, Val(Int(f))) % T, 0) +end + +function mul_with_rounding(x::F, y::F, ::RoundingMode{:Nearest}) where {F <: Fixed} + wrapping_mul(x, y) +end +function mul_with_rounding(x::F, y::F, ::RoundingMode{:NearestTiesUp}) where + {T <: Union{Int8,Int16,Int32,Int64}, f, F <: Fixed{T, f}} + z = widemul(x.i, y.i) + F(((z + (oftype(z, 1) << f >>> 1)) >> f) % T, 0) +end +function mul_with_rounding(x::F, y::F, ::RoundingMode{:Down}) where + {T <: Union{Int8,Int16,Int32,Int64}, f, F <: Fixed{T, f}} + F((widemul(x.i, y.i) >> f) % T, 0) +end + +/(x::Fixed{T,f}, y::Fixed{T,f}) where {T,f} = Fixed{T,f}(div(convert(widen(T), x.i) << f, y.i), 0) + function trunc(x::Fixed{T,f}) where {T, f} f == 0 && return x f == bitwidth(T) && return zero(x) # TODO: remove this line diff --git a/test/fixed.jl b/test/fixed.jl index 062ec972..d4bcb87e 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -246,6 +246,10 @@ end end @testset "type modulus" begin + @test Q0f7(0.2) % Q0f7 === Q0f7(0.2) + @test Q1f14(1.2) % Q0f15 === Q0f15(-0.8) + @test Q1f14(1.2) % Q0f7 === Q0f7(-0.8) + T = Fixed{Int8,7} for i = -1.0:0.1:typemax(T) @test i % T === T(i) @@ -259,6 +263,9 @@ end end @test ( 65.2 % T).i == round(Int, 65.2*512) % Int16 @test (-67.2 % T).i == round(Int, -67.2*512) % Int16 + + @test -1 % Q0f7 === Q0f7(-1) + @test -2 % Q0f7 === Q0f7(0) end @testset "mul" begin @@ -266,9 +273,6 @@ end for F in target(Fixed; ex = :thin) @test wrapping_mul(typemax(F), zero(F)) === zero(F) - # FIXME: Both the rhs and lhs of the following tests may be inaccurate due to `rem` - F === Fixed{Int128,127} && continue - @test wrapping_mul(F(-1), typemax(F)) === -typemax(F) @test wrapping_mul(typemin(F), typemax(F)) === big(typemin(F)) * big(typemax(F)) % F @@ -281,6 +285,13 @@ end fmul(x, y) = float(x) * float(y) # note that precision(Float32) < 32 @test all(((x, y),) -> wrapping_mul(x, y) === fmul(x, y) % F, xys) end + + FixedPointNumbers.mul_with_rounding(1.5Q6f1, 0.5Q6f1, RoundNearest) === 1.0Q6f1 + FixedPointNumbers.mul_with_rounding(1.5Q6f1, -0.5Q6f1, RoundNearest) === -1.0Q6f1 + FixedPointNumbers.mul_with_rounding(1.5Q6f1, 0.5Q6f1, RoundNearestTiesUp) === 1.0Q6f1 + FixedPointNumbers.mul_with_rounding(1.5Q6f1, -0.5Q6f1, RoundNearestTiesUp) === -0.5Q6f1 + FixedPointNumbers.mul_with_rounding(1.5Q6f1, 0.5Q6f1, RoundDown) === 0.5Q6f1 + FixedPointNumbers.mul_with_rounding(1.5Q6f1, -0.5Q6f1, RoundDown) === -1.0Q6f1 end @testset "rounding" begin diff --git a/test/normed.jl b/test/normed.jl index 04265a29..edca163f 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -257,8 +257,8 @@ end @test (65.2 % N6f10).i == round(Int, 65.2*1023) % UInt16 @test (-0.3 % N6f10).i == round(Int, -0.3*1023) % UInt16 - @test 1 % N0f8 == 1 - @test 2 % N0f8 == N0f8(0.996) + @test 1 % N0f8 === N0f8(1) + @test 2 % N0f8 === N0f8(0.996) # issue #150 @test all(f -> 1.0f0 % Normed{UInt32,f} == oneunit(Normed{UInt32,f}), 1:32) From 400456c6cce776cd8240e927fccce397c537fc88 Mon Sep 17 00:00:00 2001 From: kimikage Date: Sat, 29 Aug 2020 10:07:05 +0900 Subject: [PATCH 12/40] Fix `% Normed{UInt32}` on ARM and Improve `NaN % FixedPoint` (#223) This reduces the environment-/optimization-dependent instability of the results for `NaN`. This also adds tests for `NaN`/`Inf`. This clarifies the behavior regarding `NaN` and `Inf`, which had not been explicitly defined. --- src/fixed.jl | 10 +++++++++- src/normed.jl | 7 ++++++- src/utilities.jl | 6 +++++- test/fixed.jl | 19 +++++++++++++++++++ test/normed.jl | 21 +++++++++++++++++++-- 5 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/fixed.jl b/src/fixed.jl index fa74a26f..16343e18 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -103,10 +103,18 @@ function rem(x::Fixed, ::Type{F}) where {T, f, F <: Fixed{T,f}} end rem(x::Integer, ::Type{F}) where {T, f, F <: Fixed{T,f}} = F(_unsafe_trunc(T, x) << f, 0) function rem(x::Real, ::Type{F}) where {T, f, F <: Fixed{T,f}} - y = _unsafe_trunc(promote_type(Int64, T), round(x * @exp2(f))) + if bitwidth(T) < 32 + Ti = T + else + isfinite(x) || return zero(F) + Ti = promote_type(Int64, T) + end + Tf = floattype(F) + y = _unsafe_trunc(Ti, round(x * Tf(@exp2(f)))) reinterpret(F, _unsafe_trunc(T, y)) end function rem(x::BigFloat, ::Type{F}) where {T, f, F <: Fixed{T,f}} + isfinite(x) || return zero(F) reinterpret(F, _unsafe_trunc(T, round(x * @exp2(f)))) end diff --git a/src/normed.jl b/src/normed.jl index 084a926e..d7674fc2 100644 --- a/src/normed.jl +++ b/src/normed.jl @@ -110,18 +110,23 @@ 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))) +function rem(x::Real, ::Type{N}) where {T, N <: Normed{T}} + bitwidth(T) < 32 || isfinite(x) || return zero(N) + reinterpret(N, _unsafe_trunc(T, round(rawone(N) * x))) +end 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}} + isfinite(x) || return zero(N) 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}} + isfinite(x) || return zero(N) 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)) diff --git a/src/utilities.jl b/src/utilities.jl index f52a55ae..49abf2da 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -45,6 +45,10 @@ _unsafe_trunc(::Type{T}, x::BigFloat) where {T <: Integer} = trunc(BigInt, x) % if !signbit(signed(unsafe_trunc(UInt, -12.345))) # a workaround for ARM (issue #134) function _unsafe_trunc(::Type{T}, x::AbstractFloat) where {T <: Integer} - unsafe_trunc(T, unsafe_trunc(signedtype(T), x)) + if T === UInt32 + copysign(unsafe_trunc(T, abs(x)), x) + else + unsafe_trunc(T, unsafe_trunc(signedtype(T), x)) + end end end diff --git a/test/fixed.jl b/test/fixed.jl index d4bcb87e..db47e35a 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -202,6 +202,14 @@ end end end +@testset "conversions from float" begin + @testset "$F(nan)" for F in target(Fixed; ex = :thin) + @test_throws ArgumentError F(Inf) + @test_throws ArgumentError F(-Inf32) + @test_throws ArgumentError F(NaN) + end +end + @testset "conversions to float" begin for T in (Float16, Float32, Float64) @test isa(convert(T, Q0f7(0.3)), T) @@ -266,6 +274,11 @@ end @test -1 % Q0f7 === Q0f7(-1) @test -2 % Q0f7 === Q0f7(0) + + # TODO: avoid undefined behavior + @testset "nan % $F" for F in target(Fixed, :i8, :i16, :i32, :i64; ex = :thin) + @test NaN % F === NaN32 % F === NaN16 % F === zero(F) + end end @testset "mul" begin @@ -364,6 +377,12 @@ end @test clamp(0.5, Q0f7) === 0.5Q0f7 @test clamp(-1.5f0, Q0f7) === -1.0Q0f7 @test clamp(1.5Q1f6, Q0f7) === 0.992Q0f7 + + @testset "clamp(nan, $F)" for F in target(Fixed; ex = :thin) + @test clamp( Inf, F) === clamp( Inf32, F) === typemax(F) + @test clamp(-Inf, F) === clamp(-Inf32, F) === typemin(F) + @test clamp( NaN, F) === clamp( NaN32, F) === zero(F) + end end @testset "sign-related functions" begin diff --git a/test/normed.jl b/test/normed.jl index edca163f..2b9f24c2 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -173,7 +173,7 @@ end end end -@testset "conversion from float" begin +@testset "conversions from float" begin # issue 102 for Tf in (Float16, Float32, Float64) @testset "$N(::$Tf)" for N in target(Normed) @@ -202,6 +202,12 @@ end @test N0f32(Float32(0x0.7FFFFFp-32)) == zero(N0f32) @test N0f32(Float32(0x0.800000p-32)) <= eps(N0f32) # should be zero in RoundNearest mode @test N0f32(Float32(0x0.800001p-32)) == eps(N0f32) + + @testset "$N(nan)" for N in target(Normed; ex = :thin) + @test_throws ArgumentError N(Inf) + @test_throws ArgumentError N(-Inf32) + @test_throws ArgumentError N(NaN) + end end @testset "conversions to float" begin @@ -264,9 +270,14 @@ end @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) - # issu #211 + # issue #211 @test big"1.2" % N0f8 === 0.196N0f8 @test reinterpret(BigFloat(0x0_01234567_89abcdef) % N63f1) === 0x01234567_89abcdef + + # TODO: avoid undefined behavior + @testset "nan % $N" for N in target(Normed, :i8, :i16, :i32, :i64; ex = :thin) + @test NaN % N === NaN32 % N === NaN16 % N == zero(N) + end end @testset "arithmetic" begin @@ -385,6 +396,12 @@ end @test clamp(0.5, N0f8) === 0.5N0f8 @test clamp(-1.0f0, N0f8) === 0.0N0f8 @test clamp(2.0N1f7, N0f8) === 1.0N0f8 + + @testset "clamp(nan, $N)" for N in target(Normed; ex = :thin) + @test clamp( Inf, N) === clamp( Inf32, N) === typemax(N) + @test clamp(-Inf, N) === clamp(-Inf32, N) === typemin(N) + @test clamp( NaN, N) === clamp( NaN32, N) === zero(N) + end end @testset "sign-related functions" begin From 53ca2d85416c5ee331ebaa7200677609b94c0b2d Mon Sep 17 00:00:00 2001 From: kimikage Date: Mon, 31 Aug 2020 22:54:49 +0900 Subject: [PATCH 13/40] Backport partially "Commonize test codes (#224)" --- test/common.jl | 117 ++++++++++++++++++++++++++++++++++++++++++++- test/fixed.jl | 127 +++++++++++++++++++++++++++---------------------- test/normed.jl | 113 ++++++++++++++++++++++++------------------- 3 files changed, 249 insertions(+), 108 deletions(-) diff --git a/test/common.jl b/test/common.jl index 454cc673..60fa7faf 100644 --- a/test/common.jl +++ b/test/common.jl @@ -1,9 +1,15 @@ using FixedPointNumbers, Statistics, Random, Test using FixedPointNumbers: bitwidth, rawtype, nbitsfrac -# FIXME: Remove this comment (SP is borrowed from PR #224) SP = VERSION >= v"1.6.0-DEV.771" ? " " : "" # JuliaLang/julia #37085 +wrapping_neg = (-) +wrapping_abs = abs +wrapping_add = (+) +wrapping_sub = (-) +wrapping_mul = FixedPointNumbers.wrapping_mul +checked_mul = FixedPointNumbers.checked_mul + """ target(X::Type, Ss...; ex = :default) @@ -131,3 +137,112 @@ target_f_series(::Type{Normed}, T::Type{<:Integer}) = 23, 24, 31, 32, 33, 52, 53, 63, 64, 65, 112, 113, 127, 128) + +# generator for cartesian product +function xypairs(::Type{X}) where X + xs = typemin(X):eps(X):typemax(X) + ((x, y) for x in xs, y in xs) +end + + +function test_floattype(TX::Type) + @testset "floattype($X)" for X in target(TX, :i8, :i16, :i32, :i64; ex = :heavy) + @test typemax(X) <= maxintfloat(floattype(X)) + end +end + +function test_convert_from_nan(TX::Type) + @testset "$X(nan)" for X in target(TX; ex = :thin) + @test_throws ArgumentError X(Inf) + @test_throws ArgumentError X(-Inf32) + @test_throws ArgumentError X(NaN) + end +end + +function test_rem_type(TX::Type) + @testset "% $X" for X in target(TX, :i8, :i16; ex = :thin) + xs = typemin(X):0.1:typemax(X) + @test all(x -> x % X === X(x), xs) + end +end + +function test_rem_nan(TX::Type) + # TODO: avoid undefined behavior + @testset "nan % $X" for X in target(TX, :i8, :i16, :i32, :i64; ex = :thin) + @test NaN % X === NaN32 % X === NaN16 % X === zero(X) + end +end + +function test_neg(TX::Type) + for X in target(TX, :i8; ex = :thin) + xs = typemin(X):eps(X):typemax(X) + fneg(x) = -float(x) + @test all(x -> wrapping_neg(wrapping_neg(x)) === x, xs) + end +end + +function test_abs(TX::Type) + for X in target(TX, :i8; ex = :thin) + xs = typemin(X):eps(X):typemax(X) + fabs(x) = abs(float(x)) + @test all(x -> wrapping_abs(x) === (x > 0 ? x : wrapping_neg(x)), xs) + end +end + +function test_add(TX::Type) + for X in target(TX, :i8; ex = :thin) + xys = xypairs(X) + fadd(x, y) = float(x) + float(y) + @test all(((x, y),) -> wrapping_sub(wrapping_add(x, y), y) === x, xys) + end +end + +function test_sub(TX::Type) + for X in target(TX, :i8; ex = :thin) + xys = xypairs(X) + fsub(x, y) = float(x) - float(y) + @test all(((x, y),) -> wrapping_add(wrapping_sub(x, y), y) === x, xys) + end +end + +function test_mul(TX::Type) + for X in target(TX, :i8; ex = :thin) + xys = xypairs(X) + fmul(x, y) = float(x) * float(y) # note that precision(Float32) < 32 + @test all(((x, y),) -> wrapping_mul(x, y) === fmul(x, y) % X, xys) + end +end + +function test_isapprox(TX::Type) + @testset "approx $X" for X in target(TX, :i8, :i16; ex = :light) + xs = typemin(X):eps(X):typemax(X)-eps(X) + @test all(x -> x ≈ x + eps(X), xs) + @test all(x -> x + eps(X) ≈ x, xs) + @test !any(x -> x - eps(X) ≈ x + eps(X), xs) + end + +end + +function test_clamp_nan(TX::Type) + @testset "clamp(nan, $X)" for X in target(TX; ex = :thin) + @test clamp( Inf, X) === clamp( Inf32, X) === typemax(X) + @test clamp(-Inf, X) === clamp(-Inf32, X) === typemin(X) + @test clamp( NaN, X) === clamp( NaN32, X) === zero(X) + end +end + +function test_isinteger(TX::Type) + @testset "isinteger(::$X)" for X in target(TX, :i8, :i16) + xs = typemin(X):eps(X):typemax(X) + @test all(x -> isinteger(x) == isinteger(float(x)), xs) + end +end + +function test_rand(TX::Type) + @testset "rand(::$X)" for X in target(TX; ex = :thin) + @test isa(rand(X), X) + a = rand(X, (3, 5)) + @test ndims(a) == 2 && eltype(a) === X + @test size(a) == (3, 5) + end +end diff --git a/test/fixed.jl b/test/fixed.jl index db47e35a..a5c1af2a 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -197,17 +197,11 @@ end @test float(0.75Q7f24) === 0.75 @test float(0.75Q10f53)::BigFloat == big"0.75" - @testset "floattype($F)" for F in target(Fixed, :i8, :i16, :i32, :i64; ex = :heavy) - @test typemax(F) <= maxintfloat(floattype(F)) - end + test_floattype(Fixed) end @testset "conversions from float" begin - @testset "$F(nan)" for F in target(Fixed; ex = :thin) - @test_throws ArgumentError F(Inf) - @test_throws ArgumentError F(-Inf32) - @test_throws ArgumentError F(NaN) - end + test_convert_from_nan(Fixed) end @testset "conversions to float" begin @@ -217,22 +211,22 @@ end for Tf in (Float16, Float32, Float64) @testset "$Tf(::$F)" for F in target(Fixed, :i8, :i16) - T, f = rawtype(F), nbitsfrac(F) - float_err = 0.0 + T, exp2mf = rawtype(F), big(2.0^-nbitsfrac(F)) + float_err = zero(Tf) for i = typemin(T):typemax(T) - f_expected = Tf(i * BigFloat(2)^-f) + f_expected = Tf(i * exp2mf) f_actual = Tf(reinterpret(F, i)) float_err += abs(f_actual - f_expected) end - @test float_err == 0.0 + @test float_err == 0 end @testset "$Tf(::$F)" for F in target(Fixed, :i32, :i64, :i128) - T, f = rawtype(F), nbitsfrac(F) + T, exp2mf = rawtype(F), big(2.0^-nbitsfrac(F)) error_count = 0 for i in vcat(typemin(T):(typemin(T)+0xFF), -T(0xFF):T(0xFF), (typemax(T)-0xFF):typemax(T)) - f_expected = Tf(i * BigFloat(2)^-f) + f_expected = Tf(i * exp2mf) isinf(f_expected) && break # for Float16() and Float32() f_actual = Tf(reinterpret(F, i)) f_actual == f_expected && continue @@ -254,35 +248,66 @@ end end @testset "type modulus" begin + test_rem_type(Fixed) + test_rem_nan(Fixed) + @test Q0f7(0.2) % Q0f7 === Q0f7(0.2) @test Q1f14(1.2) % Q0f15 === Q0f15(-0.8) @test Q1f14(1.2) % Q0f7 === Q0f7(-0.8) - T = Fixed{Int8,7} - for i = -1.0:0.1:typemax(T) - @test i % T === T(i) - end - @test ( 1.5 % T).i == round(Int, 1.5*128) % Int8 - @test (-0.3 % T).i == round(Int, -0.3*128) % Int8 + @test ( 1.5 % Q0f7).i == round(Int, 1.5*128) % Int8 + @test (-0.3 % Q0f7).i == round(Int, -0.3*128) % Int8 - T = Fixed{Int16,9} - for i = -64.0:0.1:typemax(T) - @test i % T === T(i) - end - @test ( 65.2 % T).i == round(Int, 65.2*512) % Int16 - @test (-67.2 % T).i == round(Int, -67.2*512) % Int16 + @test ( 65.2 % Q6f9).i == round(Int, 65.2*512) % Int16 + @test (-67.2 % Q6f9).i == round(Int, -67.2*512) % Int16 @test -1 % Q0f7 === Q0f7(-1) @test -2 % Q0f7 === Q0f7(0) +end + +@testset "neg" begin + for F in target(Fixed; ex = :thin) + @test wrapping_neg(typemin(F)) === typemin(F) + + @test wrapping_neg(typemax(F)) === typemin(F) + eps(F) - # TODO: avoid undefined behavior - @testset "nan % $F" for F in target(Fixed, :i8, :i16, :i32, :i64; ex = :thin) - @test NaN % F === NaN32 % F === NaN16 % F === zero(F) + @test wrapping_neg(eps(F)) === zero(F) - eps(F) end + test_neg(Fixed) +end + +@testset "abs" begin + for F in target(Fixed; ex = :thin) + @test wrapping_abs(typemax(F)) === typemax(F) + + @test wrapping_abs(typemin(F)) === typemin(F) + end + test_abs(Fixed) +end + +@testset "add" begin + for F in target(Fixed; ex = :thin) + @test wrapping_add(typemin(F), typemin(F)) === zero(F) + + @test wrapping_add(typemax(F), eps(F)) === wrapping_add(eps(F), typemax(F)) === typemin(F) + + @test wrapping_add(zero(F), eps(F)) === wrapping_add(eps(F), zero(F)) === eps(F) + end + test_add(Fixed) +end + +@testset "sub" begin + for F in target(Fixed; ex = :thin) + @test wrapping_sub(typemin(F), typemin(F)) === zero(F) + + @test wrapping_sub(typemin(F), eps(F)) === typemax(F) + + @test wrapping_sub(eps(F), zero(F)) === eps(F) + end + test_sub(Fixed) end @testset "mul" begin - wrapping_mul = FixedPointNumbers.wrapping_mul for F in target(Fixed; ex = :thin) @test wrapping_mul(typemax(F), zero(F)) === zero(F) @@ -292,12 +317,7 @@ end @test wrapping_mul(typemin(F), typemin(F)) === big(typemin(F))^2 % F end - for F in target(Fixed, :i8; ex = :thin) - xs = typemin(F):eps(F):typemax(F) - xys = ((x, y) for x in xs, y in xs) - fmul(x, y) = float(x) * float(y) # note that precision(Float32) < 32 - @test all(((x, y),) -> wrapping_mul(x, y) === fmul(x, y) % F, xys) - end + test_mul(Fixed) FixedPointNumbers.mul_with_rounding(1.5Q6f1, 0.5Q6f1, RoundNearest) === 1.0Q6f1 FixedPointNumbers.mul_with_rounding(1.5Q6f1, -0.5Q6f1, RoundNearest) === -1.0Q6f1 @@ -358,12 +378,17 @@ end end @testset "approx" begin - @testset "approx $F" for F in target(Fixed, :i8, :i16; ex = :light) - xs = typemin(F):eps(F):typemax(F)-eps(F) - @test all(x -> x ≈ x + eps(F), xs) - @test all(x -> x + eps(F) ≈ x, xs) - @test !any(x -> x - eps(F) ≈ x + eps(F), xs) - end + test_isapprox(Fixed) + + # PR #216 required + @test_broken isapprox(-0.5Q0f7, -1Q0f7, rtol=0.5, atol=0) # issue 209 + @test_broken isapprox(typemin(Q0f7), typemax(Q0f7), rtol=2.0) + @test !isapprox(zero(Q0f7), typemax(Q0f7), rtol=0.9) + @test isapprox(zero(Q0f7), eps(Q0f7), rtol=1e-6) # atol = eps(Q0f7) + @test !isapprox(eps(Q0f7), zero(Q0f7), rtol=1e-6, atol=1e-6) + @test_broken !isapprox(1.0Q6f1, 1.5Q6f1, rtol=0.3, atol=0) # 1.5 * 0.3 < eps(Q6f1) + + @test isapprox(eps(Q8f7), eps(Q0f7), rtol=1e-6) end @testset "clamp" begin @@ -378,11 +403,7 @@ end @test clamp(-1.5f0, Q0f7) === -1.0Q0f7 @test clamp(1.5Q1f6, Q0f7) === 0.992Q0f7 - @testset "clamp(nan, $F)" for F in target(Fixed; ex = :thin) - @test clamp( Inf, F) === clamp( Inf32, F) === typemax(F) - @test clamp(-Inf, F) === clamp(-Inf32, F) === typemin(F) - @test clamp( NaN, F) === clamp( NaN32, F) === zero(F) - end + test_clamp_nan(Fixed) end @testset "sign-related functions" begin @@ -413,10 +434,7 @@ end @test !isinf(1Q7f8) @testset "isinteger" begin - @testset "isinteger(::$F)" for F in target(Fixed, :i8, :i16) - xs = typemin(F):eps(F):typemax(F) - @test all(x -> isinteger(x) == isinteger(float(x)), xs) - end + test_isinteger(Fixed) @testset "isinteger(::$F)" for F in target(Fixed, :i32, :i64, :i128) fzero, fmax, fmin = zero(F), typemax(F), typemin(F) if nbitsfrac(F) == 0 @@ -486,12 +504,7 @@ end end @testset "rand" begin - @testset "rand(::$F)" for F in target(Fixed; ex = :thin) - @test isa(rand(F), F) - a = rand(F, (3, 5)) - @test ndims(a) == 2 && eltype(a) === F - @test size(a) == (3,5) - end + test_rand(Fixed) @test rand(MersenneTwister(1234), Q0f7) === -0.156Q0f7 end diff --git a/test/normed.jl b/test/normed.jl index 2b9f24c2..8f07980b 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -168,9 +168,7 @@ end @test float(0.8N8f24) === 0.8 @test float(1N11f53)::BigFloat == big"1.0" - @testset "floattype($N)" for N in target(Normed, :i8, :i16, :i32, :i64; ex = :heavy) - @test typemax(N) <= maxintfloat(floattype(N)) - end + test_floattype(Normed) end @testset "conversions from float" begin @@ -203,11 +201,7 @@ end @test N0f32(Float32(0x0.800000p-32)) <= eps(N0f32) # should be zero in RoundNearest mode @test N0f32(Float32(0x0.800001p-32)) == eps(N0f32) - @testset "$N(nan)" for N in target(Normed; ex = :thin) - @test_throws ArgumentError N(Inf) - @test_throws ArgumentError N(-Inf32) - @test_throws ArgumentError N(NaN) - end + test_convert_from_nan(Normed) end @testset "conversions to float" begin @@ -220,14 +214,14 @@ end for Tf in (Float16, Float32, Float64) @testset "$Tf(::$N)" for N in target(Normed, :i8, :i16) T = rawtype(N) - float_err = 0.0 + float_err = zero(Tf) 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(N, i)) float_err += abs(f_actual - f_expected) end - @test float_err == 0.0 + @test float_err == 0 end @testset "$Tf(::$N)" for N in target(Normed, :i32, :i64, :i128) T = rawtype(N) @@ -247,19 +241,16 @@ end end @testset "type modulus" begin + test_rem_type(Normed) + test_rem_nan(Normed) + @test N0f8(0.2) % N0f8 === N0f8(0.2) @test N2f14(1.2) % N0f16 === N0f16(0.20002) @test N2f14(1.2) % N0f8 === N0f8(0.196) - for i = 0.0:0.1:1.0 - @test i % N0f8 === N0f8(i) - end @test ( 1.5 % N0f8).i == round(Int, 1.5*255) % UInt8 @test (-0.3 % N0f8).i == round(Int, -0.3*255) % UInt8 - for i = 0.0:0.1:64.0 - @test i % N6f10 === N6f10(i) - end @test (65.2 % N6f10).i == round(Int, 65.2*1023) % UInt16 @test (-0.3 % N6f10).i == round(Int, -0.3*1023) % UInt16 @@ -273,11 +264,6 @@ end # issue #211 @test big"1.2" % N0f8 === 0.196N0f8 @test reinterpret(BigFloat(0x0_01234567_89abcdef) % N63f1) === 0x01234567_89abcdef - - # TODO: avoid undefined behavior - @testset "nan % $N" for N in target(Normed, :i8, :i16, :i32, :i64; ex = :thin) - @test NaN % N === NaN32 % N === NaN16 % N == zero(N) - end end @testset "arithmetic" begin @@ -299,8 +285,49 @@ end end end +@testset "neg" begin + for N in target(Normed; ex = :thin) + @test wrapping_neg(typemin(N)) === zero(N) + + @test wrapping_neg(typemax(N)) === eps(N) + + @test wrapping_neg(eps(N)) === typemax(N) + end + test_neg(Normed) +end + +@testset "abs" begin + for N in target(Normed; ex = :thin) + @test wrapping_abs(typemax(N)) === typemax(N) + + @test wrapping_abs(typemin(N)) === typemin(N) + end + test_abs(Normed) +end + +@testset "add" begin + for N in target(Normed; ex = :thin) + @test wrapping_add(typemin(N), typemin(N)) === zero(N) + + @test wrapping_add(typemax(N), eps(N)) === wrapping_add(eps(N), typemax(N)) === zero(N) + + @test wrapping_add(zero(N), eps(N)) === wrapping_add(eps(N), zero(N)) === eps(N) + end + test_add(Normed) +end + +@testset "sub" begin + for N in target(Normed; ex = :thin) + @test wrapping_sub(typemin(N), typemin(N)) === zero(N) + + @test wrapping_sub(typemin(N), eps(N)) === typemax(N) + + @test wrapping_sub(eps(N), zero(N)) === eps(N) + end + test_sub(Normed) +end + @testset "mul" begin - checked_mul = FixedPointNumbers.checked_mul for N in target(Normed; ex = :thin) @test checked_mul(typemax(N), zero(N)) === zero(N) @@ -310,13 +337,7 @@ end @test_throws OverflowError checked_mul(typemax(N), typemax(N)) end end - for N in target(Normed, :i8; ex = :thin) - xs = typemin(N):eps(N):typemax(N) - xys = ((x, y) for x in xs, y in xs) - fmul(x, y) = float(x) * float(y) # note that precision(Float32) < 32 - @test all(((x, y),) -> !(typemin(N) <= fmul(x, y) <= typemax(N)) || - (fmul(x, y) % N) === checked_mul(x, y), xys) - end + test_mul(Normed) end @testset "div/fld1" begin @@ -370,12 +391,16 @@ end end @testset "approx" begin - @testset "approx $N" for N in target(Normed, :i8, :i16; ex = :light) - xs = typemin(N):eps(N):typemax(N)-eps(N) - @test all(x -> x ≈ x + eps(N), xs) - @test all(x -> x + eps(N) ≈ x, xs) - @test !any(x -> x - eps(N) ≈ x + eps(N), xs) - end + test_isapprox(Normed) + + # PR #216 required + @test_broken isapprox(typemin(N0f8), typemax(N0f8), rtol=1.0) + @test !isapprox(zero(N0f8), typemax(N0f8), rtol=0.9) + @test isapprox(zero(N0f8), eps(N0f8), rtol=1e-6) # atol = eps(N0f8) + @test !isapprox(eps(N0f8), zero(N0f8), rtol=1e-6, atol=1e-6) + @test_broken !isapprox(0.66N6f2, 1.0N6f2, rtol=0.3, atol=0) # 1.0 * 0.3 < eps(N6f2) + + @test isapprox(eps(N8f8), eps(N0f8), rtol=1e-6) end @testset "comparison" begin @@ -397,11 +422,7 @@ end @test clamp(-1.0f0, N0f8) === 0.0N0f8 @test clamp(2.0N1f7, N0f8) === 1.0N0f8 - @testset "clamp(nan, $N)" for N in target(Normed; ex = :thin) - @test clamp( Inf, N) === clamp( Inf32, N) === typemax(N) - @test clamp(-Inf, N) === clamp(-Inf32, N) === typemin(N) - @test clamp( NaN, N) === clamp( NaN32, N) === zero(N) - end + test_clamp_nan(Normed) end @testset "sign-related functions" begin @@ -432,10 +453,7 @@ end @test !isinf(1N8f8) @testset "isinteger" begin - @testset "isinteger(::$N)" for N in target(Normed, :i8, :i16) - xs = typemin(N):eps(N):typemax(N) - @test all(x -> isinteger(x) == isinteger(float(x)), xs) - end + test_isinteger(Normed) @testset "isinteger(::$N)" for N in target(Normed, :i32, :i64, :i128) if nbitsfrac(N) == 1 @test isinteger(zero(N)) & isinteger(oneunit(N)) @@ -501,12 +519,7 @@ end end @testset "rand" begin - @testset "rand(::$N)" for N in target(Normed; ex = :thin) - @test isa(rand(N), N) - a = rand(N, (3, 5)) - @test ndims(a) == 2 && eltype(a) === N - @test size(a) == (3,5) - end + test_rand(Normed) @test rand(MersenneTwister(1234), N0f8) === 0.925N0f8 end From c845201360d9fde213ad6f1aa130470c3096e2cd Mon Sep 17 00:00:00 2001 From: kimikage Date: Tue, 1 Sep 2020 07:06:25 +0900 Subject: [PATCH 14/40] Backport partially #222 --- test/common.jl | 11 +++++++++++ test/fixed.jl | 16 ++++++++++++++++ test/normed.jl | 15 +++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/test/common.jl b/test/common.jl index 60fa7faf..5f5aa4b4 100644 --- a/test/common.jl +++ b/test/common.jl @@ -9,6 +9,7 @@ wrapping_add = (+) wrapping_sub = (-) wrapping_mul = FixedPointNumbers.wrapping_mul checked_mul = FixedPointNumbers.checked_mul +checked_fdiv = (/) # The current implementations are inconsistent (do not check properly). """ target(X::Type, Ss...; ex = :default) @@ -213,6 +214,16 @@ function test_mul(TX::Type) end end +function test_fdiv(TX::Type) + for X in target(TX, :i8; ex = :thin) + xys = xypairs(X) + fdiv(x, y) = oftype(float(x), big(x) / big(y)) + fdivz(x, y) = y === zero(y) ? float(y) : fdiv(x, y) + @test_broken all(((x, y),) -> !(typemin(X) <= fdiv(x, y) <= typemax(X)) || + checked_fdiv(x, y) === fdivz(x, y) % X, xys) + end +end + function test_isapprox(TX::Type) @testset "approx $X" for X in target(TX, :i8, :i16; ex = :light) xs = typemin(X):eps(X):typemax(X)-eps(X) diff --git a/test/fixed.jl b/test/fixed.jl index a5c1af2a..a353e4df 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -327,6 +327,22 @@ end FixedPointNumbers.mul_with_rounding(1.5Q6f1, -0.5Q6f1, RoundDown) === -1.0Q6f1 end +@testset "fdiv" begin + for F in target(Fixed; ex = :thin) + @test checked_fdiv(typemax(F), -typemax(F)) === F(-1) + + @test checked_fdiv(zero(F), typemin(F)) === zero(F) + + # OverflowError on v0.9 (#222) + @test_broken (try; checked_fdiv(typemin(F), F(-1)); catch e; e; end) isa Exception + + @test_throws Exception checked_fdiv(zero(F), zero(F)) # DivideError on v0.9 (#222) + + @test_throws Exception checked_fdiv(-eps(F), zero(F)) # DivideError on v0.9 (#222) + end + test_fdiv(Fixed) +end + @testset "rounding" begin for sym in (:i8, :i16, :i32, :i64) T = symbol_to_inttype(Fixed, sym) diff --git a/test/normed.jl b/test/normed.jl index 8f07980b..15185c1d 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -340,6 +340,21 @@ end test_mul(Normed) end +@testset "fdiv" begin + for N in target(Normed; ex = :thin) + @test checked_fdiv(typemax(N), typemax(N)) === one(N) + + @test checked_fdiv(zero(N), eps(N)) === zero(N) + + @test_throws Exception checked_fdiv(typemax(N), eps(N)) # OverflowError on v0.9 (#222) + + @test_throws Exception checked_fdiv(zero(N), zero(N)) # DivideError on v0.9 (#222) + + @test_throws Exception checked_fdiv(eps(N), zero(N)) # DivideError on v0.9 (#222) + end + test_fdiv(Normed) +end + @testset "div/fld1" begin @test div(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == 8 @test div(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == 7 From 927afc0e5c8b3513cbf048b5be96a843364a731d Mon Sep 17 00:00:00 2001 From: kimikage Date: Fri, 11 Sep 2020 22:49:07 +0900 Subject: [PATCH 15/40] Backport partially "Add checked, wrapping and saturating arithmetic for div/cld/fld (#226)" The wrapping and saturating `div`/`cld`/`fld` are not backported. The default arithmetic for `div` is still checked arithmetic. This changes the error type for overflow from `DivideError` to `OverflowError`. This also adds the support for `cld` and 3-arg `div`. --- src/FixedPointNumbers.jl | 25 ++++++++++++++++++++--- test/common.jl | 44 ++++++++++++++++++++++++++++++++++++++++ test/fixed.jl | 38 ++++++++++++++++++++++++++++++++++ test/normed.jl | 35 +++++++++++++++++++++++++------- 4 files changed, 132 insertions(+), 10 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 0e3aa8ac..a9e287d0 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -5,14 +5,15 @@ import Base: ==, <, <=, -, +, *, /, ~, isapprox, isnan, isinf, isfinite, isinteger, zero, oneunit, one, typemin, typemax, floatmin, floatmax, eps, reinterpret, big, rationalize, float, trunc, round, floor, ceil, bswap, clamp, - div, fld, rem, mod, mod1, fld1, min, max, minmax, + div, fld, cld, rem, mod, mod1, fld1, min, max, minmax, signed, unsigned, copysign, flipsign, signbit, length import Statistics # for _mean_promote import Random: Random, AbstractRNG, SamplerType, rand! -using Base.Checked: checked_add, checked_sub, checked_div +import Base.Checked: checked_mul, checked_div +using Base.Checked: checked_add, checked_sub using Base: @pure @@ -183,6 +184,18 @@ float(x::FixedPoint) = convert(floattype(x), x) wrapping_mul(x::X, y::X) where {X <: FixedPoint} = (float(x) * float(y)) % X *(x::X, y::X) where {X <: FixedPoint} = wrapping_mul(x, y) +function checked_div(x::X, y::X, r::RoundingMode = RoundToZero) where {T, X <: FixedPoint{T}} + y === zero(X) && throw(DivideError()) + z = round(floattype(X)(x.i) / floattype(X)(y.i), r) + if T <: Signed + z <= typemax(T) || throw_overflowerror_div(r, x, y) + end + _unsafe_trunc(T, z) +end +div(x::X, y::X, r::RoundingMode = RoundToZero) where {X <: FixedPoint} = checked_div(x, y, r) +fld(x::X, y::X) where {X <: FixedPoint} = checked_div(x, y, RoundDown) +cld(x::X, y::X) where {X <: FixedPoint} = checked_div(x, y, RoundUp) + function minmax(x::X, y::X) where {X <: FixedPoint} a, b = minmax(reinterpret(x), reinterpret(y)) X(a,0), X(b,0) @@ -233,7 +246,7 @@ 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 (:(==), :<, :<=, :fld1) @eval begin $f(x::X, y::X) where {X <: FixedPoint} = $f(x.i, y.i) end @@ -366,6 +379,12 @@ end showtype(io, typeof(x)) throw(OverflowError(String(take!(io)))) end +@noinline function throw_overflowerror_div(r::RoundingMode, @nospecialize(x), @nospecialize(y)) + io = IOBuffer() + op = r === RoundUp ? "cld(" : r === RoundDown ? "fld(" : "div(" + print(io, op, x, ", ", y, ") overflowed for type ", rawtype(x)) + throw(OverflowError(String(take!(io)))) +end function Random.rand(r::AbstractRNG, ::SamplerType{X}) where X <: FixedPoint X(rand(r, rawtype(X)), 0) diff --git a/test/common.jl b/test/common.jl index 5f5aa4b4..8826fce7 100644 --- a/test/common.jl +++ b/test/common.jl @@ -10,6 +10,9 @@ wrapping_sub = (-) wrapping_mul = FixedPointNumbers.wrapping_mul checked_mul = FixedPointNumbers.checked_mul checked_fdiv = (/) # The current implementations are inconsistent (do not check properly). +checked_div = FixedPointNumbers.checked_div +checked_fld = fld +checked_cld = cld """ target(X::Type, Ss...; ex = :default) @@ -224,6 +227,47 @@ function test_fdiv(TX::Type) end end +function test_div(TX::Type) + for X in target(TX, :i8; ex = :thin) + T = rawtype(X) + xys = xypairs(X) + fdiv(x, y) = oftype(float(x), big(x) / big(y)) + @test all(xys) do (x, y) + z = fdiv(x, y) + t = !(typemin(T) <= trunc(z) <= typemax(T)) || trunc(z) == checked_div(x, y) + f = !(typemin(T) <= floor(z) <= typemax(T)) || floor(z) == checked_fld(x, y) + c = !(typemin(T) <= ceil(z) <= typemax(T)) || ceil(z) == checked_cld(x, y) + return t & f & c + end + end +end + +function test_div_3arg(TX::Type) + for X in target(TX; ex = :thin) + @test div(eps(X), typemax(X), RoundToZero) === div(eps(X), typemax(X)) + @test div(eps(X), typemax(X), RoundDown) === fld(eps(X), typemax(X)) + @test div(eps(X), typemax(X), RoundUp) === cld(eps(X), typemax(X)) + end +end + +function test_fld1_mod1(TX::Type) + for X in target(TX, :i8, :i16; ex = :thin) + T = rawtype(X) + eps2 = eps(X) + eps(X) + xs = reinterpret.(X, T.((17, 16, 15, 14))) + @test all(fld1.(xs, eps2) .=== T.((9, 8, 8, 7))) + @test_throws DivideError fld1(eps(X), zero(X)) + + @test all(mod1.(xs, eps2) .=== reinterpret.(X, T.((1, 2, 1, 2)))) + @test_throws DivideError mod1(eps(X), zero(X)) + + d, r = fldmod1(typemin(X), eps2) + @test d isa T && r isa X && ((d - 1.0) * eps2 + r) % X === typemin(X) # use `1.0` instead of `1` + d, r = fldmod1(typemax(X), eps2) + @test d isa T && r isa X && ((d - 1.0) * eps2 + r) % X === typemax(X) # use `1.0` instead of `1` + end +end + function test_isapprox(TX::Type) @testset "approx $X" for X in target(TX, :i8, :i16; ex = :light) xs = typemin(X):eps(X):typemax(X)-eps(X) diff --git a/test/fixed.jl b/test/fixed.jl index a353e4df..db0aab1d 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -343,6 +343,44 @@ end test_fdiv(Fixed) end +@testset "div/cld/fld" begin + for F in target(Fixed; ex = :thin) + fm, fn, fz, fe = typemax(F), typemin(F), zero(F), eps(F) + T = rawtype(F) + @test checked_div(fm, fm) === checked_fld(fm, fm) === checked_cld(fm, fm) === one(T) + + @test checked_div(fz, fe) === checked_fld(fz, fe) === checked_cld(fz, fe) === zero(T) + + @test checked_div(fm, fe) === checked_fld(fm, fe) === checked_cld(fm, fe) === typemax(T) + + @test_throws DivideError checked_div(fz, fz) + @test_throws DivideError checked_fld(fz, fz) + @test_throws DivideError checked_cld(fz, fz) + + @test_throws DivideError checked_div(fe, fz) + @test_throws DivideError checked_fld(fe, fz) + @test_throws DivideError checked_cld(fe, fz) + + @test_throws OverflowError checked_div(fn, -fe) + @test_throws OverflowError checked_fld(fn, -fe) + @test_throws OverflowError checked_cld(fn, -fe) + + @test checked_div(fe, fm) === zero(T) + @test checked_fld(fe, fm) === zero(T) + @test checked_cld(fe, fm) === one(T) + + @test checked_div(fe, fn) === zero(T) + @test checked_fld(fe, fn) === -one(T) + @test checked_cld(fe, fn) === zero(T) + end + test_div(Fixed) + test_div_3arg(Fixed) +end + +@testset "fld1/mod1" begin + test_fld1_mod1(Fixed) +end + @testset "rounding" begin for sym in (:i8, :i16, :i32, :i64) T = symbol_to_inttype(Fixed, sym) diff --git a/test/normed.jl b/test/normed.jl index 15185c1d..22ebbfde 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -355,18 +355,39 @@ end test_fdiv(Normed) end -@testset "div/fld1" begin - @test div(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == 8 - @test div(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == 7 - @test fld1(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == 8 - @test fld1(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == 8 +@testset "div/cld/fld" begin + for N in target(Normed; ex = :thin) + nm, nz, ne = typemax(N), zero(N), eps(N) + T = rawtype(N) + @test checked_div(nm, nm) === checked_fld(nm, nm) === checked_cld(nm, nm) === one(T) + + @test checked_div(nz, ne) === checked_fld(nz, ne) === checked_cld(nz, ne) === zero(T) + + @test checked_div(nm, ne) === checked_fld(nm, ne) === checked_cld(nm, ne) === typemax(T) + + @test_throws DivideError checked_div(nz, nz) + @test_throws DivideError checked_fld(nz, nz) + @test_throws DivideError checked_cld(nz, nz) + + @test_throws DivideError checked_div(ne, nz) + @test_throws DivideError checked_fld(ne, nz) + @test_throws DivideError checked_cld(ne, nz) + + @test checked_div(ne, nm) === zero(T) + @test checked_fld(ne, nm) === zero(T) + @test checked_cld(ne, nm) === one(T) + end + test_div(Normed) + test_div_3arg(Normed) end @testset "rem/mod" begin @test mod(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == rem(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == 0 @test mod(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == rem(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == reinterpret(N0f8, 0x01) - @test mod1(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == reinterpret(N0f8, 0x02) - @test mod1(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == reinterpret(N0f8, 0x01) +end + +@testset "fld1/mod1" begin + test_fld1_mod1(Normed) end @testset "rounding" begin From 9e5c68ecf5e30728a3c6cae1cedcb62e9256d04b Mon Sep 17 00:00:00 2001 From: Johnny Chen Date: Sat, 19 Sep 2020 19:57:19 +0800 Subject: [PATCH 16/40] add doctest in unittest (#229) --- Project.toml | 3 ++- src/FixedPointNumbers.jl | 2 +- test/runtests.jl | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 5f3ec55b..00cf1046 100644 --- a/Project.toml +++ b/Project.toml @@ -10,7 +10,8 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" julia = "1" [extras] +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test"] +test = ["Documenter", "Test"] diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index a9e287d0..f8d7ea03 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -139,7 +139,7 @@ except in scenarios where they are guaranteed to have hardware support. A classic usage is to avoid overflow behavior by promoting `FixedPoint` to `AbstractFloat` -```jldoctest +```jldoctest; setup = :(using FixedPointNumbers) julia> x = N0f8(1.0) 1.0N0f8 diff --git a/test/runtests.jl b/test/runtests.jl index 570cec53..810e13d3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,6 +2,9 @@ using FixedPointNumbers, Test @test isempty(detect_ambiguities(FixedPointNumbers, Base, Core)) +using Documenter +doctest(FixedPointNumbers, manual = false) + @testset "normed" begin include("normed.jl") end From b126c1b5231589fd01ffcc7c9e8884c95061f498 Mon Sep 17 00:00:00 2001 From: kimikage Date: Sun, 20 Sep 2020 16:55:11 +0900 Subject: [PATCH 17/40] Remove old iterator methods (#231) This removes `start`, `next` and `done`. They are no longer used in Julia v1 and were left as private methods of `FixedPointNumbers`. --- src/normed.jl | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/normed.jl b/src/normed.jl index d7674fc2..c085bcaf 100644 --- a/src/normed.jl +++ b/src/normed.jl @@ -316,16 +316,6 @@ function round(::Type{Ti}, x::Normed) where {Ti <: Integer} convert(Ti, r > (rawone(x) >> 0x1) ? d + oneunit(rawtype(x)) : d) end -# Iteration -# The main subtlety here is that iterating over N0f8(0):N0f8(1) will wrap around -# unless we iterate using a wider type -@inline start(r::StepRange{T}) where {T <: Normed} = widen1(reinterpret(r.start)) -@inline next(r::StepRange{T}, i::Integer) where {T <: Normed} = (T(i,0), i+reinterpret(r.step)) -@inline function done(r::StepRange{T}, i::Integer) where {T <: Normed} - i1, i2 = reinterpret(r.start), reinterpret(r.stop) - isempty(r) | (i < min(i1, i2)) | (i > max(i1, i2)) -end - function decompose(x::Normed) g = gcd(reinterpret(x), rawone(x)) div(reinterpret(x),g), 0, div(rawone(x),g) From de7faee891d9bd4095699df2f571004dd34f9132 Mon Sep 17 00:00:00 2001 From: kimikage Date: Tue, 30 Apr 2024 21:44:06 +0900 Subject: [PATCH 18/40] Backport partially #235 --- src/FixedPointNumbers.jl | 3 +++ src/normed.jl | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index f8d7ea03..9fb56056 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -241,6 +241,9 @@ end signbit(x::X) where {X <: FixedPoint} = signbit(x.i) +trunc(x::X) where {X <: FixedPoint{<:Unsigned}} = floor(x) +trunc(::Type{Ti}, x::X) where {X <: FixedPoint{<:Unsigned}, Ti <: Integer} = floor(Ti, x) + 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 c085bcaf..57e6c3cc 100644 --- a/src/normed.jl +++ b/src/normed.jl @@ -286,7 +286,6 @@ end /(x::T, y::T) where {T <: Normed} = convert(T,convert(floattype(T), x)/convert(floattype(T), y)) # Functions -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 @@ -303,7 +302,6 @@ function round(x::Normed{T,f}) where {T, f} reinterpret(Normed{T,f}, r > q ? x.i + q : x.i - r) end -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 From 3d371dccc6372269424db81e85268b7c63eeb446 Mon Sep 17 00:00:00 2001 From: kimikage Date: Sat, 19 Dec 2020 11:56:31 +0900 Subject: [PATCH 19/40] Use GitHub Actions for unit testing on Arm arch. (#238) The test runs on a docker container with QEMU. This also updates TagBot.yml. --- .github/workflows/TagBot.yml | 7 +++- .github/workflows/UnitTest.yml | 4 +- .github/workflows/UnitTestArm.yml | 66 +++++++++++++++++++++++++++++++ .travis.yml | 15 ------- test/runtests.jl | 6 ++- 5 files changed, 76 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/UnitTestArm.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml index d77d3a0c..778c06fe 100644 --- a/.github/workflows/TagBot.yml +++ b/.github/workflows/TagBot.yml @@ -1,9 +1,12 @@ name: TagBot on: - schedule: - - cron: 0 * * * * + issue_comment: + types: + - created + workflow_dispatch: jobs: TagBot: + if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' runs-on: ubuntu-latest steps: - uses: JuliaRegistries/TagBot@v1 diff --git a/.github/workflows/UnitTest.yml b/.github/workflows/UnitTest.yml index 24695405..8032ea37 100644 --- a/.github/workflows/UnitTest.yml +++ b/.github/workflows/UnitTest.yml @@ -38,7 +38,7 @@ jobs: env: cache-name: cache-artifacts with: - path: ~/.julia/artifacts + path: ~/.julia/artifacts key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} restore-keys: | ${{ runner.os }}-test-${{ env.cache-name }}- @@ -51,5 +51,3 @@ jobs: - uses: codecov/codecov-action@v1 with: file: lcov.info - - diff --git a/.github/workflows/UnitTestArm.yml b/.github/workflows/UnitTestArm.yml new file mode 100644 index 00000000..f31e3bc2 --- /dev/null +++ b/.github/workflows/UnitTestArm.yml @@ -0,0 +1,66 @@ +name: Unit test for Arm + +on: + push: + branches: + - master + pull_request: + workflow_dispatch: +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + julia-version: ['1.0', '1', 'nightly'] + os: [ubuntu-20.04] + distro: [ubuntu20.04] + arch: [aarch64] + + steps: + - uses: actions/checkout@v2 + + - name: Download Julia Binary + run: > + julia -e ' + using Pkg; Pkg.add("JSON"); using JSON; + if "${{ matrix.julia-version }}" == "nightly"; + url = "https://julialangnightlies-s3.julialang.org/bin/linux/${{ matrix.arch }}/julia-latest-linux${{ matrix.arch }}.tar.gz"; + else; + path = download("https://julialang-s3.julialang.org/bin/versions.json"); + json = JSON.parsefile(path); + rm(path); + a(f) = f["arch"] == "${{ matrix.arch }}" && f["os"] == "linux" && !occursin("musl", f["triplet"]); + m = filter(json) do v; VersionNumber(v[1]) in Pkg.Types.VersionSpec("${{ matrix.julia-version }}") && any(a, v[2]["files"]); end; + v = sort(VersionNumber.(keys(m)))[end]; + url = filter(a, json[string(v)]["files"])[1]["url"]; + end; + download(url, "/tmp/julia-aarch64.tar.gz");' + + - name: Extract Julia Files + run: | + cd /tmp + tar -xzf julia-aarch64.tar.gz -C /home/runner/work/ + mv /home/runner/work/julia-*/ /home/runner/work/julia/ + rm julia-aarch64.tar.gz + + - uses: uraimo/run-on-arch-action@v2.0.7 + name: Unit Test + with: + arch: ${{ matrix.arch }} + distro: ${{ matrix.distro }} + dockerRunArgs: | + -v "/home/runner/work/julia:/home/runner/work/julia" + -v "/home/runner/.julia/registries/General:/root/.julia/registries/General" + --net=host + install: | + ln -s /home/runner/work/julia/bin/julia /usr/local/bin/julia + echo /home/runner/work/julia/lib > /etc/ld.so.conf.d/julia.conf + mkdir -p /root/.julia/registries/General + run: | + julia -e 'using InteractiveUtils; versioninfo();' + julia --project=. --check-bounds=yes --color=yes -e 'using Pkg; Pkg.build(); Pkg.test(coverage=true)' + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v1 + with: + file: lcov.info diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index aff8557d..00000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: julia -os: - - linux -julia: - - 1.0 - - 1 - - nightly -arch: - - arm64 -cache: - directories: - - $HOME/.julia/artifacts -notifications: - email: false -codecov: true diff --git a/test/runtests.jl b/test/runtests.jl index 810e13d3..860bf618 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,8 +2,10 @@ using FixedPointNumbers, Test @test isempty(detect_ambiguities(FixedPointNumbers, Base, Core)) -using Documenter -doctest(FixedPointNumbers, manual = false) +if Sys.ARCH === :x86_64 || Sys.ARCH === :i686 + using Documenter + doctest(FixedPointNumbers, manual = false) +end @testset "normed" begin include("normed.jl") From 8bdec42f16328565817639f208b03348aecff4e0 Mon Sep 17 00:00:00 2001 From: kimikage Date: Sat, 19 Dec 2020 13:53:48 +0900 Subject: [PATCH 20/40] Improve generation of typealiases (#233) --- src/FixedPointNumbers.jl | 27 +++++++++++++++++---------- src/deprecations.jl | 8 ++++++++ src/fixed.jl | 8 ++++---- src/normed.jl | 8 ++++---- test/fixed.jl | 2 ++ test/normed.jl | 2 ++ 6 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 9fb56056..15feccd3 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -290,17 +290,24 @@ end hasalias(::Type) = false hasalias(::Type{X}) where {T<:NotBiggerThanInt64, f, X<:FixedPoint{T,f}} = f isa Int -# 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} +# `alias_symbol` is used to define type aliases, so we need this before we +# include "src/fixed.jl" / "src/normed.jl". +function alias_symbol(@nospecialize(X)) + Symbol(type_prefix(X), nbitsint(X), 'f', nbitsfrac(X)) +end + +function _alias_symbol(::Type{X}) where {X <: FixedPoint} + if @generated + sym = string(alias_symbol(X)) + return :(Symbol($sym)) + else + return alias_symbol(X) + end +end + +@inline function showtype(io::IO, ::Type{X}) where {X <: FixedPoint} if hasalias(X) - f = nbitsfrac(X) - m = nbitsint(X) - write(io, typechar(X)) - m > 9 && write(io, Char(m ÷ 10 + 0x30)) - write(io, Char(m % 10 + 0x30), 'f') - f > 9 && write(io, Char(f ÷ 10 + 0x30)) - write(io, Char(f % 10 + 0x30)) + write(io, _alias_symbol(X)) else print(io, X) end diff --git a/src/deprecations.jl b/src/deprecations.jl index 30d586a8..2441efd3 100644 --- a/src/deprecations.jl +++ b/src/deprecations.jl @@ -7,3 +7,11 @@ function floattype(::Type{T}) where {T <: Real} """, :floattype) return T end + +function typechar(::Type{X}) where {X} + Base.depwarn(""" + `typechar` was deprecated since the prefix may not be a single character in the future. + We recommend not using private functions, but if you need to, use `type_prefix` instead. + """, :typechar) + Char(string(type_prefix(X))[1]) +end diff --git a/src/fixed.jl b/src/fixed.jl index 16343e18..af7fc1ce 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -27,14 +27,14 @@ end # TODO: remove this hasalias(::Type{F}) where {F <: Union{Fixed{Int8,8},Fixed{Int16,16},Fixed{Int32,32},Fixed{Int64,64}}} = false -typechar(::Type{X}) where {X <: Fixed} = 'Q' +type_prefix(::Type{F}) where {F <: Fixed{<:Signed}} = :Q for T in (Int8, Int16, Int32, Int64) - io = IOBuffer() for f in 0:bitwidth(T)-1 - sym = Symbol(String(take!(showtype(io, Fixed{T,f})))) + F = Fixed{T,f} + sym = alias_symbol(F) @eval begin - const $sym = Fixed{$T,$f} + const $sym = $F export $sym end end diff --git a/src/normed.jl b/src/normed.jl index 57e6c3cc..759f369f 100644 --- a/src/normed.jl +++ b/src/normed.jl @@ -19,14 +19,14 @@ struct Normed{T <: Unsigned, f} <: FixedPoint{T, f} end end -typechar(::Type{X}) where {X <: Normed} = 'N' +type_prefix(::Type{N}) where {N <: Normed{<:Unsigned}} = :N for T in (UInt8, UInt16, UInt32, UInt64) - io = IOBuffer() for f in 1:bitwidth(T) - sym = Symbol(String(take!(showtype(io, Normed{T,f})))) + N = Normed{T,f} + sym = alias_symbol(N) @eval begin - const $sym = Normed{$T,$f} + const $sym = $N export $sym end end diff --git a/test/fixed.jl b/test/fixed.jl index db0aab1d..fb16b9af 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -600,6 +600,8 @@ end end @testset "show" begin + @test (@test_deprecated FixedPointNumbers.typechar(Q0f7)) === 'Q' + iob = IOBuffer() q0f7 = reinterpret(Q0f7, signed(0xaa)) show(iob, q0f7) diff --git a/test/normed.jl b/test/normed.jl index 22ebbfde..1a611d0f 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -597,6 +597,8 @@ end end @testset "show" begin + @test (@test_deprecated FixedPointNumbers.typechar(N0f8)) === 'N' + iob = IOBuffer() n0f8 = reinterpret(N0f8, 0xaa) show(iob, n0f8) From 55a7232d5ad65b9aa411b9aaa52d1706e41f5c3f Mon Sep 17 00:00:00 2001 From: kimikage Date: Wed, 1 May 2024 09:47:53 +0900 Subject: [PATCH 21/40] Backport partially #243 --- src/FixedPointNumbers.jl | 6 +++--- test/fixed.jl | 4 ++++ test/normed.jl | 4 ++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 15feccd3..ffccee54 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -298,8 +298,7 @@ end function _alias_symbol(::Type{X}) where {X <: FixedPoint} if @generated - sym = string(alias_symbol(X)) - return :(Symbol($sym)) + return QuoteNode(alias_symbol(X)) else return alias_symbol(X) end @@ -311,7 +310,7 @@ end else print(io, X) end - io + return nothing end function show(io::IO, x::FixedPoint{T,f}) where {T,f} @@ -326,6 +325,7 @@ function show(io::IO, x::FixedPoint{T,f}) where {T,f} else print(io, typeof(x), '(', val, ')') end + return nothing end function Base.showarg(io::IO, a::Array{T}, toplevel) where {T<:FixedPoint} diff --git a/test/fixed.jl b/test/fixed.jl index fb16b9af..73bb57a5 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -609,6 +609,10 @@ end @test str == "-0.672Q0f7" @test eval(Meta.parse(str)) === q0f7 + print(iob, q0f7) + str = String(take!(iob)) + @test str == "-0.672Q0f7" # w/ type suffix (cf. PR #243) + q15f16 = reinterpret(Q15f16, signed(0xaaaaaaaa)) show(iob, q15f16) str = String(take!(iob)) diff --git a/test/normed.jl b/test/normed.jl index 1a611d0f..a3356c3b 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -606,6 +606,10 @@ end @test str == "0.667N0f8" @test eval(Meta.parse(str)) === n0f8 + print(iob, n0f8) + str = String(take!(iob)) + @test str == "0.667N0f8" # w/ type suffix (cf. PR #243) + n16f16 = reinterpret(N16f16, 0xaaaaaaaa) show(iob, n16f16) str = String(take!(iob)) From 26e9a3499d125c4987db7779a67235ef6fea3227 Mon Sep 17 00:00:00 2001 From: kimikage Date: Tue, 16 Mar 2021 21:47:34 +0900 Subject: [PATCH 22/40] Avoid selecting pre-release in ARM CI (#244) --- .github/workflows/UnitTestArm.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/UnitTestArm.yml b/.github/workflows/UnitTestArm.yml index f31e3bc2..d474e725 100644 --- a/.github/workflows/UnitTestArm.yml +++ b/.github/workflows/UnitTestArm.yml @@ -29,9 +29,10 @@ jobs: else; path = download("https://julialang-s3.julialang.org/bin/versions.json"); json = JSON.parsefile(path); - rm(path); + try rm(path) catch end; + vspec = Pkg.Types.VersionSpec("${{ matrix.julia-version }}"); a(f) = f["arch"] == "${{ matrix.arch }}" && f["os"] == "linux" && !occursin("musl", f["triplet"]); - m = filter(json) do v; VersionNumber(v[1]) in Pkg.Types.VersionSpec("${{ matrix.julia-version }}") && any(a, v[2]["files"]); end; + m = filter(json) do v; vn = VersionNumber(v[1]); vn in vspec && isempty(vn.prerelease) && any(a, v[2]["files"]); end; v = sort(VersionNumber.(keys(m)))[end]; url = filter(a, json[string(v)]["files"])[1]["url"]; end; @@ -44,7 +45,7 @@ jobs: mv /home/runner/work/julia-*/ /home/runner/work/julia/ rm julia-aarch64.tar.gz - - uses: uraimo/run-on-arch-action@v2.0.7 + - uses: uraimo/run-on-arch-action@v2.0.9 name: Unit Test with: arch: ${{ matrix.arch }} From 115b27413090684e0393b29cd65df9d8b36e7014 Mon Sep 17 00:00:00 2001 From: kimikage Date: Mon, 19 Apr 2021 20:34:40 +0900 Subject: [PATCH 23/40] Work around rounding errors in nightly test (#248) This forces the `BigFloat`-->`Float16` conversion to go through `Float32` instead of `Float64`. --- test/normed.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/normed.jl b/test/normed.jl index a3356c3b..07651ae8 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -216,7 +216,8 @@ end T = rawtype(N) float_err = zero(Tf) for i = typemin(T):typemax(T) - f_expected = Tf(i / BigFloat(FixedPointNumbers.rawone(N))) + b_expected = i / BigFloat(FixedPointNumbers.rawone(N)) + f_expected = Tf(promote_type(Tf, Float32)(b_expected)) # workaround for issue #246 isinf(f_expected) && break # for Float16(::Normed{UInt16,1}) f_actual = Tf(reinterpret(N, i)) float_err += abs(f_actual - f_expected) @@ -227,7 +228,8 @@ end T = rawtype(N) error_count = 0 for i in vcat(T(0x00):T(0xFF), (typemax(T)-0xFF):typemax(T)) - f_expected = Tf(i / BigFloat(FixedPointNumbers.rawone(N))) + b_expected = i / BigFloat(FixedPointNumbers.rawone(N)) + f_expected = Tf(promote_type(Tf, Float32)(b_expected)) # workaround for issue #246 isinf(f_expected) && break # for Float16() and Float32() f_actual = Tf(reinterpret(N, i)) f_actual == f_expected && continue From 3149fb602e5edad26e1574b5999ee2731d108202 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Mon, 2 Aug 2021 20:34:02 +0200 Subject: [PATCH 24/40] fix tests on 1.7 (#253) Co-authored-by: Johnny Chen Co-authored-by: kimikage --- test/fixed.jl | 2 +- test/normed.jl | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/test/fixed.jl b/test/fixed.jl index 73bb57a5..a4eac058 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -529,7 +529,7 @@ end @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(typemin(QInt1):eps(QInt1):typemax(QInt1)-eps(QInt1)) @test_throws OverflowError length(QInt1(-1):eps(QInt1):typemax(QInt1)-eps(QInt1)) end diff --git a/test/normed.jl b/test/normed.jl index 07651ae8..8de2ba80 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -514,7 +514,6 @@ end 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 From 8c9770e684d70991e75138bbc4851f1382a8f32f Mon Sep 17 00:00:00 2001 From: kimikage Date: Tue, 3 Aug 2021 12:41:12 +0900 Subject: [PATCH 25/40] Remove assertions in "src/precompile.jl" (#254) --- src/precompile.jl | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/precompile.jl b/src/precompile.jl index 178be83f..c48576aa 100644 --- a/src/precompile.jl +++ b/src/precompile.jl @@ -4,27 +4,27 @@ function _precompile_() realtypes = (Float16, Float32, Float64, Int) # types for mixed Normed/Real operations for T in normedtypes for f in (+, -, abs, eps, rand) # unary operations - @assert precompile(Tuple{typeof(f),T}) + precompile(Tuple{typeof(f),T}) end - @assert precompile(Tuple{typeof(rand),T,Tuple{Int}}) - @assert precompile(Tuple{typeof(rand),T,Tuple{Int,Int}}) + precompile(Tuple{typeof(rand),T,Tuple{Int}}) + precompile(Tuple{typeof(rand),T,Tuple{Int,Int}}) for f in (trunc, floor, ceil, round) # rounding operations - @assert precompile(Tuple{typeof(f),T}) - @assert precompile(Tuple{typeof(f),Type{Int},T}) + precompile(Tuple{typeof(f),T}) + precompile(Tuple{typeof(f),Type{Int},T}) end for f in (+, -, *, /, <, <=, ==) # binary operations - @assert precompile(Tuple{typeof(f),T,T}) + precompile(Tuple{typeof(f),T,T}) for S in realtypes - @assert precompile(Tuple{typeof(f),T,S}) - @assert precompile(Tuple{typeof(f),S,T}) + precompile(Tuple{typeof(f),T,S}) + precompile(Tuple{typeof(f),S,T}) end end # conversions for S in realtypes - @assert precompile(Tuple{Type{T},S}) - @assert precompile(Tuple{Type{S},T}) - @assert precompile(Tuple{typeof(convert),Type{T},S}) - @assert precompile(Tuple{typeof(convert),Type{S},T}) + precompile(Tuple{Type{T},S}) + precompile(Tuple{Type{S},T}) + precompile(Tuple{typeof(convert),Type{T},S}) + precompile(Tuple{typeof(convert),Type{S},T}) end end end From a93650270e6d5ec91485e74adefc32e59539032d Mon Sep 17 00:00:00 2001 From: Johnny Chen Date: Thu, 7 Jul 2022 11:47:08 +0800 Subject: [PATCH 26/40] CI: test Julia 1.6 and update TagBot permission (#257) --- .github/workflows/TagBot.yml | 5 +++++ .github/workflows/UnitTest.yml | 4 +--- .github/workflows/UnitTestArm.yml | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml index 778c06fe..eb6c8610 100644 --- a/.github/workflows/TagBot.yml +++ b/.github/workflows/TagBot.yml @@ -4,6 +4,11 @@ on: types: - created workflow_dispatch: + inputs: + lookback: + default: 3 +permissions: + contents: write jobs: TagBot: if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' diff --git a/.github/workflows/UnitTest.yml b/.github/workflows/UnitTest.yml index 8032ea37..1851b6a0 100644 --- a/.github/workflows/UnitTest.yml +++ b/.github/workflows/UnitTest.yml @@ -7,8 +7,6 @@ on: branches: - master pull_request: - schedule: - - cron: '20 00 1 * *' jobs: test: @@ -16,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - julia-version: ['1.0', '1', 'nightly'] + julia-version: ['1.0', '1', '1.6', 'nightly'] os: [ubuntu-latest, windows-latest, macOS-latest] julia-arch: [x64] # only test one 32-bit job diff --git a/.github/workflows/UnitTestArm.yml b/.github/workflows/UnitTestArm.yml index d474e725..1461793a 100644 --- a/.github/workflows/UnitTestArm.yml +++ b/.github/workflows/UnitTestArm.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - julia-version: ['1.0', '1', 'nightly'] + julia-version: ['1.0', '1', '1.6', 'nightly'] os: [ubuntu-20.04] distro: [ubuntu20.04] arch: [aarch64] From 37d724e5b31fc8e90dbc02b406661de3cae7c0fd Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Thu, 16 Mar 2023 03:33:36 -0500 Subject: [PATCH 27/40] CI: rename julia-runtest branch (#263) --- .github/workflows/UnitTest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/UnitTest.yml b/.github/workflows/UnitTest.yml index 1851b6a0..8aeaafe5 100644 --- a/.github/workflows/UnitTest.yml +++ b/.github/workflows/UnitTest.yml @@ -43,7 +43,7 @@ jobs: ${{ runner.os }}-test- ${{ runner.os }}- - name: "Unit Test" - uses: julia-actions/julia-runtest@master + uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v1 From 53f2a3a01c36478896c8f2578131062edc41ec29 Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Wed, 14 Feb 2024 10:30:12 -0500 Subject: [PATCH 28/40] Merge pull request #270 from JuliaMath/vc/random use Random explicitly --- src/precompile.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/precompile.jl b/src/precompile.jl index c48576aa..d945baf2 100644 --- a/src/precompile.jl +++ b/src/precompile.jl @@ -1,3 +1,5 @@ +using Random + function _precompile_() ccall(:jl_generating_output, Cint, ()) == 1 || return nothing normedtypes = (N0f8, N0f16) # precompiled Normed types From 2c564ef6438dcaf54b5b901f08bdf57cf4d1b27f Mon Sep 17 00:00:00 2001 From: inky Date: Thu, 4 Apr 2024 19:44:24 -0500 Subject: [PATCH 29/40] ci: fix codecov (#276) --- .github/workflows/UnitTest.yml | 2 ++ .github/workflows/UnitTestArm.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/UnitTest.yml b/.github/workflows/UnitTest.yml index 8aeaafe5..56d3390a 100644 --- a/.github/workflows/UnitTest.yml +++ b/.github/workflows/UnitTest.yml @@ -48,4 +48,6 @@ jobs: - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v1 with: + token: ${{ secrets.CODECOV_TOKEN }} # required + fail_ci_if_error: true file: lcov.info diff --git a/.github/workflows/UnitTestArm.yml b/.github/workflows/UnitTestArm.yml index 1461793a..13ff8acf 100644 --- a/.github/workflows/UnitTestArm.yml +++ b/.github/workflows/UnitTestArm.yml @@ -64,4 +64,6 @@ jobs: - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v1 with: + token: ${{ secrets.CODECOV_TOKEN }} # required + fail_ci_if_error: true file: lcov.info From 0af6e82cb3ff8338e1a39673d0a0c4a8db5e43e1 Mon Sep 17 00:00:00 2001 From: kimikage Date: Sat, 6 Apr 2024 02:03:30 +0900 Subject: [PATCH 30/40] Fix Arm CI workflow (#279) * Use ubuntu-latest for Arm CI (host and container) * Fix download URL of nightly build binary * Use julia-actions/setup-julia@v2 for the host and use the same version as the target --- .github/workflows/UnitTestArm.yml | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/.github/workflows/UnitTestArm.yml b/.github/workflows/UnitTestArm.yml index 13ff8acf..0fae9a3c 100644 --- a/.github/workflows/UnitTestArm.yml +++ b/.github/workflows/UnitTestArm.yml @@ -6,6 +6,9 @@ on: - master pull_request: workflow_dispatch: +permissions: + actions: write + contents: read jobs: test: runs-on: ${{ matrix.os }} @@ -13,19 +16,22 @@ jobs: fail-fast: false matrix: julia-version: ['1.0', '1', '1.6', 'nightly'] - os: [ubuntu-20.04] - distro: [ubuntu20.04] + os: [ubuntu-latest] + distro: [ubuntu_latest] arch: [aarch64] steps: - - uses: actions/checkout@v2 - + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 + with: + version: ${{ matrix.julia-version }} + - uses: julia-actions/cache@v1 - name: Download Julia Binary run: > julia -e ' using Pkg; Pkg.add("JSON"); using JSON; if "${{ matrix.julia-version }}" == "nightly"; - url = "https://julialangnightlies-s3.julialang.org/bin/linux/${{ matrix.arch }}/julia-latest-linux${{ matrix.arch }}.tar.gz"; + url = "https://julialangnightlies-s3.julialang.org/bin/linux/${{ matrix.arch }}/julia-latest-linux-${{ matrix.arch }}.tar.gz"; else; path = download("https://julialang-s3.julialang.org/bin/versions.json"); json = JSON.parsefile(path); @@ -40,26 +46,25 @@ jobs: - name: Extract Julia Files run: | - cd /tmp - tar -xzf julia-aarch64.tar.gz -C /home/runner/work/ - mv /home/runner/work/julia-*/ /home/runner/work/julia/ - rm julia-aarch64.tar.gz + mkdir -p /home/runner/work/julia/ + tar -xf /tmp/julia-aarch64.tar.gz --strip-components=1 -C /home/runner/work/julia/ + rm /tmp/julia-aarch64.tar.gz - - uses: uraimo/run-on-arch-action@v2.0.9 + - uses: uraimo/run-on-arch-action@v2.7.1 name: Unit Test with: arch: ${{ matrix.arch }} distro: ${{ matrix.distro }} dockerRunArgs: | -v "/home/runner/work/julia:/home/runner/work/julia" - -v "/home/runner/.julia/registries/General:/root/.julia/registries/General" + -v "/home/runner/.julia/registries:/root/.julia/registries" --net=host install: | ln -s /home/runner/work/julia/bin/julia /usr/local/bin/julia echo /home/runner/work/julia/lib > /etc/ld.so.conf.d/julia.conf mkdir -p /root/.julia/registries/General run: | - julia -e 'using InteractiveUtils; versioninfo();' + julia --compile=min -O0 -e 'using InteractiveUtils; versioninfo();' julia --project=. --check-bounds=yes --color=yes -e 'using Pkg; Pkg.build(); Pkg.test(coverage=true)' - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v1 From 703ace4294d42d0b778a4f9e09d062fe52d79119 Mon Sep 17 00:00:00 2001 From: kimikage Date: Sat, 6 Apr 2024 10:18:31 +0900 Subject: [PATCH 31/40] Fix test for exponentially growing `promote_type` (#280) --- test/fixed.jl | 8 +++++++- test/normed.jl | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/test/fixed.jl b/test/fixed.jl index a4eac058..f113f2e2 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -594,7 +594,13 @@ end @test @inferred(promote_type(Float32, Int, Q0f7)) === Float32 @test @inferred(promote_type(Float32, Q0f7, Int)) === Float32 - @test @inferred(promote_type(Q0f7,Q1f6,Q2f5,Q3f4,Q4f3,Q5f2)) == Fixed{Int128,7} + if promote_type(Int, Float32, Complex{Int}, typeof(pi)) === ComplexF64 + # right-to-left + @test @inferred(promote_type(Q0f7, Q1f6, Q2f5, Q3f4, Q4f3, Q5f2)) == Fixed{Int128,7} + else + # left-to-right + @test @inferred(promote_type(Q5f2, Q4f3, Q3f4, Q2f5, Q1f6, Q0f7)) == Fixed{Int128,7} + end @test @inferred(promote_type(Q0f7, N0f32)) === FixedPoint # Float64 on v0.9 (#207) end diff --git a/test/normed.jl b/test/normed.jl index 8de2ba80..3a8789af 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -592,7 +592,13 @@ end @test @inferred(promote_type(Float32, Int, N0f8)) === Float32 @test @inferred(promote_type(Float32, N0f8, Int)) === Float32 - @test @inferred(promote_type(N0f8,N1f7,N2f6,N3f5,N4f4,N5f3)) === Normed{UInt128,8} + if promote_type(Int, Float32, Complex{Int}, typeof(pi)) === ComplexF64 + # right-to-left + @test @inferred(promote_type(N0f8, N1f7, N2f6, N3f5, N4f4, N5f3)) === Normed{UInt128,8} + else + # left-to-right + @test @inferred(promote_type(N5f3, N4f4, N3f5, N2f6, N1f7, N0f8)) === Normed{UInt128,8} + end @test @inferred(promote_type(N0f8, Q0f31)) === FixedPoint # Float64 on v0.9 (#207) end From 841f8b1abb50b25a91c72df8d0dcc5e74d4a39d1 Mon Sep 17 00:00:00 2001 From: Yuto Horikawa Date: Sat, 13 Apr 2024 20:58:56 +0900 Subject: [PATCH 32/40] Enable package extension for Statistics (#277) --- Project.toml | 12 +++++++++++- ext/FixedPointNumbersStatisticsExt.jl | 13 +++++++++++++ src/FixedPointNumbers.jl | 9 ++++----- 3 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 ext/FixedPointNumbersStatisticsExt.jl diff --git a/Project.toml b/Project.toml index 00cf1046..b0dc990d 100644 --- a/Project.toml +++ b/Project.toml @@ -6,12 +6,22 @@ version = "0.8.4" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +[weakdeps] +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + +[extensions] +FixedPointNumbersStatisticsExt = "Statistics" + [compat] +# Update this version specifier when Statistics.jl v1.11.2 is released. +# https://github.com/JuliaStats/Statistics.jl/issues/165 +Statistics = "< 1.11.2" julia = "1" [extras] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Documenter", "Test"] +test = ["Documenter", "Statistics", "Test"] diff --git a/ext/FixedPointNumbersStatisticsExt.jl b/ext/FixedPointNumbersStatisticsExt.jl new file mode 100644 index 00000000..5cf777fa --- /dev/null +++ b/ext/FixedPointNumbersStatisticsExt.jl @@ -0,0 +1,13 @@ +module FixedPointNumbersStatisticsExt + +using FixedPointNumbers +import FixedPointNumbers: Treduce +using Statistics + +import Statistics +if isdefined(Statistics, :_mean_promote) + # https://github.com/JuliaMath/FixedPointNumbers.jl/pull/183 + Statistics._mean_promote(x::Real, y::FixedPoint) = Treduce(y) +end + +end diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index ffccee54..f1368ae0 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -9,7 +9,6 @@ import Base: ==, <, <=, -, +, *, /, ~, isapprox, signed, unsigned, copysign, flipsign, signbit, length -import Statistics # for _mean_promote import Random: Random, AbstractRNG, SamplerType, rand! import Base.Checked: checked_mul, checked_div @@ -350,10 +349,6 @@ Base.mul_prod(x::FixedPoint, y::FixedPoint) = Treduce(x) * Treduce(y) Base.reduce_empty(::typeof(Base.mul_prod), ::Type{F}) where {F<:FixedPoint} = one(Treduce) Base.reduce_first(::typeof(Base.mul_prod), x::FixedPoint) = Treduce(x) -if isdefined(Statistics, :_mean_promote) - Statistics._mean_promote(x::Real, y::FixedPoint) = Treduce(y) -end - """ sd, ad = scaledual(s::Number, a) @@ -406,6 +401,10 @@ function rand!(r::AbstractRNG, A::Array{X}, ::SamplerType{X}) where {T, X <: Fix A end +if !isdefined(Base, :get_extension) + include("../ext/FixedPointNumbersStatisticsExt.jl") +end + if VERSION >= v"1.1" # work around https://github.com/JuliaLang/julia/issues/34121 include("precompile.jl") _precompile_() From f1621b82c135840bd95b0f7a4d962cdfe642411d Mon Sep 17 00:00:00 2001 From: kimikage Date: Sat, 13 Apr 2024 21:38:09 +0900 Subject: [PATCH 33/40] Use `StableRNG` in tests (#278) --- Project.toml | 4 +++- test/common.jl | 2 +- test/fixed.jl | 3 ++- test/normed.jl | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Project.toml b/Project.toml index b0dc990d..944b8245 100644 --- a/Project.toml +++ b/Project.toml @@ -13,6 +13,7 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" FixedPointNumbersStatisticsExt = "Statistics" [compat] +StableRNGs = "1" # Update this version specifier when Statistics.jl v1.11.2 is released. # https://github.com/JuliaStats/Statistics.jl/issues/165 Statistics = "< 1.11.2" @@ -20,8 +21,9 @@ julia = "1" [extras] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Documenter", "Statistics", "Test"] +test = ["Documenter", "StableRNGs", "Statistics", "Test"] diff --git a/test/common.jl b/test/common.jl index 8826fce7..f8b1295b 100644 --- a/test/common.jl +++ b/test/common.jl @@ -1,4 +1,4 @@ -using FixedPointNumbers, Statistics, Random, Test +using FixedPointNumbers, Statistics, Random, StableRNGs, Test using FixedPointNumbers: bitwidth, rawtype, nbitsfrac SP = VERSION >= v"1.6.0-DEV.771" ? " " : "" # JuliaLang/julia #37085 diff --git a/test/fixed.jl b/test/fixed.jl index f113f2e2..54e5128a 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -559,7 +559,8 @@ end @testset "rand" begin test_rand(Fixed) - @test rand(MersenneTwister(1234), Q0f7) === -0.156Q0f7 + @test !(rand(Q0f15) == rand(Q0f15) == rand(Q0f15)) # If this fails, we should suspect a bug. + @test rand(StableRNG(1234), Q0f7) === 0.531Q0f7 end @testset "Promotion within Fixed" begin diff --git a/test/normed.jl b/test/normed.jl index 3a8789af..6ec141f0 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -557,7 +557,8 @@ end @testset "rand" begin test_rand(Normed) - @test rand(MersenneTwister(1234), N0f8) === 0.925N0f8 + @test !(rand(N0f16) == rand(N0f16) == rand(N0f16)) # If this fails, we should suspect a bug. + @test rand(StableRNG(1234), N0f8) === 0.267N0f8 end @testset "Promotion within Normed" begin From e7a6e1e1c2d38b7285787cbf4b34001d310145ef Mon Sep 17 00:00:00 2001 From: Yuto Horikawa Date: Sun, 14 Apr 2024 01:19:48 +0900 Subject: [PATCH 34/40] Add tests with Aqua.jl (#283) --- Project.toml | 7 ++++++- README.md | 1 + test/runtests.jl | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index 944b8245..d2c137aa 100644 --- a/Project.toml +++ b/Project.toml @@ -13,17 +13,22 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" FixedPointNumbersStatisticsExt = "Statistics" [compat] +Aqua = "0.8" +Documenter = "0.27, 1" +Random = "<0.0.1, 1" StableRNGs = "1" # Update this version specifier when Statistics.jl v1.11.2 is released. # https://github.com/JuliaStats/Statistics.jl/issues/165 Statistics = "< 1.11.2" +Test = "1" julia = "1" [extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Documenter", "StableRNGs", "Statistics", "Test"] +test = ["Aqua", "Documenter", "StableRNGs", "Statistics", "Test"] diff --git a/README.md b/README.md index 0366ace5..35f9e202 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Build Status][action-img]][action-url] [![Build Status][pkgeval-img]][pkgeval-url] [![coverage][codecov-img]][codecov-url] +[![Aqua QA](https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg)](https://github.com/JuliaTesting/Aqua.jl) This library implements fixed-point number types. A [fixed-point number] represents a fractional, or diff --git a/test/runtests.jl b/test/runtests.jl index 860bf618..d037d440 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,6 @@ -using FixedPointNumbers, Test +using FixedPointNumbers, Test, Aqua -@test isempty(detect_ambiguities(FixedPointNumbers, Base, Core)) +Aqua.test_all(FixedPointNumbers) if Sys.ARCH === :x86_64 || Sys.ARCH === :i686 using Documenter From df9dd8a34047318f5a6c8d718184d59fbe626c14 Mon Sep 17 00:00:00 2001 From: kimikage Date: Sun, 14 Apr 2024 05:56:22 +0900 Subject: [PATCH 35/40] Avoid multiple `include`s of "test/common.jl." (#286) --- test/fixed.jl | 4 +++- test/normed.jl | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/fixed.jl b/test/fixed.jl index 54e5128a..da79a5fb 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -1,4 +1,6 @@ -include("common.jl") +if !isdefined(Main, :target) + include("common.jl") +end function symbol_to_inttype(::Type{Fixed}, s::Symbol) d = Dict(:i8 => Int8, :i16 => Int16, :i32 => Int32, :i64 => Int64, :i128 => Int128) diff --git a/test/normed.jl b/test/normed.jl index 6ec141f0..b223a7b0 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -1,4 +1,6 @@ -include("common.jl") +if !isdefined(Main, :target) + include("common.jl") +end function symbol_to_inttype(::Type{Normed}, s::Symbol) d = Dict(:i8 => UInt8, :i16 => UInt16, :i32 => UInt32, :i64 => UInt64, :i128 => UInt128) From e9a7a438dfb641c78fa3ee04ac7f01259ac2a4c7 Mon Sep 17 00:00:00 2001 From: kimikage Date: Sun, 28 Apr 2024 18:51:03 +0900 Subject: [PATCH 36/40] Update macOS test targets (#290) --- .github/workflows/UnitTest.yml | 17 +++++++++++------ .github/workflows/UnitTestArm.yml | 4 ++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/UnitTest.yml b/.github/workflows/UnitTest.yml index 56d3390a..4c438626 100644 --- a/.github/workflows/UnitTest.yml +++ b/.github/workflows/UnitTest.yml @@ -1,11 +1,11 @@ name: Unit test on: - create: - tags: push: branches: - master + - release-* + tags: ['*'] pull_request: jobs: @@ -14,14 +14,19 @@ jobs: strategy: fail-fast: false matrix: - julia-version: ['1.0', '1', '1.6', 'nightly'] - os: [ubuntu-latest, windows-latest, macOS-latest] + julia-version: ['1.0', '1.6', '1', 'nightly'] + os: [ubuntu-latest, windows-latest, macos-13] julia-arch: [x64] - # only test one 32-bit job include: - - os: ubuntu-latest + - os: ubuntu-latest # only test one 32-bit job julia-version: '1' julia-arch: x86 + - os: macos-latest + julia-version: '1' + julia-arch: aarch64 + - os: macos-latest + julia-version: 'nightly' + julia-arch: aarch64 steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/UnitTestArm.yml b/.github/workflows/UnitTestArm.yml index 0fae9a3c..6acd400e 100644 --- a/.github/workflows/UnitTestArm.yml +++ b/.github/workflows/UnitTestArm.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - julia-version: ['1.0', '1', '1.6', 'nightly'] + julia-version: ['1.0', '1.6', '1', 'nightly'] os: [ubuntu-latest] distro: [ubuntu_latest] arch: [aarch64] @@ -50,7 +50,7 @@ jobs: tar -xf /tmp/julia-aarch64.tar.gz --strip-components=1 -C /home/runner/work/julia/ rm /tmp/julia-aarch64.tar.gz - - uses: uraimo/run-on-arch-action@v2.7.1 + - uses: uraimo/run-on-arch-action@v2.7.2 name: Unit Test with: arch: ${{ matrix.arch }} From 14f8f1443ec36273d11ebf224c2027c34295c07c Mon Sep 17 00:00:00 2001 From: kimikage Date: Mon, 29 Apr 2024 18:22:35 +0900 Subject: [PATCH 37/40] Add test for `unsafe_trunc` returning arbitrary values (#289) --- test/fixed.jl | 40 ++++++++++++++++++++++++++++++++++++++++ test/normed.jl | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/test/fixed.jl b/test/fixed.jl index da79a5fb..ff3fd79a 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -7,6 +7,46 @@ function symbol_to_inttype(::Type{Fixed}, s::Symbol) d[s] end +# issue #288 +# The following needs to be outside of `@testset` to reproduce the issue. +_to_fixed(::Val, x) = x % Q0f7 +_to_fixed(::Val{:Q0f7}, x) = x % Q0f7 +_to_fixed(::Val{:Q0f15}, x) = x % Q0f15 +buf = IOBuffer() +# in range +for vs in ((:Q0f7, :Q0f15), (:Q0f15, :Q0f7)) + for v in vs + show(buf, _to_fixed(Val(v), -1.0)) + print(buf, " ") + end +end +issue288_in = String(take!(buf)) +# out of range +for vs in ((:Q0f7, :Q0f15), (:Q0f15, :Q0f7)) + for v in vs + show(buf, _to_fixed(Val(v), 1.0)) + print(buf, " ") + end +end +issue288_out = String(take!(buf)) + +@testset "issue288" begin + expected_issue288 = "-1.0Q0f7 -1.0Q0f15 -1.0Q0f15 -1.0Q0f7 " + if issue288_in == expected_issue288 # just leave it in the report + @test issue288_in == expected_issue288 + else + @test_broken issue288_in == expected_issue288 + @warn """broken: "$issue288_in"\nexpected: "$expected_issue288" """ + end + expected_issue288 = "-1.0Q0f7 -1.0Q0f15 -1.0Q0f15 -1.0Q0f7 " + if issue288_out == expected_issue288 # just leave it in the report + @test issue288_out == expected_issue288 + else + @test_broken issue288_out == expected_issue288 + @warn """broken: "$issue288_out"\nexpected: "$expected_issue288" """ + end +end + function test_op(fun::Fun, fx::F, fy::F, fxf, fyf, tol) where {Fun, F} # Make sure that the result is representable zf = fun(fxf, fyf) diff --git a/test/normed.jl b/test/normed.jl index b223a7b0..b8cec4a8 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -7,6 +7,46 @@ function symbol_to_inttype(::Type{Normed}, s::Symbol) d[s] end +# issue #288 +# The following needs to be outside of `@testset` to reproduce the issue. +_to_normed(::Val, x) = x % N0f8 +_to_normed(::Val{:N0f8}, x) = x % N0f8 +_to_normed(::Val{:N0f16}, x) = x % N0f16 +buf = IOBuffer() +# in range +for vs in ((:N0f8, :N0f16), (:N0f16, :N0f8)) + for v in vs + show(buf, _to_normed(Val(v), 1.0)) + print(buf, " ") + end +end +issue288_in = String(take!(buf)) +# out of range +for vs in ((:N0f8, :N0f16), (:N0f16, :N0f8)) + for v in vs + show(buf, _to_normed(Val(v), -1.0)) + print(buf, " ") + end +end +issue288_out = String(take!(buf)) + +@testset "issue288" begin + expected_issue288 = "1.0N0f8 1.0N0f16 1.0N0f16 1.0N0f8 " + if issue288_in == expected_issue288 # just leave it in the report + @test issue288_in == expected_issue288 + else + @test_broken issue288_in == expected_issue288 + @warn """broken: "$issue288_in"\nexpected: "$expected_issue288" """ + end + expected_issue288 = "0.004N0f8 2.0e-5N0f16 2.0e-5N0f16 0.004N0f8 " + if issue288_out == expected_issue288 # just leave it in the report + @test issue288_out == expected_issue288 + else + @test_broken issue288_out == expected_issue288 + @warn """broken: "$issue288_out"\nexpected: "$expected_issue288" """ + end +end + @testset "domain of f" begin @test_throws DomainError zero(Normed{UInt8,-1}) @test_throws DomainError zero(Normed{UInt8,0}) From 6e5484dfa4998b41a6df5136400aa2ab541e93ef Mon Sep 17 00:00:00 2001 From: kimikage Date: Tue, 30 Apr 2024 10:23:55 +0900 Subject: [PATCH 38/40] Fix `_unsafe_trunc` to reduce the likelihood of arbitrary values (#291) --- src/utilities.jl | 16 ++++++++-------- test/fixed.jl | 16 ++-------------- test/normed.jl | 16 ++-------------- 3 files changed, 12 insertions(+), 36 deletions(-) diff --git a/src/utilities.jl b/src/utilities.jl index 49abf2da..60352e93 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -42,13 +42,13 @@ _unsafe_trunc(::Type{T}, x) where {T} = unsafe_trunc(T, x) # issue #202, #211 _unsafe_trunc(::Type{T}, x::BigFloat) where {T <: Integer} = trunc(BigInt, x) % T -if !signbit(signed(unsafe_trunc(UInt, -12.345))) - # a workaround for ARM (issue #134) - function _unsafe_trunc(::Type{T}, x::AbstractFloat) where {T <: Integer} - if T === UInt32 - copysign(unsafe_trunc(T, abs(x)), x) - else - unsafe_trunc(T, unsafe_trunc(signedtype(T), x)) - end +# issue #288 +function _unsafe_trunc(::Type{T}, x::AbstractFloat) where {T <: Integer} + if T <: ShortInts + return unsafe_trunc(Int32, x) % T + elseif T <: Unsigned + return copysign(unsafe_trunc(T, abs(x)), x) + else + return unsafe_trunc(T, x) end end diff --git a/test/fixed.jl b/test/fixed.jl index ff3fd79a..255f56d0 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -31,20 +31,8 @@ end issue288_out = String(take!(buf)) @testset "issue288" begin - expected_issue288 = "-1.0Q0f7 -1.0Q0f15 -1.0Q0f15 -1.0Q0f7 " - if issue288_in == expected_issue288 # just leave it in the report - @test issue288_in == expected_issue288 - else - @test_broken issue288_in == expected_issue288 - @warn """broken: "$issue288_in"\nexpected: "$expected_issue288" """ - end - expected_issue288 = "-1.0Q0f7 -1.0Q0f15 -1.0Q0f15 -1.0Q0f7 " - if issue288_out == expected_issue288 # just leave it in the report - @test issue288_out == expected_issue288 - else - @test_broken issue288_out == expected_issue288 - @warn """broken: "$issue288_out"\nexpected: "$expected_issue288" """ - end + @test issue288_in == "-1.0Q0f7 -1.0Q0f15 -1.0Q0f15 -1.0Q0f7 " + @test issue288_out == "-1.0Q0f7 -1.0Q0f15 -1.0Q0f15 -1.0Q0f7 " end function test_op(fun::Fun, fx::F, fy::F, fxf, fyf, tol) where {Fun, F} diff --git a/test/normed.jl b/test/normed.jl index b8cec4a8..975d34d9 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -31,20 +31,8 @@ end issue288_out = String(take!(buf)) @testset "issue288" begin - expected_issue288 = "1.0N0f8 1.0N0f16 1.0N0f16 1.0N0f8 " - if issue288_in == expected_issue288 # just leave it in the report - @test issue288_in == expected_issue288 - else - @test_broken issue288_in == expected_issue288 - @warn """broken: "$issue288_in"\nexpected: "$expected_issue288" """ - end - expected_issue288 = "0.004N0f8 2.0e-5N0f16 2.0e-5N0f16 0.004N0f8 " - if issue288_out == expected_issue288 # just leave it in the report - @test issue288_out == expected_issue288 - else - @test_broken issue288_out == expected_issue288 - @warn """broken: "$issue288_out"\nexpected: "$expected_issue288" """ - end + @test issue288_in == "1.0N0f8 1.0N0f16 1.0N0f16 1.0N0f8 " + @test issue288_out == "0.004N0f8 2.0e-5N0f16 2.0e-5N0f16 0.004N0f8 " end @testset "domain of f" begin From cb271e5c011b754eaaede769c34e192da5a0f080 Mon Sep 17 00:00:00 2001 From: kimikage Date: Tue, 30 Apr 2024 18:24:53 +0900 Subject: [PATCH 39/40] Add `workflow-dispatch` trigger to UnitTest.yml (#295) --- .github/workflows/UnitTest.yml | 1 + .github/workflows/UnitTestArm.yml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/UnitTest.yml b/.github/workflows/UnitTest.yml index 4c438626..47966aff 100644 --- a/.github/workflows/UnitTest.yml +++ b/.github/workflows/UnitTest.yml @@ -7,6 +7,7 @@ on: - release-* tags: ['*'] pull_request: + workflow_dispatch: jobs: test: diff --git a/.github/workflows/UnitTestArm.yml b/.github/workflows/UnitTestArm.yml index 6acd400e..bdb4793c 100644 --- a/.github/workflows/UnitTestArm.yml +++ b/.github/workflows/UnitTestArm.yml @@ -4,6 +4,8 @@ on: push: branches: - master + - release-* + tags: ['*'] pull_request: workflow_dispatch: permissions: From 295b8d15f7f2a9a99c87ca79171b5881722cb34d Mon Sep 17 00:00:00 2001 From: kimikage Date: Wed, 1 May 2024 18:32:04 +0900 Subject: [PATCH 40/40] Update workflows --- .github/workflows/UnitTest.yml | 8 ++++---- .github/workflows/UnitTestArm.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/UnitTest.yml b/.github/workflows/UnitTest.yml index 47966aff..0bcc5eb4 100644 --- a/.github/workflows/UnitTest.yml +++ b/.github/workflows/UnitTest.yml @@ -30,15 +30,15 @@ jobs: julia-arch: aarch64 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: "Set up Julia" - uses: julia-actions/setup-julia@v1 + uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.julia-version }} arch: ${{ matrix.julia-arch }} - name: Cache artifacts - uses: actions/cache@v1 + uses: actions/cache@v4 env: cache-name: cache-artifacts with: @@ -52,7 +52,7 @@ jobs: uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} # required fail_ci_if_error: true diff --git a/.github/workflows/UnitTestArm.yml b/.github/workflows/UnitTestArm.yml index bdb4793c..3af6d0a5 100644 --- a/.github/workflows/UnitTestArm.yml +++ b/.github/workflows/UnitTestArm.yml @@ -69,7 +69,7 @@ jobs: julia --compile=min -O0 -e 'using InteractiveUtils; versioninfo();' julia --project=. --check-bounds=yes --color=yes -e 'using Pkg; Pkg.build(); Pkg.test(coverage=true)' - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} # required fail_ci_if_error: true