Skip to content

Commit

Permalink
Add checked arithmetic for / (#222)
Browse files Browse the repository at this point in the history
This also changes the implementation of `/` for `Fixed`.
`/` (`checked_fdiv`) throws `DivideError` for division by zero and `OverflowError` for overflow.
  • Loading branch information
kimikage authored Aug 31, 2020
1 parent 7f6575e commit f7d7bbc
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 3 deletions.
23 changes: 22 additions & 1 deletion src/FixedPointNumbers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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)
Expand All @@ -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}
Expand All @@ -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
Expand All @@ -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}
Expand Down
1 change: 0 additions & 1 deletion src/fixed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion src/normed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions test/common.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
25 changes: 25 additions & 0 deletions test/fixed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
25 changes: 25 additions & 0 deletions test/normed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit f7d7bbc

Please sign in to comment.