From 0042f3fc18145156c70ae41099068a822d6c1eb7 Mon Sep 17 00:00:00 2001 From: kimikage Date: Tue, 7 Jul 2020 12:51:16 +0900 Subject: [PATCH] Add checked, wrapping and saturating arithmetic for add/sub/neg The default arithmetic is still the wrapping arithmetic. --- src/FixedPointNumbers.jl | 49 +++++++++++++++++++++++--- test/common.jl | 1 + test/fixed.jl | 75 ++++++++++++++++++++++++++++++++++++++++ test/normed.jl | 75 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 196 insertions(+), 4 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 666ecb14..970cd8cb 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -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 @@ -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") @@ -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) @@ -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 diff --git a/test/common.jl b/test/common.jl index f8ae4a96..062a6dd3 100644 --- a/test/common.jl +++ b/test/common.jl @@ -1,5 +1,6 @@ using FixedPointNumbers, Statistics, Random, Test using FixedPointNumbers: bitwidth, rawtype, nbitsfrac +using Base.Checked """ target(X::Type, Ss...; ex = :default) diff --git a/test/fixed.jl b/test/fixed.jl index c37f54ba..251934b8 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -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) diff --git a/test/normed.jl b/test/normed.jl index 40e69b3c..6c751724 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -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