Skip to content

Commit 7248e33

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 7248e33

File tree

4 files changed

+144
-4
lines changed

4 files changed

+144
-4
lines changed

src/FixedPointNumbers.jl

Lines changed: 45 additions & 4 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,55 @@ 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+
end
275+
276+
@testset "add" begin
277+
for F in target(Fixed; ex = :thin)
278+
@test wrapping_add(typemin(F), typemin(F)) === zero(F)
279+
@test saturating_add(typemin(F), typemin(F)) === typemin(F)
280+
@test_throws OverflowError checked_add(typemin(F), typemin(F))
281+
282+
@test wrapping_add(typemax(F), eps(F)) === wrapping_add(eps(F), typemax(F)) === typemin(F)
283+
@test saturating_add(typemax(F), eps(F)) === saturating_add(eps(F), typemax(F)) === typemax(F)
284+
@test_throws OverflowError checked_add(typemax(F), eps(F))
285+
@test_throws OverflowError checked_add(eps(F), typemax(F))
286+
287+
@test wrapping_add(zero(F), eps(F)) === wrapping_add(eps(F), zero(F)) === eps(F)
288+
@test saturating_add(zero(F), eps(F)) === saturating_add(eps(F), zero(F)) === eps(F)
289+
@test checked_add(zero(F), eps(F)) === checked_add(eps(F), zero(F)) === eps(F)
290+
end
291+
end
292+
293+
@testset "sub" begin
294+
for F in target(Fixed; ex = :thin)
295+
@test wrapping_sub(typemin(F), typemin(F)) === zero(F)
296+
@test saturating_sub(typemin(F), typemin(F)) === zero(F)
297+
@test checked_sub(typemin(F), typemin(F)) === zero(F)
298+
299+
@test wrapping_sub(typemin(F), eps(F)) === typemax(F)
300+
@test saturating_sub(typemin(F), eps(F)) === typemin(F)
301+
@test_throws OverflowError checked_sub(typemin(F), eps(F))
302+
303+
@test wrapping_sub(eps(F), zero(F)) === eps(F)
304+
@test saturating_sub(eps(F), zero(F)) === eps(F)
305+
@test checked_sub(eps(F), zero(F)) === eps(F)
306+
end
307+
end
308+
260309
@testset "rounding" begin
261310
for sym in (:i8, :i16, :i32, :i64)
262311
T = symbol_to_inttype(Fixed, sym)

test/normed.jl

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,55 @@ 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+
end
302+
303+
@testset "add" begin
304+
for N in target(Normed; ex = :thin)
305+
@test wrapping_add(typemin(N), typemin(N)) === zero(N)
306+
@test saturating_add(typemin(N), typemin(N)) === zero(N)
307+
@test checked_add(typemin(N), typemin(N)) === zero(N)
308+
309+
@test wrapping_add(typemax(N), eps(N)) === wrapping_add(eps(N), typemax(N)) === zero(N)
310+
@test saturating_add(typemax(N), eps(N)) === saturating_add(eps(N), typemax(N)) === typemax(N)
311+
@test_throws OverflowError checked_add(typemax(N), eps(N))
312+
@test_throws OverflowError checked_add(eps(N), typemax(N))
313+
314+
@test wrapping_add(zero(N), eps(N)) === wrapping_add(eps(N), zero(N)) === eps(N)
315+
@test saturating_add(zero(N), eps(N)) === saturating_add(eps(N), zero(N)) === eps(N)
316+
@test checked_add(zero(N), eps(N)) === checked_add(eps(N), zero(N)) === eps(N)
317+
end
318+
end
319+
320+
@testset "sub" begin
321+
for N in target(Normed; ex = :thin)
322+
@test wrapping_sub(typemin(N), typemin(N)) === zero(N)
323+
@test saturating_sub(typemin(N), typemin(N)) === zero(N)
324+
@test checked_sub(typemin(N), typemin(N)) === zero(N)
325+
326+
@test wrapping_sub(typemin(N), eps(N)) === typemax(N)
327+
@test saturating_sub(typemin(N), eps(N)) === typemin(N)
328+
@test_throws OverflowError checked_sub(typemin(N), eps(N))
329+
330+
@test wrapping_sub(eps(N), zero(N)) === eps(N)
331+
@test saturating_sub(eps(N), zero(N)) === eps(N)
332+
@test checked_sub(eps(N), zero(N)) === eps(N)
333+
end
334+
end
335+
287336
@testset "div/fld1" begin
288337
@test div(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x10), reinterpret(N0f8, 0x02)) == 8
289338
@test div(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == fld(reinterpret(N0f8, 0x0f), reinterpret(N0f8, 0x02)) == 7

0 commit comments

Comments
 (0)