Skip to content

Commit f7d7bbc

Browse files
authored
Add checked arithmetic for / (#222)
This also changes the implementation of `/` for `Fixed`. `/` (`checked_fdiv`) throws `DivideError` for division by zero and `OverflowError` for overflow.
1 parent 7f6575e commit f7d7bbc

File tree

6 files changed

+84
-3
lines changed

6 files changed

+84
-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)
@@ -225,6 +232,9 @@ saturating_sub(x::X, y::X) where {X <: FixedPoint{<:Unsigned}} = X(x.i - min(x.i
225232

226233
saturating_mul(x::X, y::X) where {X <: FixedPoint} = clamp(float(x) * float(y), X)
227234

235+
saturating_fdiv(x::X, y::X) where {X <: FixedPoint} =
236+
clamp(floattype(X)(x.i) / floattype(X)(y.i), X)
237+
228238
# checked arithmetic
229239
checked_neg(x::X) where {X <: FixedPoint} = checked_sub(zero(X), x)
230240
function checked_abs(x::X) where {X <: FixedPoint}
@@ -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/common.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,18 @@ function test_mul(TX::Type)
222222
end
223223
end
224224

225+
function test_fdiv(TX::Type)
226+
for X in target(TX, :i8; ex = :thin)
227+
xys = xypairs(X)
228+
fdiv(x, y) = oftype(float(x), big(x) / big(y))
229+
fdivz(x, y) = y === zero(y) ? float(y) : fdiv(x, y)
230+
@test all(((x, y),) -> wrapping_fdiv(x, y) === fdivz(x, y) % X, xys)
231+
@test all(((x, y),) -> saturating_fdiv(x, y) === clamp(fdiv(x, y), X), xys)
232+
@test all(((x, y),) -> !(typemin(X) <= fdiv(x, y) <= typemax(X)) ||
233+
wrapping_fdiv(x, y) === checked_fdiv(x, y), xys)
234+
end
235+
end
236+
225237
function test_isapprox(TX::Type)
226238
@testset "approx $X" for X in target(TX, :i8, :i16; ex = :light)
227239
xs = typemin(X):eps(X):typemax(X)-eps(X)

test/fixed.jl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,31 @@ end
377377
FixedPointNumbers.mul_with_rounding(1.5Q6f1, -0.5Q6f1, RoundDown) === -1.0Q6f1
378378
end
379379

380+
@testset "fdiv" begin
381+
for F in target(Fixed; ex = :thin)
382+
@test wrapping_fdiv(typemax(F), -typemax(F)) === F(-1)
383+
@test saturating_fdiv(typemax(F), -typemax(F)) === F(-1)
384+
@test checked_fdiv(typemax(F), -typemax(F)) === F(-1)
385+
386+
@test wrapping_fdiv(zero(F), typemin(F)) === zero(F)
387+
@test saturating_fdiv(zero(F), typemin(F)) === zero(F)
388+
@test checked_fdiv(zero(F), typemin(F)) === zero(F)
389+
390+
@test wrapping_fdiv(typemin(F), F(-1)) === wrapping_neg(typemin(F))
391+
@test saturating_fdiv(typemin(F), F(-1)) === typemax(F)
392+
@test_throws OverflowError checked_fdiv(typemin(F), F(-1))
393+
394+
@test wrapping_fdiv(zero(F), zero(F)) === zero(F)
395+
@test saturating_fdiv(zero(F), zero(F)) === zero(F)
396+
@test_throws DivideError checked_fdiv(zero(F), zero(F))
397+
398+
@test wrapping_fdiv(-eps(F), zero(F)) === zero(F)
399+
@test saturating_fdiv(-eps(F), zero(F)) === typemin(F)
400+
@test_throws DivideError checked_fdiv(-eps(F), zero(F))
401+
end
402+
test_fdiv(Fixed)
403+
end
404+
380405
@testset "rounding" begin
381406
for sym in (:i8, :i16, :i32, :i64)
382407
T = symbol_to_inttype(Fixed, sym)

test/normed.jl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,31 @@ end
380380
test_mul(Normed)
381381
end
382382

383+
@testset "fdiv" begin
384+
for N in target(Normed; ex = :thin)
385+
@test wrapping_fdiv(typemax(N), typemax(N)) === one(N)
386+
@test saturating_fdiv(typemax(N), typemax(N)) === one(N)
387+
@test checked_fdiv(typemax(N), typemax(N)) === one(N)
388+
389+
@test wrapping_fdiv(zero(N), eps(N)) === zero(N)
390+
@test saturating_fdiv(zero(N), eps(N)) === zero(N)
391+
@test checked_fdiv(zero(N), eps(N)) === zero(N)
392+
393+
@test wrapping_fdiv(typemax(N), eps(N)) === (floattype(N))(typemax(rawtype(N))) % N
394+
@test saturating_fdiv(typemax(N), eps(N)) === typemax(N)
395+
@test_throws OverflowError checked_fdiv(typemax(N), eps(N))
396+
397+
@test wrapping_fdiv(zero(N), zero(N)) === zero(N)
398+
@test saturating_fdiv(zero(N), zero(N)) === zero(N)
399+
@test_throws DivideError checked_fdiv(zero(N), zero(N))
400+
401+
@test wrapping_fdiv(eps(N), zero(N)) === zero(N)
402+
@test saturating_fdiv(eps(N), zero(N)) === typemax(N)
403+
@test_throws DivideError checked_fdiv(eps(N), zero(N))
404+
end
405+
test_fdiv(Normed)
406+
end
407+
383408
@testset "div/fld1" begin
384409
@test div(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == 8
385410
@test div(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == 7

0 commit comments

Comments
 (0)