Skip to content

Commit c6b8a9c

Browse files
authored
Add checked, wrapping and saturating arithmetic for div/cld/fld (#226)
The default arithmetic for `div` is still checked arithmetic. This changes the error type for overflow from `DivideError` to `OverflowError`. This also adds the support for `cld` and 3-arg `div`.
1 parent 595f7a7 commit c6b8a9c

File tree

4 files changed

+192
-14
lines changed

4 files changed

+192
-14
lines changed

src/FixedPointNumbers.jl

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ import Base: ==, <, <=, -, +, *, /, ~, isapprox,
55
isnan, isinf, isfinite, isinteger,
66
zero, oneunit, one, typemin, typemax, floatmin, floatmax, eps, reinterpret,
77
big, rationalize, float, trunc, round, floor, ceil, bswap, clamp,
8-
div, fld, rem, mod, mod1, fld1, min, max, minmax,
8+
div, fld, cld, rem, mod, mod1, fld1, min, max, minmax,
99
signed, unsigned, copysign, flipsign, signbit,
1010
length
1111

1212
import Statistics # for _mean_promote
1313
import Random: Random, AbstractRNG, SamplerType, rand!
1414

1515
import Base.Checked: checked_neg, checked_abs, checked_add, checked_sub, checked_mul,
16-
checked_div
16+
checked_div, checked_fld, checked_cld
1717

1818
using Base: @pure
1919

@@ -38,9 +38,9 @@ export
3838
# Functions
3939
scaledual,
4040
wrapping_neg, wrapping_abs, wrapping_add, wrapping_sub, wrapping_mul,
41-
wrapping_fdiv,
41+
wrapping_fdiv, wrapping_div, wrapping_fld, wrapping_cld,
4242
saturating_neg, saturating_abs, saturating_add, saturating_sub, saturating_mul,
43-
saturating_fdiv,
43+
saturating_fdiv, saturating_div, saturating_fld, saturating_cld,
4444
checked_fdiv
4545

4646
include("utilities.jl")
@@ -214,6 +214,17 @@ function wrapping_fdiv(x::X, y::X) where {X <: FixedPoint}
214214
z = floattype(X)(x.i) / floattype(X)(y.i)
215215
isfinite(z) ? z % X : zero(X)
216216
end
217+
function wrapping_div(x::X, y::X, r::RoundingMode = RoundToZero) where {T, X <: FixedPoint{T}}
218+
z = round(floattype(X)(x.i) / floattype(X)(y.i), r)
219+
isfinite(z) || return zero(T)
220+
if T <: Unsigned
221+
_unsafe_trunc(T, z)
222+
else
223+
z > typemax(T) ? typemin(T) : _unsafe_trunc(T, z)
224+
end
225+
end
226+
wrapping_fld(x::X, y::X) where {X <: FixedPoint} = wrapping_div(x, y, RoundDown)
227+
wrapping_cld(x::X, y::X) where {X <: FixedPoint} = wrapping_div(x, y, RoundUp)
217228

218229
# saturating arithmetic
219230
saturating_neg(x::X) where {X <: FixedPoint} = X(~min(x.i - true, x.i), 0)
@@ -235,6 +246,18 @@ saturating_mul(x::X, y::X) where {X <: FixedPoint} = clamp(float(x) * float(y),
235246
saturating_fdiv(x::X, y::X) where {X <: FixedPoint} =
236247
clamp(floattype(X)(x.i) / floattype(X)(y.i), X)
237248

249+
function saturating_div(x::X, y::X, r::RoundingMode = RoundToZero) where {T, X <: FixedPoint{T}}
250+
z = round(floattype(X)(x.i) / floattype(X)(y.i), r)
251+
isnan(z) && return zero(T)
252+
if T <: Unsigned
253+
isfinite(z) ? _unsafe_trunc(T, z) : typemax(T)
254+
else
255+
_unsafe_trunc(T, clamp(z, typemin(T), typemax(T)))
256+
end
257+
end
258+
saturating_fld(x::X, y::X) where {X <: FixedPoint} = saturating_div(x, y, RoundDown)
259+
saturating_cld(x::X, y::X) where {X <: FixedPoint} = saturating_div(x, y, RoundUp)
260+
238261
# checked arithmetic
239262
checked_neg(x::X) where {X <: FixedPoint} = checked_sub(zero(X), x)
240263
function checked_abs(x::X) where {X <: FixedPoint}
@@ -268,6 +291,16 @@ function checked_fdiv(x::X, y::X) where {T, X <: FixedPoint{T}}
268291
end
269292
z % X
270293
end
294+
function checked_div(x::X, y::X, r::RoundingMode = RoundToZero) where {T, X <: FixedPoint{T}}
295+
y === zero(X) && throw(DivideError())
296+
z = round(floattype(X)(x.i) / floattype(X)(y.i), r)
297+
if T <: Signed
298+
z <= typemax(T) || throw_overflowerror_div(r, x, y)
299+
end
300+
_unsafe_trunc(T, z)
301+
end
302+
checked_fld(x::X, y::X) where {X <: FixedPoint} = checked_div(x, y, RoundDown)
303+
checked_cld(x::X, y::X) where {X <: FixedPoint} = checked_div(x, y, RoundUp)
271304

272305
# default arithmetic
273306
const DEFAULT_ARITHMETIC = :wrapping
@@ -284,8 +317,11 @@ for (op, name) in ((:+, :add), (:-, :sub), (:*, :mul))
284317
$op(x::X, y::X) where {X <: FixedPoint} = $f(x, y)
285318
end
286319
end
287-
/(x::X, y::X) where {X <: FixedPoint} = checked_fdiv(x, y) # force checked arithmetic
288-
320+
# force checked arithmetic
321+
/(x::X, y::X) where {X <: FixedPoint} = checked_fdiv(x, y)
322+
div(x::X, y::X, r::RoundingMode = RoundToZero) where {X <: FixedPoint} = checked_div(x, y, r)
323+
fld(x::X, y::X) where {X <: FixedPoint} = checked_div(x, y, RoundDown)
324+
cld(x::X, y::X) where {X <: FixedPoint} = checked_div(x, y, RoundUp)
289325

290326
function minmax(x::X, y::X) where {X <: FixedPoint}
291327
a, b = minmax(reinterpret(x), reinterpret(y))
@@ -331,7 +367,7 @@ for f in (:zero, :oneunit, :one, :eps, :rawone, :rawtype, :floattype)
331367
$f(x::FixedPoint) = $f(typeof(x))
332368
end
333369
end
334-
for f in (:(==), :<, :<=, :div, :fld, :fld1)
370+
for f in (:(==), :<, :<=, :fld1)
335371
@eval begin
336372
$f(x::X, y::X) where {X <: FixedPoint} = $f(x.i, y.i)
337373
end
@@ -502,6 +538,12 @@ end
502538
showtype(io, typeof(x))
503539
throw(OverflowError(String(take!(io))))
504540
end
541+
@noinline function throw_overflowerror_div(r::RoundingMode, @nospecialize(x), @nospecialize(y))
542+
io = IOBuffer()
543+
op = r === RoundUp ? "cld(" : r === RoundDown ? "fld(" : "div("
544+
print(io, op, x, ", ", y, ") overflowed for type ", rawtype(x))
545+
throw(OverflowError(String(take!(io))))
546+
end
505547

506548
function Random.rand(r::AbstractRNG, ::SamplerType{X}) where X <: FixedPoint
507549
X(rand(r, rawtype(X)), 0)

test/common.jl

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,61 @@ function test_fdiv(TX::Type)
234234
end
235235
end
236236

237+
function test_div(TX::Type)
238+
for X in target(TX, :i8; ex = :thin)
239+
T = rawtype(X)
240+
xys = xypairs(X)
241+
fdiv(x, y) = oftype(float(x), big(x) / big(y))
242+
@test all(xys) do (x, y)
243+
rem_t(x) = x > typemax(T) ? typemin(T) : unsafe_trunc(T, x)
244+
z = y === zero(y) ? float(y) : fdiv(x, y)
245+
return (wrapping_div(x, y) === rem_t(trunc(z))) &
246+
(wrapping_fld(x, y) === rem_t(floor(z))) &
247+
(wrapping_cld(x, y) === rem_t( ceil(z)))
248+
end
249+
@test all(xys) do (x, y)
250+
clamp_t(x) = isnan(x) ? zero(T) : trunc(T, clamp(x, typemin(T), typemax(T)))
251+
z = fdiv(x, y)
252+
return (saturating_div(x, y) === clamp_t(trunc(z))) &
253+
(saturating_fld(x, y) === clamp_t(floor(z))) &
254+
(saturating_cld(x, y) === clamp_t( ceil(z)))
255+
end
256+
@test all(xys) do (x, y)
257+
z = fdiv(x, y)
258+
t = !(typemin(T) <= trunc(z) <= typemax(T)) || wrapping_div(x, y) === checked_div(x, y)
259+
f = !(typemin(T) <= floor(z) <= typemax(T)) || wrapping_fld(x, y) === checked_fld(x, y)
260+
c = !(typemin(T) <= ceil(z) <= typemax(T)) || wrapping_cld(x, y) === checked_cld(x, y)
261+
return t & f & c
262+
end
263+
end
264+
end
265+
266+
function test_div_3arg(TX::Type)
267+
for X in target(TX; ex = :thin)
268+
@test div(eps(X), typemax(X), RoundToZero) === div(eps(X), typemax(X))
269+
@test div(eps(X), typemax(X), RoundDown) === fld(eps(X), typemax(X))
270+
@test div(eps(X), typemax(X), RoundUp) === cld(eps(X), typemax(X))
271+
end
272+
end
273+
274+
function test_fld1_mod1(TX::Type)
275+
for X in target(TX, :i8, :i16; ex = :thin)
276+
T = rawtype(X)
277+
eps2 = eps(X) + eps(X)
278+
xs = reinterpret.(X, T.((17, 16, 15, 14)))
279+
@test all(fld1.(xs, eps2) .=== T.((9, 8, 8, 7)))
280+
@test_throws DivideError fld1(eps(X), zero(X))
281+
282+
@test all(mod1.(xs, eps2) .=== reinterpret.(X, T.((1, 2, 1, 2))))
283+
@test_throws DivideError mod1(eps(X), zero(X))
284+
285+
d, r = fldmod1(typemin(X), eps2)
286+
@test d isa T && r isa X && ((d - 1) * eps2 + r) % X === typemin(X)
287+
d, r = fldmod1(typemax(X), eps2)
288+
@test d isa T && r isa X && ((d - 1) * eps2 + r) % X === typemax(X)
289+
end
290+
end
291+
237292
function test_isapprox(TX::Type)
238293
@testset "approx $X" for X in target(TX, :i8, :i16; ex = :light)
239294
xs = typemin(X):eps(X):typemax(X)-eps(X)

test/fixed.jl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,56 @@ end
402402
test_fdiv(Fixed)
403403
end
404404

405+
@testset "div/cld/fld" begin
406+
for F in target(Fixed; ex = :thin)
407+
fm, fn, fz, fe = typemax(F), typemin(F), zero(F), eps(F)
408+
T = rawtype(F)
409+
@test wrapping_div(fm, fm) === wrapping_fld(fm, fm) === wrapping_cld(fm, fm) === one(T)
410+
@test saturating_div(fm, fm) === saturating_fld(fm, fm) === saturating_cld(fm, fm) === one(T)
411+
@test checked_div(fm, fm) === checked_fld(fm, fm) === checked_cld(fm, fm) === one(T)
412+
413+
@test wrapping_div(fz, fe) === wrapping_fld(fz, fe) === wrapping_cld(fz, fe) === zero(T)
414+
@test saturating_div(fz, fe) === saturating_fld(fz, fe) === saturating_cld(fz, fe) === zero(T)
415+
@test checked_div(fz, fe) === checked_fld(fz, fe) === checked_cld(fz, fe) === zero(T)
416+
417+
@test wrapping_div(fm, fe) === wrapping_fld(fm, fe) === wrapping_cld(fm, fe) === typemax(T)
418+
@test saturating_div(fm, fe) === saturating_fld(fm, fe) === saturating_cld(fm, fe) === typemax(T)
419+
@test checked_div(fm, fe) === checked_fld(fm, fe) === checked_cld(fm, fe) === typemax(T)
420+
421+
@test wrapping_div(fz, fz) === wrapping_fld(fz, fz) === wrapping_cld(fz, fz) === zero(T)
422+
@test saturating_div(fz, fz) === saturating_fld(fz, fz) === saturating_cld(fz, fz) === zero(T)
423+
@test_throws DivideError checked_div(fz, fz)
424+
@test_throws DivideError checked_fld(fz, fz)
425+
@test_throws DivideError checked_cld(fz, fz)
426+
427+
@test wrapping_div(fe, fz) === wrapping_fld(fe, fz) === wrapping_cld(fe, fz) === zero(T)
428+
@test saturating_div(fe, fz) === saturating_fld(fe, fz) === saturating_cld(fe, fz) === typemax(T)
429+
@test_throws DivideError checked_div(fe, fz)
430+
@test_throws DivideError checked_fld(fe, fz)
431+
@test_throws DivideError checked_cld(fe, fz)
432+
433+
@test wrapping_div(fn, -fe) === wrapping_fld(fn, -fe) === wrapping_cld(fn, -fe) === typemin(T)
434+
@test saturating_div(fn, -fe) === saturating_fld(fn, -fe) === saturating_cld(fn, -fe) === typemax(T)
435+
@test_throws OverflowError checked_div(fn, -fe)
436+
@test_throws OverflowError checked_fld(fn, -fe)
437+
@test_throws OverflowError checked_cld(fn, -fe)
438+
439+
@test wrapping_div(fe, fm) === saturating_div(fe, fm) === checked_div(fe, fm) === zero(T)
440+
@test wrapping_fld(fe, fm) === saturating_fld(fe, fm) === checked_fld(fe, fm) === zero(T)
441+
@test wrapping_cld(fe, fm) === saturating_cld(fe, fm) === checked_cld(fe, fm) === one(T)
442+
443+
@test wrapping_div(fe, fn) === saturating_div(fe, fn) === checked_div(fe, fn) === zero(T)
444+
@test wrapping_fld(fe, fn) === saturating_fld(fe, fn) === checked_fld(fe, fn) === -one(T)
445+
@test wrapping_cld(fe, fn) === saturating_cld(fe, fn) === checked_cld(fe, fn) === zero(T)
446+
end
447+
test_div(Fixed)
448+
test_div_3arg(Fixed)
449+
end
450+
451+
@testset "fld1/mod1" begin
452+
test_fld1_mod1(Fixed)
453+
end
454+
405455
@testset "rounding" begin
406456
for sym in (:i8, :i16, :i32, :i64)
407457
T = symbol_to_inttype(Fixed, sym)

test/normed.jl

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -405,18 +405,49 @@ end
405405
test_fdiv(Normed)
406406
end
407407

408-
@testset "div/fld1" begin
409-
@test div(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == 8
410-
@test div(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == 7
411-
@test fld1(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == 8
412-
@test fld1(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == 8
408+
@testset "div/cld/fld" begin
409+
for N in target(Normed; ex = :thin)
410+
nm, nz, ne = typemax(N), zero(N), eps(N)
411+
T = rawtype(N)
412+
@test wrapping_div(nm, nm) === wrapping_fld(nm, nm) === wrapping_cld(nm, nm) === one(T)
413+
@test saturating_div(nm, nm) === saturating_fld(nm, nm) === saturating_cld(nm, nm) === one(T)
414+
@test checked_div(nm, nm) === checked_fld(nm, nm) === checked_cld(nm, nm) === one(T)
415+
416+
@test wrapping_div(nz, ne) === wrapping_fld(nz, ne) === wrapping_cld(nz, ne) === zero(T)
417+
@test saturating_div(nz, ne) === saturating_fld(nz, ne) === saturating_cld(nz, ne) === zero(T)
418+
@test checked_div(nz, ne) === checked_fld(nz, ne) === checked_cld(nz, ne) === zero(T)
419+
420+
@test wrapping_div(nm, ne) === wrapping_fld(nm, ne) === wrapping_cld(nm, ne) === typemax(T)
421+
@test saturating_div(nm, ne) === saturating_fld(nm, ne) === saturating_cld(nm, ne) === typemax(T)
422+
@test checked_div(nm, ne) === checked_fld(nm, ne) === checked_cld(nm, ne) === typemax(T)
423+
424+
@test wrapping_div(nz, nz) === wrapping_fld(nz, nz) === wrapping_cld(nz, nz) === zero(T)
425+
@test saturating_div(nz, nz) === saturating_fld(nz, nz) === saturating_cld(nz, nz) === zero(T)
426+
@test_throws DivideError checked_div(nz, nz)
427+
@test_throws DivideError checked_fld(nz, nz)
428+
@test_throws DivideError checked_cld(nz, nz)
429+
430+
@test wrapping_div(ne, nz) === wrapping_fld(ne, nz) === wrapping_cld(ne, nz) === zero(T)
431+
@test saturating_div(ne, nz) === saturating_fld(ne, nz) === saturating_cld(ne, nz) === typemax(T)
432+
@test_throws DivideError checked_div(ne, nz)
433+
@test_throws DivideError checked_fld(ne, nz)
434+
@test_throws DivideError checked_cld(ne, nz)
435+
436+
@test wrapping_div(ne, nm) === saturating_div(ne, nm) === checked_div(ne, nm) === zero(T)
437+
@test wrapping_fld(ne, nm) === saturating_fld(ne, nm) === checked_fld(ne, nm) === zero(T)
438+
@test wrapping_cld(ne, nm) === saturating_cld(ne, nm) === checked_cld(ne, nm) === one(T)
439+
end
440+
test_div(Normed)
441+
test_div_3arg(Normed)
413442
end
414443

415444
@testset "rem/mod" begin
416445
@test mod(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == rem(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == 0
417446
@test mod(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == rem(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == reinterpret(N0f8, 0x01)
418-
@test mod1(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == reinterpret(N0f8, 0x02)
419-
@test mod1(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == reinterpret(N0f8, 0x01)
447+
end
448+
449+
@testset "fld1/mod1" begin
450+
test_fld1_mod1(Normed)
420451
end
421452

422453
@testset "rounding" begin

0 commit comments

Comments
 (0)