Skip to content

Commit 32f7463

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 32f7463

File tree

4 files changed

+148
-4
lines changed

4 files changed

+148
-4
lines changed

src/FixedPointNumbers.jl

Lines changed: 49 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,49 @@ 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+
function saturating_add(x::X, y::X) where {X <: FixedPoint}
195+
r, f = Base.Checked.add_with_overflow(x.i, y.i)
196+
ifelse(f, ifelse(y.i < 0, typemin(X), typemax(X)), X(r, 0))
197+
end
198+
saturating_add(x::X, y::X) where {X <: FixedPoint{<:Unsigned}} = X(x.i + min(~x.i, y.i), 0)
199+
200+
function saturating_sub(x::X, y::X) where {X <: FixedPoint}
201+
r, f = Base.Checked.sub_with_overflow(x.i, y.i)
202+
ifelse(f, ifelse(y.i < 0, typemax(X), typemin(X)), X(r, 0))
203+
end
204+
saturating_sub(x::X, y::X) where {X <: FixedPoint{<:Unsigned}} = X(x.i - min(x.i, y.i), 0)
205+
206+
# checked arithmetic
207+
checked_neg(x::X) where {X <: FixedPoint} = X(checked_neg(x.i), 0)
208+
checked_add(x::X, y::X) where {X <: FixedPoint} = X(checked_add(x.i, y.i), 0)
209+
checked_sub(x::X, y::X) where {X <: FixedPoint} = X(checked_sub(x.i, y.i), 0)
210+
211+
# default arithmetic
212+
const DEFAULT_ARITHMETIC = :wrapping
213+
214+
for (op, name) in ((:-, :neg), )
215+
f = Symbol(DEFAULT_ARITHMETIC, :_, name)
216+
@eval begin
217+
$op(x::X) where {X <: FixedPoint} = $f(x)
218+
end
219+
end
220+
for (op, name) in ((:+, :add), (:-, :sub))
221+
f = Symbol(DEFAULT_ARITHMETIC, :_, name)
222+
@eval begin
223+
$op(x::X, y::X) where {X <: FixedPoint} = $f(x, y)
224+
end
225+
end
226+
227+
183228
function minmax(x::X, y::X) where {X <: FixedPoint}
184229
a, b = minmax(reinterpret(x), reinterpret(y))
185230
X(a,0), X(b,0)
@@ -229,12 +274,12 @@ for f in (:(==), :<, :<=, :div, :fld, :fld1)
229274
$f(x::X, y::X) where {X <: FixedPoint} = $f(x.i, y.i)
230275
end
231276
end
232-
for f in (:-, :~, :abs)
277+
for f in (:~, :abs)
233278
@eval begin
234279
$f(x::X) where {X <: FixedPoint} = X($f(x.i), 0)
235280
end
236281
end
237-
for f in (:+, :-, :rem, :mod, :mod1, :min, :max)
282+
for f in (:rem, :mod, :mod1, :min, :max)
238283
@eval begin
239284
$f(x::X, y::X) where {X <: FixedPoint} = X($f(x.i, y.i), 0)
240285
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)