diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 3c996375..78d5f268 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -38,7 +38,10 @@ export # Functions scaledual, wrapping_neg, wrapping_abs, wrapping_add, wrapping_sub, wrapping_mul, - saturating_neg, saturating_abs, saturating_add, saturating_sub, saturating_mul + wrapping_fdiv, + saturating_neg, saturating_abs, saturating_add, saturating_sub, saturating_mul, + saturating_fdiv, + checked_fdiv include("utilities.jl") @@ -207,6 +210,10 @@ wrapping_abs(x::X) where {X <: FixedPoint} = X(abs(x.i), 0) wrapping_add(x::X, y::X) where {X <: FixedPoint} = X(x.i + y.i, 0) wrapping_sub(x::X, y::X) where {X <: FixedPoint} = X(x.i - y.i, 0) wrapping_mul(x::X, y::X) where {X <: FixedPoint} = (float(x) * float(y)) % X +function wrapping_fdiv(x::X, y::X) where {X <: FixedPoint} + z = floattype(X)(x.i) / floattype(X)(y.i) + isfinite(z) ? z % X : zero(X) +end # saturating arithmetic saturating_neg(x::X) where {X <: FixedPoint} = X(~min(x.i - true, x.i), 0) @@ -225,6 +232,9 @@ saturating_sub(x::X, y::X) where {X <: FixedPoint{<:Unsigned}} = X(x.i - min(x.i saturating_mul(x::X, y::X) where {X <: FixedPoint} = clamp(float(x) * float(y), X) +saturating_fdiv(x::X, y::X) where {X <: FixedPoint} = + clamp(floattype(X)(x.i) / floattype(X)(y.i), X) + # checked arithmetic checked_neg(x::X) where {X <: FixedPoint} = checked_sub(zero(X), x) function checked_abs(x::X) where {X <: FixedPoint} @@ -248,6 +258,16 @@ function checked_mul(x::X, y::X) where {X <: FixedPoint} typemin(X) - eps(X)/2 <= z < typemax(X) + eps(X)/2 || throw_overflowerror(:*, x, y) z % X end +function checked_fdiv(x::X, y::X) where {T, X <: FixedPoint{T}} + y === zero(X) && throw(DivideError()) + z = floattype(X)(x.i) / floattype(X)(y.i) + if T <: Unsigned + z < typemax(X) + eps(X)/2 || throw_overflowerror(:/, x, y) + else + typemin(X) - eps(X)/2 <= z < typemax(X) + eps(X)/2 || throw_overflowerror(:/, x, y) + end + z % X +end # default arithmetic const DEFAULT_ARITHMETIC = :wrapping @@ -264,6 +284,7 @@ for (op, name) in ((:+, :add), (:-, :sub), (:*, :mul)) $op(x::X, y::X) where {X <: FixedPoint} = $f(x, y) end end +/(x::X, y::X) where {X <: FixedPoint} = checked_fdiv(x, y) # force checked arithmetic function minmax(x::X, y::X) where {X <: FixedPoint} diff --git a/src/fixed.jl b/src/fixed.jl index 7d321579..33000781 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -179,7 +179,6 @@ function mul_with_rounding(x::F, y::F, ::RoundingMode{:Down}) where 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 diff --git a/src/normed.jl b/src/normed.jl index 07a272dc..f3a905f7 100644 --- a/src/normed.jl +++ b/src/normed.jl @@ -291,7 +291,6 @@ 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 trunc(x::N) where {N <: Normed} = floor(x) diff --git a/test/common.jl b/test/common.jl index 6660010d..a03ce150 100644 --- a/test/common.jl +++ b/test/common.jl @@ -222,6 +222,18 @@ 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 all(((x, y),) -> wrapping_fdiv(x, y) === fdivz(x, y) % X, xys) + @test all(((x, y),) -> saturating_fdiv(x, y) === clamp(fdiv(x, y), X), xys) + @test all(((x, y),) -> !(typemin(X) <= fdiv(x, y) <= typemax(X)) || + wrapping_fdiv(x, y) === checked_fdiv(x, y), 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 2f57b9b4..a1cb3801 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -377,6 +377,31 @@ end FixedPointNumbers.mul_with_rounding(1.5Q6f1, -0.5Q6f1, RoundDown) === -1.0Q6f1 end +@testset "fdiv" begin + for F in target(Fixed; ex = :thin) + @test wrapping_fdiv(typemax(F), -typemax(F)) === F(-1) + @test saturating_fdiv(typemax(F), -typemax(F)) === F(-1) + @test checked_fdiv(typemax(F), -typemax(F)) === F(-1) + + @test wrapping_fdiv(zero(F), typemin(F)) === zero(F) + @test saturating_fdiv(zero(F), typemin(F)) === zero(F) + @test checked_fdiv(zero(F), typemin(F)) === zero(F) + + @test wrapping_fdiv(typemin(F), F(-1)) === wrapping_neg(typemin(F)) + @test saturating_fdiv(typemin(F), F(-1)) === typemax(F) + @test_throws OverflowError checked_fdiv(typemin(F), F(-1)) + + @test wrapping_fdiv(zero(F), zero(F)) === zero(F) + @test saturating_fdiv(zero(F), zero(F)) === zero(F) + @test_throws DivideError checked_fdiv(zero(F), zero(F)) + + @test wrapping_fdiv(-eps(F), zero(F)) === zero(F) + @test saturating_fdiv(-eps(F), zero(F)) === typemin(F) + @test_throws DivideError checked_fdiv(-eps(F), zero(F)) + 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 2f8c4a63..feb3411d 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -380,6 +380,31 @@ end test_mul(Normed) end +@testset "fdiv" begin + for N in target(Normed; ex = :thin) + @test wrapping_fdiv(typemax(N), typemax(N)) === one(N) + @test saturating_fdiv(typemax(N), typemax(N)) === one(N) + @test checked_fdiv(typemax(N), typemax(N)) === one(N) + + @test wrapping_fdiv(zero(N), eps(N)) === zero(N) + @test saturating_fdiv(zero(N), eps(N)) === zero(N) + @test checked_fdiv(zero(N), eps(N)) === zero(N) + + @test wrapping_fdiv(typemax(N), eps(N)) === (floattype(N))(typemax(rawtype(N))) % N + @test saturating_fdiv(typemax(N), eps(N)) === typemax(N) + @test_throws OverflowError checked_fdiv(typemax(N), eps(N)) + + @test wrapping_fdiv(zero(N), zero(N)) === zero(N) + @test saturating_fdiv(zero(N), zero(N)) === zero(N) + @test_throws DivideError checked_fdiv(zero(N), zero(N)) + + @test wrapping_fdiv(eps(N), zero(N)) === zero(N) + @test saturating_fdiv(eps(N), zero(N)) === typemax(N) + @test_throws DivideError checked_fdiv(eps(N), zero(N)) + 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