Skip to content

Commit 9a10b16

Browse files
committed
Add checked, wrapping and saturating arithmetic for add/sub/neg
The default arithmetic is still the wrapping arithmetic.
1 parent 3e41a6a commit 9a10b16

File tree

4 files changed

+196
-4
lines changed

4 files changed

+196
-4
lines changed

src/FixedPointNumbers.jl

+45-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import Base: ==, <, <=, -, +, *, /, ~, isapprox,
1212
import Statistics # for _mean_promote
1313
import Random: Random, AbstractRNG, SamplerType, rand!
1414

15-
using Base.Checked: checked_add, checked_sub, checked_div
15+
import Base.Checked: checked_neg, checked_add, checked_sub, checked_div
1616

1717
using Base: @pure
1818

@@ -35,7 +35,9 @@ export
3535
# "special" typealiases
3636
# Q and N typealiases are exported in separate source files
3737
# Functions
38-
scaledual
38+
scaledual,
39+
wrapping_neg, wrapping_add, wrapping_sub,
40+
saturating_neg, saturating_add, saturating_sub
3941

4042
include("utilities.jl")
4143

@@ -180,6 +182,45 @@ floattype(::Type{Base.TwicePrecision{T}}) where T<:Union{Float16,Float32} = wide
180182

181183
float(x::FixedPoint) = convert(floattype(x), x)
182184

185+
# wrapping arithmetic
186+
wrapping_neg(x::X) where {X <: FixedPoint} = X(-x.i, 0)
187+
wrapping_add(x::X, y::X) where {X <: FixedPoint} = X(x.i + y.i, 0)
188+
wrapping_sub(x::X, y::X) where {X <: FixedPoint} = X(x.i - y.i, 0)
189+
190+
# saturating arithmetic
191+
saturating_neg(x::X) where {X <: FixedPoint} = X(~min(x.i - true, x.i), 0)
192+
saturating_neg(x::X) where {X <: FixedPoint{<:Unsigned}} = zero(X)
193+
194+
saturating_add(x::X, y::X) where {X <: FixedPoint} =
195+
X(x.i + ifelse(x.i < 0, max(y.i, typemin(x.i) - x.i), min(y.i, typemax(x.i) - x.i)), 0)
196+
saturating_add(x::X, y::X) where {X <: FixedPoint{<:Unsigned}} = X(x.i + min(~x.i, y.i), 0)
197+
198+
saturating_sub(x::X, y::X) where {X <: FixedPoint} =
199+
X(x.i - ifelse(x.i < 0, min(y.i, x.i - typemin(x.i)), max(y.i, x.i - typemax(x.i))), 0)
200+
saturating_sub(x::X, y::X) where {X <: FixedPoint{<:Unsigned}} = X(x.i - min(x.i, y.i), 0)
201+
202+
# checked arithmetic
203+
checked_neg(x::X) where {X <: FixedPoint} = X(checked_neg(x.i), 0)
204+
checked_add(x::X, y::X) where {X <: FixedPoint} = X(checked_add(x.i, y.i), 0)
205+
checked_sub(x::X, y::X) where {X <: FixedPoint} = X(checked_sub(x.i, y.i), 0)
206+
207+
# default arithmetic
208+
const DEFAULT_ARITHMETIC = :wrapping
209+
210+
for (op, name) in ((:-, :neg), )
211+
f = Symbol(DEFAULT_ARITHMETIC, :_, name)
212+
@eval begin
213+
$op(x::X) where {X <: FixedPoint} = $f(x)
214+
end
215+
end
216+
for (op, name) in ((:+, :add), (:-, :sub))
217+
f = Symbol(DEFAULT_ARITHMETIC, :_, name)
218+
@eval begin
219+
$op(x::X, y::X) where {X <: FixedPoint} = $f(x, y)
220+
end
221+
end
222+
223+
183224
function minmax(x::X, y::X) where {X <: FixedPoint}
184225
a, b = minmax(reinterpret(x), reinterpret(y))
185226
X(a,0), X(b,0)
@@ -229,12 +270,12 @@ for f in (:(==), :<, :<=, :div, :fld, :fld1)
229270
$f(x::X, y::X) where {X <: FixedPoint} = $f(x.i, y.i)
230271
end
231272
end
232-
for f in (:-, :~, :abs)
273+
for f in (:~, :abs)
233274
@eval begin
234275
$f(x::X) where {X <: FixedPoint} = X($f(x.i), 0)
235276
end
236277
end
237-
for f in (:+, :-, :rem, :mod, :mod1, :min, :max)
278+
for f in (:rem, :mod, :mod1, :min, :max)
238279
@eval begin
239280
$f(x::X, y::X) where {X <: FixedPoint} = X($f(x.i, y.i), 0)
240281
end

test/common.jl

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using FixedPointNumbers, Statistics, Random, Test
22
using FixedPointNumbers: bitwidth, rawtype, nbitsfrac
3+
using Base.Checked
34

45
"""
56
target(X::Type, Ss...; ex = :default)

test/fixed.jl

+75
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,81 @@ end
257257
@test (-67.2 % T).i == round(Int, -67.2*512) % Int16
258258
end
259259

260+
@testset "neg" begin
261+
for F in target(Fixed; ex = :thin)
262+
@test wrapping_neg(typemin(F)) === typemin(F)
263+
@test saturating_neg(typemin(F)) === typemax(F)
264+
@test_throws OverflowError checked_neg(typemin(F))
265+
266+
@test wrapping_neg(typemax(F)) === typemin(F) + eps(F)
267+
@test saturating_neg(typemax(F)) === typemin(F) + eps(F)
268+
@test checked_neg(typemax(F)) === typemin(F) + eps(F)
269+
270+
@test wrapping_neg(eps(F)) === zero(F) - eps(F)
271+
@test saturating_neg(eps(F)) === zero(F) - eps(F)
272+
@test checked_neg(eps(F)) === zero(F) - eps(F)
273+
end
274+
for F in target(Fixed, :i8; ex = :thin)
275+
xs = typemin(F):eps(F):typemax(F)
276+
fneg(x) = -float(x)
277+
@test all(x -> wrapping_neg(wrapping_neg(x)) === x, xs)
278+
@test all(x -> saturating_neg(x) == clamp(fneg(x), F), xs)
279+
@test all(x -> !(typemin(F) < fneg(x) < typemax(F)) ||
280+
wrapping_neg(x) === checked_neg(x) === fneg(x) % F, xs)
281+
end
282+
end
283+
284+
@testset "add" begin
285+
for F in target(Fixed; ex = :thin)
286+
@test wrapping_add(typemin(F), typemin(F)) === zero(F)
287+
@test saturating_add(typemin(F), typemin(F)) === typemin(F)
288+
@test_throws OverflowError checked_add(typemin(F), typemin(F))
289+
290+
@test wrapping_add(typemax(F), eps(F)) === wrapping_add(eps(F), typemax(F)) === typemin(F)
291+
@test saturating_add(typemax(F), eps(F)) === saturating_add(eps(F), typemax(F)) === typemax(F)
292+
@test_throws OverflowError checked_add(typemax(F), eps(F))
293+
@test_throws OverflowError checked_add(eps(F), typemax(F))
294+
295+
@test wrapping_add(zero(F), eps(F)) === wrapping_add(eps(F), zero(F)) === eps(F)
296+
@test saturating_add(zero(F), eps(F)) === saturating_add(eps(F), zero(F)) === eps(F)
297+
@test checked_add(zero(F), eps(F)) === checked_add(eps(F), zero(F)) === eps(F)
298+
end
299+
for F in target(Fixed, :i8; ex = :thin)
300+
xs = typemin(F):eps(F):typemax(F)
301+
xys = ((x, y) for x in xs, y in xs)
302+
fadd(x, y) = float(x) + float(y)
303+
@test all(((x, y),) -> wrapping_sub(wrapping_add(x, y), y) === x, xys)
304+
@test all(((x, y),) -> saturating_add(x, y) == clamp(fadd(x, y), F), xys)
305+
@test all(((x, y),) -> !(typemin(F) < fadd(x, y) < typemax(F)) ||
306+
wrapping_add(x, y) === checked_add(x, y) === fadd(x, y) % F, xys)
307+
end
308+
end
309+
310+
@testset "sub" begin
311+
for F in target(Fixed; ex = :thin)
312+
@test wrapping_sub(typemin(F), typemin(F)) === zero(F)
313+
@test saturating_sub(typemin(F), typemin(F)) === zero(F)
314+
@test checked_sub(typemin(F), typemin(F)) === zero(F)
315+
316+
@test wrapping_sub(typemin(F), eps(F)) === typemax(F)
317+
@test saturating_sub(typemin(F), eps(F)) === typemin(F)
318+
@test_throws OverflowError checked_sub(typemin(F), eps(F))
319+
320+
@test wrapping_sub(eps(F), zero(F)) === eps(F)
321+
@test saturating_sub(eps(F), zero(F)) === eps(F)
322+
@test checked_sub(eps(F), zero(F)) === eps(F)
323+
end
324+
for F in target(Fixed, :i8; ex = :thin)
325+
xs = typemin(F):eps(F):typemax(F)
326+
xys = ((x, y) for x in xs, y in xs)
327+
fsub(x, y) = float(x) - float(y)
328+
@test all(((x, y),) -> wrapping_add(wrapping_sub(x, y), y) === x, xys)
329+
@test all(((x, y),) -> saturating_sub(x, y) == clamp(fsub(x, y), F), xys)
330+
@test all(((x, y),) -> !(typemin(F) < fsub(x, y) < typemax(F)) ||
331+
wrapping_sub(x, y) === checked_sub(x, y) === fsub(x, y) % F, xys)
332+
end
333+
end
334+
260335
@testset "rounding" begin
261336
for sym in (:i8, :i16, :i32, :i64)
262337
T = symbol_to_inttype(Fixed, sym)

test/normed.jl

+75
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,81 @@ end
284284
end
285285
end
286286

287+
@testset "neg" begin
288+
for N in target(Normed; ex = :thin)
289+
@test wrapping_neg(typemin(N)) === zero(N)
290+
@test saturating_neg(typemin(N)) === zero(N)
291+
@test checked_neg(typemin(N)) === zero(N)
292+
293+
@test wrapping_neg(typemax(N)) === eps(N)
294+
@test saturating_neg(typemax(N)) === zero(N)
295+
@test_throws OverflowError checked_neg(typemax(N))
296+
297+
@test wrapping_neg(eps(N)) === typemax(N)
298+
@test saturating_neg(eps(N)) === zero(N)
299+
@test_throws OverflowError checked_neg(eps(N))
300+
end
301+
for N in target(Normed, :i8; ex = :thin)
302+
xs = typemin(N):eps(N):typemax(N)
303+
fneg(x) = -float(x)
304+
@test all(x -> wrapping_neg(wrapping_neg(x)) === x, xs)
305+
@test all(x -> saturating_neg(x) == clamp(fneg(x), N), xs)
306+
@test all(x -> !(typemin(N) < fneg(x) < typemax(N)) ||
307+
wrapping_neg(x) === checked_neg(x) === fneg(x) % N, xs)
308+
end
309+
end
310+
311+
@testset "add" begin
312+
for N in target(Normed; ex = :thin)
313+
@test wrapping_add(typemin(N), typemin(N)) === zero(N)
314+
@test saturating_add(typemin(N), typemin(N)) === zero(N)
315+
@test checked_add(typemin(N), typemin(N)) === zero(N)
316+
317+
@test wrapping_add(typemax(N), eps(N)) === wrapping_add(eps(N), typemax(N)) === zero(N)
318+
@test saturating_add(typemax(N), eps(N)) === saturating_add(eps(N), typemax(N)) === typemax(N)
319+
@test_throws OverflowError checked_add(typemax(N), eps(N))
320+
@test_throws OverflowError checked_add(eps(N), typemax(N))
321+
322+
@test wrapping_add(zero(N), eps(N)) === wrapping_add(eps(N), zero(N)) === eps(N)
323+
@test saturating_add(zero(N), eps(N)) === saturating_add(eps(N), zero(N)) === eps(N)
324+
@test checked_add(zero(N), eps(N)) === checked_add(eps(N), zero(N)) === eps(N)
325+
end
326+
for N in target(Normed, :i8; ex = :thin)
327+
xs = typemin(N):eps(N):typemax(N)
328+
xys = ((x, y) for x in xs, y in xs)
329+
fadd(x, y) = float(x) + float(y)
330+
@test all(((x, y),) -> wrapping_sub(wrapping_add(x, y), y) === x, xys)
331+
@test all(((x, y),) -> saturating_add(x, y) == clamp(fadd(x, y), N), xys)
332+
@test all(((x, y),) -> !(typemin(N) < fadd(x, y) < typemax(N)) ||
333+
wrapping_add(x, y) === checked_add(x, y) === fadd(x, y) % N, xys)
334+
end
335+
end
336+
337+
@testset "sub" begin
338+
for N in target(Normed; ex = :thin)
339+
@test wrapping_sub(typemin(N), typemin(N)) === zero(N)
340+
@test saturating_sub(typemin(N), typemin(N)) === zero(N)
341+
@test checked_sub(typemin(N), typemin(N)) === zero(N)
342+
343+
@test wrapping_sub(typemin(N), eps(N)) === typemax(N)
344+
@test saturating_sub(typemin(N), eps(N)) === typemin(N)
345+
@test_throws OverflowError checked_sub(typemin(N), eps(N))
346+
347+
@test wrapping_sub(eps(N), zero(N)) === eps(N)
348+
@test saturating_sub(eps(N), zero(N)) === eps(N)
349+
@test checked_sub(eps(N), zero(N)) === eps(N)
350+
end
351+
for N in target(Normed, :i8; ex = :thin)
352+
xs = typemin(N):eps(N):typemax(N)
353+
xys = ((x, y) for x in xs, y in xs)
354+
fsub(x, y) = float(x) - float(y)
355+
@test all(((x, y),) -> wrapping_add(wrapping_sub(x, y), y) === x, xys)
356+
@test all(((x, y),) -> saturating_sub(x, y) == clamp(fsub(x, y), N), xys)
357+
@test all(((x, y),) -> !(typemin(N) < fsub(x, y) < typemax(N)) ||
358+
wrapping_sub(x, y) === checked_sub(x, y) === fsub(x, y) % N, xys)
359+
end
360+
end
361+
287362
@testset "div/fld1" begin
288363
@test div(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == 8
289364
@test div(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == 7

0 commit comments

Comments
 (0)