diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 3755280a..1d8be6f5 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -12,7 +12,8 @@ import Base: ==, <, <=, -, +, *, /, ~, isapprox, import Statistics # for _mean_promote import Random: Random, AbstractRNG, SamplerType, rand! -import Base.Checked: checked_neg, checked_add, checked_sub, checked_mul, checked_div +import Base.Checked: checked_neg, checked_abs, checked_add, checked_sub, checked_mul, + checked_div using Base: @pure @@ -36,8 +37,8 @@ export # Q and N typealiases are exported in separate source files # Functions scaledual, - wrapping_neg, wrapping_add, wrapping_sub, wrapping_mul, - saturating_neg, saturating_add, saturating_sub, saturating_mul + wrapping_neg, wrapping_abs, wrapping_add, wrapping_sub, wrapping_mul, + saturating_neg, saturating_abs, saturating_add, saturating_sub, saturating_mul include("utilities.jl") @@ -202,6 +203,7 @@ float(x::FixedPoint) = convert(floattype(x), x) # wrapping arithmetic wrapping_neg(x::X) where {X <: FixedPoint} = X(-x.i, 0) +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) @@ -209,6 +211,9 @@ wrapping_sub(x::X, y::X) where {X <: FixedPoint} = X(x.i - y.i, 0) saturating_neg(x::X) where {X <: FixedPoint} = X(~min(x.i - true, x.i), 0) saturating_neg(x::X) where {X <: FixedPoint{<:Unsigned}} = zero(X) +saturating_abs(x::X) where {X <: FixedPoint} = + X(ifelse(signbit(abs(x.i)), typemax(x.i), abs(x.i)), 0) + saturating_add(x::X, y::X) where {X <: FixedPoint} = X(x.i + ifelse(x.i < 0, max(y.i, typemin(x.i) - x.i), min(y.i, typemax(x.i) - x.i)), 0) saturating_add(x::X, y::X) where {X <: FixedPoint{<:Unsigned}} = X(x.i + min(~x.i, y.i), 0) @@ -217,8 +222,13 @@ saturating_sub(x::X, y::X) where {X <: FixedPoint} = X(x.i - ifelse(x.i < 0, min(y.i, x.i - typemin(x.i)), max(y.i, x.i - typemax(x.i))), 0) saturating_sub(x::X, y::X) where {X <: FixedPoint{<:Unsigned}} = X(x.i - min(x.i, y.i), 0) + # checked arithmetic checked_neg(x::X) where {X <: FixedPoint} = checked_sub(zero(X), x) +function checked_abs(x::X) where {X <: FixedPoint} + abs(x.i) >= 0 || throw_overflowerror_abs(x) + X(abs(x.i), 0) +end function checked_add(x::X, y::X) where {X <: FixedPoint} r, f = Base.Checked.add_with_overflow(x.i, y.i) z = X(r, 0) # store first @@ -235,7 +245,7 @@ end # default arithmetic const DEFAULT_ARITHMETIC = :wrapping -for (op, name) in ((:-, :neg), ) +for (op, name) in ((:-, :neg), (:abs, :abs)) f = Symbol(DEFAULT_ARITHMETIC, :_, name) @eval begin $op(x::X) where {X <: FixedPoint} = $f(x) @@ -298,7 +308,7 @@ for f in (:(==), :<, :<=, :div, :fld, :fld1) $f(x::X, y::X) where {X <: FixedPoint} = $f(x.i, y.i) end end -for f in (:~, :abs) +for f in (:~, ) @eval begin $f(x::X) where {X <: FixedPoint} = X($f(x.i), 0) end @@ -458,6 +468,12 @@ end showtype(io, typeof(x)) throw(OverflowError(String(take!(io)))) end +@noinline function throw_overflowerror_abs(@nospecialize(x)) + io = IOBuffer() + print(io, "abs(", x, ") 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) diff --git a/test/fixed.jl b/test/fixed.jl index 919e0406..fd40a18a 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -302,6 +302,26 @@ end end end +@testset "abs" begin + for F in target(Fixed; ex = :thin) + @test wrapping_abs(typemax(F)) === typemax(F) + @test saturating_abs(typemax(F)) === typemax(F) + @test checked_abs(typemax(F)) === typemax(F) + + @test wrapping_abs(typemin(F)) === typemin(F) + @test saturating_abs(typemin(F)) === typemax(F) + @test_throws OverflowError checked_abs(typemin(F)) + end + for F in target(Fixed, :i8; ex = :thin) + xs = typemin(F):eps(F):typemax(F) + fabs(x) = abs(float(x)) + @test all(x -> wrapping_abs(x) === (x > 0 ? x : wrapping_neg(x)), xs) + @test all(x -> saturating_abs(x) === clamp(fabs(x), F), xs) + @test all(x -> !(typemin(F) <= fabs(x) <= typemax(F)) || + wrapping_abs(x) === checked_abs(x) === fabs(x) % F, xs) + end +end + @testset "add" begin for F in target(Fixed; ex = :thin) @test wrapping_add(typemin(F), typemin(F)) === zero(F) diff --git a/test/normed.jl b/test/normed.jl index d88a7a12..ccca8d5f 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -323,6 +323,26 @@ end end end +@testset "abs" begin + for N in target(Normed; ex = :thin) + @test wrapping_abs(typemax(N)) === typemax(N) + @test saturating_abs(typemax(N)) === typemax(N) + @test checked_abs(typemax(N)) === typemax(N) + + @test wrapping_abs(typemin(N)) === typemin(N) + @test saturating_abs(typemin(N)) === typemin(N) + @test checked_abs(typemin(N)) === typemin(N) + end + for N in target(Normed, :i8; ex = :thin) + xs = typemin(N):eps(N):typemax(N) + fabs(x) = abs(float(x)) + @test all(x -> wrapping_abs(x) === (x > 0 ? x : wrapping_neg(x)), xs) + @test all(x -> saturating_abs(x) === clamp(fabs(x), N), xs) + @test all(x -> !(typemin(N) <= fabs(x) <= typemax(N)) || + wrapping_abs(x) === checked_abs(x) === fabs(x) % N, xs) + end +end + @testset "add" begin for N in target(Normed; ex = :thin) @test wrapping_add(typemin(N), typemin(N)) === zero(N)