diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 78d5f268..13165195 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -5,7 +5,7 @@ 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 @@ -13,7 +13,7 @@ import Statistics # for _mean_promote import Random: Random, AbstractRNG, SamplerType, rand! import Base.Checked: checked_neg, checked_abs, checked_add, checked_sub, checked_mul, - checked_div + checked_div, checked_fld, checked_cld using Base: @pure @@ -38,9 +38,9 @@ export # Functions scaledual, wrapping_neg, wrapping_abs, wrapping_add, wrapping_sub, wrapping_mul, - wrapping_fdiv, + wrapping_fdiv, wrapping_div, wrapping_fld, wrapping_cld, saturating_neg, saturating_abs, saturating_add, saturating_sub, saturating_mul, - saturating_fdiv, + saturating_fdiv, saturating_div, saturating_fld, saturating_cld, checked_fdiv include("utilities.jl") @@ -214,6 +214,17 @@ 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 +function wrapping_div(x::X, y::X, r::RoundingMode = RoundToZero) where {T, X <: FixedPoint{T}} + z = round(floattype(X)(x.i) / floattype(X)(y.i), r) + isfinite(z) || return zero(T) + if T <: Unsigned + _unsafe_trunc(T, z) + else + z > typemax(T) ? typemin(T) : _unsafe_trunc(T, z) + end +end +wrapping_fld(x::X, y::X) where {X <: FixedPoint} = wrapping_div(x, y, RoundDown) +wrapping_cld(x::X, y::X) where {X <: FixedPoint} = wrapping_div(x, y, RoundUp) # saturating arithmetic saturating_neg(x::X) where {X <: FixedPoint} = X(~min(x.i - true, x.i), 0) @@ -235,6 +246,18 @@ saturating_mul(x::X, y::X) where {X <: FixedPoint} = clamp(float(x) * float(y), saturating_fdiv(x::X, y::X) where {X <: FixedPoint} = clamp(floattype(X)(x.i) / floattype(X)(y.i), X) +function saturating_div(x::X, y::X, r::RoundingMode = RoundToZero) where {T, X <: FixedPoint{T}} + z = round(floattype(X)(x.i) / floattype(X)(y.i), r) + isnan(z) && return zero(T) + if T <: Unsigned + isfinite(z) ? _unsafe_trunc(T, z) : typemax(T) + else + _unsafe_trunc(T, clamp(z, typemin(T), typemax(T))) + end +end +saturating_fld(x::X, y::X) where {X <: FixedPoint} = saturating_div(x, y, RoundDown) +saturating_cld(x::X, y::X) where {X <: FixedPoint} = saturating_div(x, y, RoundUp) + # checked arithmetic checked_neg(x::X) where {X <: FixedPoint} = checked_sub(zero(X), x) function checked_abs(x::X) where {X <: FixedPoint} @@ -268,6 +291,16 @@ function checked_fdiv(x::X, y::X) where {T, X <: FixedPoint{T}} end z % X end +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 +checked_fld(x::X, y::X) where {X <: FixedPoint} = checked_div(x, y, RoundDown) +checked_cld(x::X, y::X) where {X <: FixedPoint} = checked_div(x, y, RoundUp) # default arithmetic const DEFAULT_ARITHMETIC = :wrapping @@ -284,8 +317,11 @@ 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 - +# force checked arithmetic +/(x::X, y::X) where {X <: FixedPoint} = checked_fdiv(x, y) +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)) @@ -331,7 +367,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 @@ -502,6 +538,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 a03ce150..6d27c9d5 100644 --- a/test/common.jl +++ b/test/common.jl @@ -234,6 +234,61 @@ 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) + rem_t(x) = x > typemax(T) ? typemin(T) : unsafe_trunc(T, x) + z = y === zero(y) ? float(y) : fdiv(x, y) + return (wrapping_div(x, y) === rem_t(trunc(z))) & + (wrapping_fld(x, y) === rem_t(floor(z))) & + (wrapping_cld(x, y) === rem_t( ceil(z))) + end + @test all(xys) do (x, y) + clamp_t(x) = isnan(x) ? zero(T) : trunc(T, clamp(x, typemin(T), typemax(T))) + z = fdiv(x, y) + return (saturating_div(x, y) === clamp_t(trunc(z))) & + (saturating_fld(x, y) === clamp_t(floor(z))) & + (saturating_cld(x, y) === clamp_t( ceil(z))) + end + @test all(xys) do (x, y) + z = fdiv(x, y) + t = !(typemin(T) <= trunc(z) <= typemax(T)) || wrapping_div(x, y) === checked_div(x, y) + f = !(typemin(T) <= floor(z) <= typemax(T)) || wrapping_fld(x, y) === checked_fld(x, y) + c = !(typemin(T) <= ceil(z) <= typemax(T)) || wrapping_cld(x, y) === 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) * eps2 + r) % X === typemin(X) + d, r = fldmod1(typemax(X), eps2) + @test d isa T && r isa X && ((d - 1) * eps2 + r) % X === typemax(X) + 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 a1cb3801..de415983 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -402,6 +402,56 @@ 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 wrapping_div(fm, fm) === wrapping_fld(fm, fm) === wrapping_cld(fm, fm) === one(T) + @test saturating_div(fm, fm) === saturating_fld(fm, fm) === saturating_cld(fm, fm) === one(T) + @test checked_div(fm, fm) === checked_fld(fm, fm) === checked_cld(fm, fm) === one(T) + + @test wrapping_div(fz, fe) === wrapping_fld(fz, fe) === wrapping_cld(fz, fe) === zero(T) + @test saturating_div(fz, fe) === saturating_fld(fz, fe) === saturating_cld(fz, fe) === zero(T) + @test checked_div(fz, fe) === checked_fld(fz, fe) === checked_cld(fz, fe) === zero(T) + + @test wrapping_div(fm, fe) === wrapping_fld(fm, fe) === wrapping_cld(fm, fe) === typemax(T) + @test saturating_div(fm, fe) === saturating_fld(fm, fe) === saturating_cld(fm, fe) === typemax(T) + @test checked_div(fm, fe) === checked_fld(fm, fe) === checked_cld(fm, fe) === typemax(T) + + @test wrapping_div(fz, fz) === wrapping_fld(fz, fz) === wrapping_cld(fz, fz) === zero(T) + @test saturating_div(fz, fz) === saturating_fld(fz, fz) === saturating_cld(fz, fz) === zero(T) + @test_throws DivideError checked_div(fz, fz) + @test_throws DivideError checked_fld(fz, fz) + @test_throws DivideError checked_cld(fz, fz) + + @test wrapping_div(fe, fz) === wrapping_fld(fe, fz) === wrapping_cld(fe, fz) === zero(T) + @test saturating_div(fe, fz) === saturating_fld(fe, fz) === saturating_cld(fe, fz) === typemax(T) + @test_throws DivideError checked_div(fe, fz) + @test_throws DivideError checked_fld(fe, fz) + @test_throws DivideError checked_cld(fe, fz) + + @test wrapping_div(fn, -fe) === wrapping_fld(fn, -fe) === wrapping_cld(fn, -fe) === typemin(T) + @test saturating_div(fn, -fe) === saturating_fld(fn, -fe) === saturating_cld(fn, -fe) === typemax(T) + @test_throws OverflowError checked_div(fn, -fe) + @test_throws OverflowError checked_fld(fn, -fe) + @test_throws OverflowError checked_cld(fn, -fe) + + @test wrapping_div(fe, fm) === saturating_div(fe, fm) === checked_div(fe, fm) === zero(T) + @test wrapping_fld(fe, fm) === saturating_fld(fe, fm) === checked_fld(fe, fm) === zero(T) + @test wrapping_cld(fe, fm) === saturating_cld(fe, fm) === checked_cld(fe, fm) === one(T) + + @test wrapping_div(fe, fn) === saturating_div(fe, fn) === checked_div(fe, fn) === zero(T) + @test wrapping_fld(fe, fn) === saturating_fld(fe, fn) === checked_fld(fe, fn) === -one(T) + @test wrapping_cld(fe, fn) === saturating_cld(fe, fn) === 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 feb3411d..b3334d15 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -405,18 +405,49 @@ 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 wrapping_div(nm, nm) === wrapping_fld(nm, nm) === wrapping_cld(nm, nm) === one(T) + @test saturating_div(nm, nm) === saturating_fld(nm, nm) === saturating_cld(nm, nm) === one(T) + @test checked_div(nm, nm) === checked_fld(nm, nm) === checked_cld(nm, nm) === one(T) + + @test wrapping_div(nz, ne) === wrapping_fld(nz, ne) === wrapping_cld(nz, ne) === zero(T) + @test saturating_div(nz, ne) === saturating_fld(nz, ne) === saturating_cld(nz, ne) === zero(T) + @test checked_div(nz, ne) === checked_fld(nz, ne) === checked_cld(nz, ne) === zero(T) + + @test wrapping_div(nm, ne) === wrapping_fld(nm, ne) === wrapping_cld(nm, ne) === typemax(T) + @test saturating_div(nm, ne) === saturating_fld(nm, ne) === saturating_cld(nm, ne) === typemax(T) + @test checked_div(nm, ne) === checked_fld(nm, ne) === checked_cld(nm, ne) === typemax(T) + + @test wrapping_div(nz, nz) === wrapping_fld(nz, nz) === wrapping_cld(nz, nz) === zero(T) + @test saturating_div(nz, nz) === saturating_fld(nz, nz) === saturating_cld(nz, nz) === zero(T) + @test_throws DivideError checked_div(nz, nz) + @test_throws DivideError checked_fld(nz, nz) + @test_throws DivideError checked_cld(nz, nz) + + @test wrapping_div(ne, nz) === wrapping_fld(ne, nz) === wrapping_cld(ne, nz) === zero(T) + @test saturating_div(ne, nz) === saturating_fld(ne, nz) === saturating_cld(ne, nz) === typemax(T) + @test_throws DivideError checked_div(ne, nz) + @test_throws DivideError checked_fld(ne, nz) + @test_throws DivideError checked_cld(ne, nz) + + @test wrapping_div(ne, nm) === saturating_div(ne, nm) === checked_div(ne, nm) === zero(T) + @test wrapping_fld(ne, nm) === saturating_fld(ne, nm) === checked_fld(ne, nm) === zero(T) + @test wrapping_cld(ne, nm) === saturating_cld(ne, nm) === 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