Skip to content

Commit b8f066a

Browse files
committed
Add checked arithmetic for /
This also changes the implementation of `/` for `Fixed`. `/` (`checked_fdiv`) throws `DivideError` for division by zero and `OverflowError` for overflow.
1 parent a142308 commit b8f066a

File tree

5 files changed

+90
-3
lines changed

5 files changed

+90
-3
lines changed

src/FixedPointNumbers.jl

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ export
3838
# Functions
3939
scaledual,
4040
wrapping_neg, wrapping_abs, wrapping_add, wrapping_sub, wrapping_mul,
41-
saturating_neg, saturating_abs, saturating_add, saturating_sub, saturating_mul
41+
wrapping_fdiv,
42+
saturating_neg, saturating_abs, saturating_add, saturating_sub, saturating_mul,
43+
saturating_fdiv,
44+
checked_fdiv
4245

4346
include("utilities.jl")
4447

@@ -207,6 +210,10 @@ wrapping_abs(x::X) where {X <: FixedPoint} = X(abs(x.i), 0)
207210
wrapping_add(x::X, y::X) where {X <: FixedPoint} = X(x.i + y.i, 0)
208211
wrapping_sub(x::X, y::X) where {X <: FixedPoint} = X(x.i - y.i, 0)
209212
wrapping_mul(x::X, y::X) where {X <: FixedPoint} = (float(x) * float(y)) % X
213+
function wrapping_fdiv(x::X, y::X) where {X <: FixedPoint}
214+
z = floattype(X)(x.i) / floattype(X)(y.i)
215+
isfinite(z) ? z % X : zero(X)
216+
end
210217

211218
# saturating arithmetic
212219
saturating_neg(x::X) where {X <: FixedPoint} = X(~min(x.i - true, x.i), 0)
@@ -224,6 +231,9 @@ saturating_sub(x::X, y::X) where {X <: FixedPoint} =
224231
saturating_sub(x::X, y::X) where {X <: FixedPoint{<:Unsigned}} = X(x.i - min(x.i, y.i), 0)
225232

226233
saturating_mul(x::X, y::X) where {X <: FixedPoint} = clamp(float(x) * float(y), X)
234+
function saturating_fdiv(x::X, y::X) where {X <: FixedPoint}
235+
clamp(floattype(X)(x.i) / floattype(X)(y.i), X)
236+
end
227237

228238
# checked arithmetic
229239
checked_neg(x::X) where {X <: FixedPoint} = checked_sub(zero(X), x)
@@ -248,6 +258,16 @@ function checked_mul(x::X, y::X) where {X <: FixedPoint}
248258
typemin(X) - eps(X)/2 <= z < typemax(X) + eps(X)/2 || throw_overflowerror(:*, x, y)
249259
z % X
250260
end
261+
function checked_fdiv(x::X, y::X) where {T, X <: FixedPoint{T}}
262+
y === zero(X) && throw(DivideError())
263+
z = floattype(X)(x.i) / floattype(X)(y.i)
264+
if T <: Unsigned
265+
z < typemax(X) + eps(X)/2 || throw_overflowerror(:/, x, y)
266+
else
267+
typemin(X) - eps(X)/2 <= z < typemax(X) + eps(X)/2 || throw_overflowerror(:/, x, y)
268+
end
269+
z % X
270+
end
251271

252272
# default arithmetic
253273
const DEFAULT_ARITHMETIC = :wrapping
@@ -264,6 +284,7 @@ for (op, name) in ((:+, :add), (:-, :sub), (:*, :mul))
264284
$op(x::X, y::X) where {X <: FixedPoint} = $f(x, y)
265285
end
266286
end
287+
/(x::X, y::X) where {X <: FixedPoint} = checked_fdiv(x, y) # force checked arithmetic
267288

268289

269290
function minmax(x::X, y::X) where {X <: FixedPoint}

src/fixed.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,6 @@ function mul_with_rounding(x::F, y::F, ::RoundingMode{:Down}) where
179179
F((widemul(x.i, y.i) >> f) % T, 0)
180180
end
181181

182-
/(x::Fixed{T,f}, y::Fixed{T,f}) where {T,f} = Fixed{T,f}(div(convert(widen(T), x.i) << f, y.i), 0)
183182

184183
function trunc(x::Fixed{T,f}) where {T, f}
185184
f == 0 && return x

src/normed.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,6 @@ end
291291
# Override the default arithmetic with `checked` for backward compatibility
292292
*(x::N, y::N) where {N <: Normed} = checked_mul(x, y)
293293

294-
/(x::T, y::T) where {T <: Normed} = convert(T,convert(floattype(T), x)/convert(floattype(T), y))
295294

296295
# Functions
297296
trunc(x::N) where {N <: Normed} = floor(x)

test/fixed.jl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,40 @@ end
431431
FixedPointNumbers.mul_with_rounding(1.5Q6f1, -0.5Q6f1, RoundDown) === -1.0Q6f1
432432
end
433433

434+
@testset "fdiv" begin
435+
for F in target(Fixed; ex = :thin)
436+
@test wrapping_fdiv(typemax(F), -typemax(F)) === F(-1)
437+
@test saturating_fdiv(typemax(F), -typemax(F)) === F(-1)
438+
@test checked_fdiv(typemax(F), -typemax(F)) === F(-1)
439+
440+
@test wrapping_fdiv(zero(F), typemin(F)) === zero(F)
441+
@test saturating_fdiv(zero(F), typemin(F)) === zero(F)
442+
@test checked_fdiv(zero(F), typemin(F)) === zero(F)
443+
444+
@test wrapping_fdiv(typemin(F), F(-1)) === wrapping_neg(typemin(F))
445+
@test saturating_fdiv(typemin(F), F(-1)) === typemax(F)
446+
@test_throws OverflowError checked_fdiv(typemin(F), F(-1))
447+
448+
@test wrapping_fdiv(zero(F), zero(F)) === zero(F)
449+
@test saturating_fdiv(zero(F), zero(F)) === zero(F)
450+
@test_throws DivideError checked_fdiv(zero(F), zero(F))
451+
452+
@test wrapping_fdiv(-eps(F), zero(F)) === zero(F)
453+
@test saturating_fdiv(-eps(F), zero(F)) === typemin(F)
454+
@test_throws DivideError checked_fdiv(-eps(F), zero(F))
455+
end
456+
for F in target(Fixed, :i8; ex = :thin)
457+
xs = typemin(F):eps(F):typemax(F)
458+
xys = ((x, y) for x in xs, y in xs)
459+
fdiv(x, y) = oftype(float(x), big(x) / big(y))
460+
fdivz(x, y) = y === zero(y) ? float(y) : fdiv(x, y)
461+
@test all(((x, y),) -> wrapping_fdiv(x, y) === fdivz(x, y) % F, xys)
462+
@test all(((x, y),) -> saturating_fdiv(x, y) === clamp(fdiv(x, y), F), xys)
463+
@test all(((x, y),) -> !(typemin(F) <= fdiv(x, y) <= typemax(F)) ||
464+
wrapping_fdiv(x, y) === checked_fdiv(x, y), xys)
465+
end
466+
end
467+
434468
@testset "rounding" begin
435469
for sym in (:i8, :i16, :i32, :i64)
436470
T = symbol_to_inttype(Fixed, sym)

test/normed.jl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,40 @@ end
432432
end
433433
end
434434

435+
@testset "fdiv" begin
436+
for N in target(Normed; ex = :thin)
437+
@test wrapping_fdiv(typemax(N), typemax(N)) === one(N)
438+
@test saturating_fdiv(typemax(N), typemax(N)) === one(N)
439+
@test checked_fdiv(typemax(N), typemax(N)) === one(N)
440+
441+
@test wrapping_fdiv(zero(N), eps(N)) === zero(N)
442+
@test saturating_fdiv(zero(N), eps(N)) === zero(N)
443+
@test checked_fdiv(zero(N), eps(N)) === zero(N)
444+
445+
@test wrapping_fdiv(typemax(N), eps(N)) === (floattype(N))(typemax(rawtype(N))) % N
446+
@test saturating_fdiv(typemax(N), eps(N)) === typemax(N)
447+
@test_throws OverflowError checked_fdiv(typemax(N), eps(N))
448+
449+
@test wrapping_fdiv(zero(N), zero(N)) === zero(N)
450+
@test saturating_fdiv(zero(N), zero(N)) === zero(N)
451+
@test_throws DivideError checked_fdiv(zero(N), zero(N))
452+
453+
@test wrapping_fdiv(eps(N), zero(N)) === zero(N)
454+
@test saturating_fdiv(eps(N), zero(N)) === typemax(N)
455+
@test_throws DivideError checked_fdiv(eps(N), zero(N))
456+
end
457+
for N in target(Normed, :i8; ex = :thin)
458+
xs = typemin(N):eps(N):typemax(N)
459+
xys = ((x, y) for x in xs, y in xs)
460+
fdiv(x, y) = oftype(float(x), big(x) / big(y))
461+
fdivz(x, y) = y === zero(y) ? float(y) : fdiv(x, y)
462+
@test all(((x, y),) -> wrapping_fdiv(x, y) === fdivz(x, y) % N, xys)
463+
@test all(((x, y),) -> saturating_fdiv(x, y) === clamp(fdiv(x, y), N), xys)
464+
@test all(((x, y),) -> !(typemin(N) <= fdiv(x, y) <= typemax(N)) ||
465+
wrapping_fdiv(x, y) === checked_fdiv(x, y), xys)
466+
end
467+
end
468+
435469
@testset "div/fld1" begin
436470
@test div(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == 8
437471
@test div(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == 7

0 commit comments

Comments
 (0)