Skip to content

Add checked, wrapping and saturating arithmetic for add/sub/neg #190

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 45 additions & 4 deletions src/FixedPointNumbers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Base: ==, <, <=, -, +, *, /, ~, isapprox,
import Statistics # for _mean_promote
import Random: Random, AbstractRNG, SamplerType, rand!

using Base.Checked: checked_add, checked_sub, checked_div
import Base.Checked: checked_neg, checked_add, checked_sub, checked_div

using Base: @pure

Expand All @@ -35,7 +35,9 @@ export
# "special" typealiases
# Q and N typealiases are exported in separate source files
# Functions
scaledual
scaledual,
wrapping_neg, wrapping_add, wrapping_sub,
saturating_neg, saturating_add, saturating_sub

include("utilities.jl")

Expand Down Expand Up @@ -180,6 +182,45 @@ floattype(::Type{Base.TwicePrecision{T}}) where T<:Union{Float16,Float32} = wide

float(x::FixedPoint) = convert(floattype(x), x)

# wrapping arithmetic
wrapping_neg(x::X) where {X <: FixedPoint} = X(-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)

# saturating arithmetic
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_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)

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} = X(checked_neg(x.i), 0)
checked_add(x::X, y::X) where {X <: FixedPoint} = X(checked_add(x.i, y.i), 0)
checked_sub(x::X, y::X) where {X <: FixedPoint} = X(checked_sub(x.i, y.i), 0)

# default arithmetic
const DEFAULT_ARITHMETIC = :wrapping

for (op, name) in ((:-, :neg), )
f = Symbol(DEFAULT_ARITHMETIC, :_, name)
@eval begin
$op(x::X) where {X <: FixedPoint} = $f(x)
end
end
for (op, name) in ((:+, :add), (:-, :sub))
f = Symbol(DEFAULT_ARITHMETIC, :_, name)
@eval begin
$op(x::X, y::X) where {X <: FixedPoint} = $f(x, y)
end
end


function minmax(x::X, y::X) where {X <: FixedPoint}
a, b = minmax(reinterpret(x), reinterpret(y))
X(a,0), X(b,0)
Expand Down Expand Up @@ -229,12 +270,12 @@ 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 (:~, :abs)
@eval begin
$f(x::X) where {X <: FixedPoint} = X($f(x.i), 0)
end
end
for f in (:+, :-, :rem, :mod, :mod1, :min, :max)
for f in (:rem, :mod, :mod1, :min, :max)
@eval begin
$f(x::X, y::X) where {X <: FixedPoint} = X($f(x.i, y.i), 0)
end
Expand Down
1 change: 1 addition & 0 deletions test/common.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using FixedPointNumbers, Statistics, Random, Test
using FixedPointNumbers: bitwidth, rawtype, nbitsfrac
using Base.Checked

"""
target(X::Type, Ss...; ex = :default)
Expand Down
75 changes: 75 additions & 0 deletions test/fixed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,81 @@ end
@test (-67.2 % T).i == round(Int, -67.2*512) % Int16
end

@testset "neg" begin
for F in target(Fixed; ex = :thin)
@test wrapping_neg(typemin(F)) === typemin(F)
@test saturating_neg(typemin(F)) === typemax(F)
@test_throws OverflowError checked_neg(typemin(F))

@test wrapping_neg(typemax(F)) === typemin(F) + eps(F)
@test saturating_neg(typemax(F)) === typemin(F) + eps(F)
@test checked_neg(typemax(F)) === typemin(F) + eps(F)

@test wrapping_neg(eps(F)) === zero(F) - eps(F)
@test saturating_neg(eps(F)) === zero(F) - eps(F)
@test checked_neg(eps(F)) === zero(F) - eps(F)
end
for F in target(Fixed, :i8; ex = :thin)
xs = typemin(F):eps(F):typemax(F)
fneg(x) = -float(x)
@test all(x -> wrapping_neg(wrapping_neg(x)) === x, xs)
@test all(x -> saturating_neg(x) == clamp(fneg(x), F), xs)
@test all(x -> !(typemin(F) < fneg(x) < typemax(F)) ||
wrapping_neg(x) === checked_neg(x) === fneg(x) % F, xs)
end
end

@testset "add" begin
for F in target(Fixed; ex = :thin)
@test wrapping_add(typemin(F), typemin(F)) === zero(F)
@test saturating_add(typemin(F), typemin(F)) === typemin(F)
@test_throws OverflowError checked_add(typemin(F), typemin(F))

@test wrapping_add(typemax(F), eps(F)) === wrapping_add(eps(F), typemax(F)) === typemin(F)
@test saturating_add(typemax(F), eps(F)) === saturating_add(eps(F), typemax(F)) === typemax(F)
@test_throws OverflowError checked_add(typemax(F), eps(F))
@test_throws OverflowError checked_add(eps(F), typemax(F))

@test wrapping_add(zero(F), eps(F)) === wrapping_add(eps(F), zero(F)) === eps(F)
@test saturating_add(zero(F), eps(F)) === saturating_add(eps(F), zero(F)) === eps(F)
@test checked_add(zero(F), eps(F)) === checked_add(eps(F), zero(F)) === eps(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)
fadd(x, y) = float(x) + float(y)
@test all(((x, y),) -> wrapping_sub(wrapping_add(x, y), y) === x, xys)
@test all(((x, y),) -> saturating_add(x, y) == clamp(fadd(x, y), F), xys)
@test all(((x, y),) -> !(typemin(F) < fadd(x, y) < typemax(F)) ||
wrapping_add(x, y) === checked_add(x, y) === fadd(x, y) % F, xys)
end
end

@testset "sub" begin
for F in target(Fixed; ex = :thin)
@test wrapping_sub(typemin(F), typemin(F)) === zero(F)
@test saturating_sub(typemin(F), typemin(F)) === zero(F)
@test checked_sub(typemin(F), typemin(F)) === zero(F)

@test wrapping_sub(typemin(F), eps(F)) === typemax(F)
@test saturating_sub(typemin(F), eps(F)) === typemin(F)
@test_throws OverflowError checked_sub(typemin(F), eps(F))

@test wrapping_sub(eps(F), zero(F)) === eps(F)
@test saturating_sub(eps(F), zero(F)) === eps(F)
@test checked_sub(eps(F), zero(F)) === eps(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)
fsub(x, y) = float(x) - float(y)
@test all(((x, y),) -> wrapping_add(wrapping_sub(x, y), y) === x, xys)
@test all(((x, y),) -> saturating_sub(x, y) == clamp(fsub(x, y), F), xys)
@test all(((x, y),) -> !(typemin(F) < fsub(x, y) < typemax(F)) ||
wrapping_sub(x, y) === checked_sub(x, y) === fsub(x, y) % F, xys)
end
end

@testset "rounding" begin
for sym in (:i8, :i16, :i32, :i64)
T = symbol_to_inttype(Fixed, sym)
Expand Down
75 changes: 75 additions & 0 deletions test/normed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,81 @@ end
end
end

@testset "neg" begin
for N in target(Normed; ex = :thin)
@test wrapping_neg(typemin(N)) === zero(N)
@test saturating_neg(typemin(N)) === zero(N)
@test checked_neg(typemin(N)) === zero(N)

@test wrapping_neg(typemax(N)) === eps(N)
@test saturating_neg(typemax(N)) === zero(N)
@test_throws OverflowError checked_neg(typemax(N))

@test wrapping_neg(eps(N)) === typemax(N)
@test saturating_neg(eps(N)) === zero(N)
@test_throws OverflowError checked_neg(eps(N))
end
for N in target(Normed, :i8; ex = :thin)
xs = typemin(N):eps(N):typemax(N)
fneg(x) = -float(x)
@test all(x -> wrapping_neg(wrapping_neg(x)) === x, xs)
@test all(x -> saturating_neg(x) === clamp(fneg(x), N), xs)
@test all(x -> !(typemin(N) < fneg(x) < typemax(N)) ||
wrapping_neg(x) === checked_neg(x) === fneg(x) % N, xs)
end
end

@testset "add" begin
for N in target(Normed; ex = :thin)
@test wrapping_add(typemin(N), typemin(N)) === zero(N)
@test saturating_add(typemin(N), typemin(N)) === zero(N)
@test checked_add(typemin(N), typemin(N)) === zero(N)

@test wrapping_add(typemax(N), eps(N)) === wrapping_add(eps(N), typemax(N)) === zero(N)
@test saturating_add(typemax(N), eps(N)) === saturating_add(eps(N), typemax(N)) === typemax(N)
@test_throws OverflowError checked_add(typemax(N), eps(N))
@test_throws OverflowError checked_add(eps(N), typemax(N))

@test wrapping_add(zero(N), eps(N)) === wrapping_add(eps(N), zero(N)) === eps(N)
@test saturating_add(zero(N), eps(N)) === saturating_add(eps(N), zero(N)) === eps(N)
@test checked_add(zero(N), eps(N)) === checked_add(eps(N), zero(N)) === eps(N)
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)
fadd(x, y) = float(x) + float(y)
@test all(((x, y),) -> wrapping_sub(wrapping_add(x, y), y) === x, xys)
@test all(((x, y),) -> saturating_add(x, y) === clamp(fadd(x, y), N), xys)
@test all(((x, y),) -> !(typemin(N) < fadd(x, y) < typemax(N)) ||
wrapping_add(x, y) === checked_add(x, y) === fadd(x, y) % N, xys)
end
end

@testset "sub" begin
for N in target(Normed; ex = :thin)
@test wrapping_sub(typemin(N), typemin(N)) === zero(N)
@test saturating_sub(typemin(N), typemin(N)) === zero(N)
@test checked_sub(typemin(N), typemin(N)) === zero(N)

@test wrapping_sub(typemin(N), eps(N)) === typemax(N)
@test saturating_sub(typemin(N), eps(N)) === typemin(N)
@test_throws OverflowError checked_sub(typemin(N), eps(N))

@test wrapping_sub(eps(N), zero(N)) === eps(N)
@test saturating_sub(eps(N), zero(N)) === eps(N)
@test checked_sub(eps(N), zero(N)) === eps(N)
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)
fsub(x, y) = float(x) - float(y)
@test all(((x, y),) -> wrapping_add(wrapping_sub(x, y), y) === x, xys)
@test all(((x, y),) -> saturating_sub(x, y) === clamp(fsub(x, y), N), xys)
@test all(((x, y),) -> !(typemin(N) < fsub(x, y) < typemax(N)) ||
wrapping_sub(x, y) === checked_sub(x, y) === fsub(x, y) % N, 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
Expand Down