Skip to content

Commit e65efe4

Browse files
committed
Add checked, wrapping and saturating arithmetic for add/sub/neg
The default arithmetic is still the wrapping arithmetic.
1 parent 2f2a34a commit e65efe4

File tree

3 files changed

+147
-4
lines changed

3 files changed

+147
-4
lines changed

src/FixedPointNumbers.jl

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Base: ==, <, <=, -, +, *, /, ~, isapprox,
1010

1111
import Statistics # for _mean_promote
1212

13-
using Base.Checked: checked_add, checked_sub, checked_div
13+
import Base.Checked: checked_neg, checked_add, checked_sub, checked_div
1414

1515
using Base: @pure
1616

@@ -33,7 +33,9 @@ export
3333
# "special" typealiases
3434
# Q and N typealiases are exported in separate source files
3535
# Functions
36-
scaledual
36+
scaledual,
37+
wrapping_neg, wrapping_add, wrapping_sub,
38+
saturating_neg, saturating_add, saturating_sub
3739

3840
include("utilities.jl")
3941

@@ -166,6 +168,47 @@ floattype(::Type{Base.TwicePrecision{T}}) where T<:Union{Float16,Float32} = wide
166168

167169
float(x::FixedPoint) = convert(floattype(x), x)
168170

171+
# wrapping arithmetic
172+
wrapping_neg(x::X) where {X <: FixedPoint} = X(-x.i, 0)
173+
wrapping_add(x::X, y::X) where {X <: FixedPoint} = X(x.i + y.i, 0)
174+
wrapping_sub(x::X, y::X) where {X <: FixedPoint} = X(x.i - y.i, 0)
175+
176+
# saturating arithmetic
177+
function saturating_neg(x::X) where {X <: FixedPoint}
178+
r, f = Base.Checked.sub_with_overflow(zero(X).i, x.i)
179+
ifelse(f, ifelse(x.i < 0, typemax(X), typemin(X)), X(r, 0))
180+
end
181+
function saturating_add(x::X, y::X) where {X <: FixedPoint}
182+
r, f = Base.Checked.add_with_overflow(x.i, y.i)
183+
ifelse(f, ifelse(y.i < 0, typemin(X), typemax(X)), X(r, 0))
184+
end
185+
function saturating_sub(x::X, y::X) where {X <: FixedPoint}
186+
r, f = Base.Checked.sub_with_overflow(x.i, y.i)
187+
ifelse(f, ifelse(y.i < 0, typemax(X), typemin(X)), X(r, 0))
188+
end
189+
190+
# checked arithmetic
191+
checked_neg(x::X) where {X <: FixedPoint} = X(checked_neg(x.i), 0)
192+
checked_add(x::X, y::X) where {X <: FixedPoint} = X(checked_add(x.i, y.i), 0)
193+
checked_sub(x::X, y::X) where {X <: FixedPoint} = X(checked_sub(x.i, y.i), 0)
194+
195+
# default arithmetic
196+
const DEFAULT_ARITHMETIC = :wrapping
197+
198+
for (op, name) in ((:-, :neg), )
199+
f = Symbol(DEFAULT_ARITHMETIC, :_, name)
200+
@eval begin
201+
$op(x::X) where {X <: FixedPoint} = $f(x)
202+
end
203+
end
204+
for (op, name) in ((:+, :add), (:-, :sub))
205+
f = Symbol(DEFAULT_ARITHMETIC, :_, name)
206+
@eval begin
207+
$op(x::X, y::X) where {X <: FixedPoint} = $f(x, y)
208+
end
209+
end
210+
211+
169212
function minmax(x::X, y::X) where {X <: FixedPoint}
170213
a, b = minmax(reinterpret(x), reinterpret(y))
171214
X(a,0), X(b,0)
@@ -183,12 +226,12 @@ for f in (:(==), :<, :<=, :div, :fld, :fld1)
183226
$f(x::X, y::X) where {X <: FixedPoint} = $f(x.i, y.i)
184227
end
185228
end
186-
for f in (:-, :~, :abs)
229+
for f in (:~, :abs)
187230
@eval begin
188231
$f(x::X) where {X <: FixedPoint} = X($f(x.i), 0)
189232
end
190233
end
191-
for f in (:+, :-, :rem, :mod, :mod1, :min, :max)
234+
for f in (:rem, :mod, :mod1, :min, :max)
192235
@eval begin
193236
$f(x::X, y::X) where {X <: FixedPoint} = X($f(x.i, y.i), 0)
194237
end

test/fixed.jl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using FixedPointNumbers, Statistics, Test
22
using FixedPointNumbers: bitwidth
3+
using Base.Checked
34

45
function test_op(fun::F, ::Type{T}, fx, fy, fxf, fyf, tol) where {F,T}
56
# Make sure that the result is representable
@@ -126,6 +127,55 @@ end
126127
end
127128
end
128129

130+
@testset "neg" begin
131+
for F in (Q0f7, Q7f8, Q0f15, Q15f16, Q0f31, Q31f32, Q0f63)
132+
@test wrapping_neg(typemin(F)) === typemin(F)
133+
@test saturating_neg(typemin(F)) === typemax(F)
134+
@test_throws OverflowError checked_neg(typemin(F))
135+
136+
@test wrapping_neg(typemax(F)) === typemin(F) + eps(F)
137+
@test saturating_neg(typemax(F)) === typemin(F) + eps(F)
138+
@test checked_neg(typemax(F)) === typemin(F) + eps(F)
139+
140+
@test wrapping_neg(eps(F)) === zero(F) - eps(F)
141+
@test saturating_neg(eps(F)) === zero(F) - eps(F)
142+
@test checked_neg(eps(F)) === zero(F) - eps(F)
143+
end
144+
end
145+
146+
@testset "add" begin
147+
for F in (Q0f7, Q7f8, Q0f15, Q15f16, Q0f31, Q31f32, Q0f63)
148+
@test wrapping_add(typemin(F), typemin(F)) === zero(F)
149+
@test saturating_add(typemin(F), typemin(F)) === typemin(F)
150+
@test_throws OverflowError checked_add(typemin(F), typemin(F))
151+
152+
@test wrapping_add(typemax(F), eps(F)) === wrapping_add(eps(F), typemax(F)) === typemin(F)
153+
@test saturating_add(typemax(F), eps(F)) === saturating_add(eps(F), typemax(F)) === typemax(F)
154+
@test_throws OverflowError checked_add(typemax(F), eps(F))
155+
@test_throws OverflowError checked_add(eps(F), typemax(F))
156+
157+
@test wrapping_add(zero(F), eps(F)) === wrapping_add(eps(F), zero(F)) === eps(F)
158+
@test saturating_add(zero(F), eps(F)) === saturating_add(eps(F), zero(F)) === eps(F)
159+
@test checked_add(zero(F), eps(F)) === checked_add(eps(F), zero(F)) === eps(F)
160+
end
161+
end
162+
163+
@testset "sub" begin
164+
for F in (Q0f7, Q7f8, Q0f15, Q15f16, Q0f31, Q31f32, Q0f63)
165+
@test wrapping_sub(typemin(F), typemin(F)) === zero(F)
166+
@test saturating_sub(typemin(F), typemin(F)) === zero(F)
167+
@test checked_sub(typemin(F), typemin(F)) === zero(F)
168+
169+
@test wrapping_sub(typemin(F), eps(F)) === typemax(F)
170+
@test saturating_sub(typemin(F), eps(F)) === typemin(F)
171+
@test_throws OverflowError checked_sub(typemin(F), eps(F))
172+
173+
@test wrapping_sub(eps(F), zero(F)) === eps(F)
174+
@test saturating_sub(eps(F), zero(F)) === eps(F)
175+
@test checked_sub(eps(F), zero(F)) === eps(F)
176+
end
177+
end
178+
129179
@testset "rounding" begin
130180
for T in (Int8, Int16, Int32, Int64)
131181
rs = vcat([ oneunit(T) << b - oneunit(T) for b = 0:bitwidth(T)-1],

test/normed.jl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using FixedPointNumbers, Statistics, Test
22
using FixedPointNumbers: bitwidth
3+
using Base.Checked
34

45
@testset "domain of f" begin
56
@test_throws DomainError zero(Normed{UInt8,-1})
@@ -256,6 +257,55 @@ end
256257
end
257258
end
258259

260+
@testset "neg" begin
261+
for N in (N0f8, N8f8, N0f16, N16f16, N0f32, N32f32, N0f64)
262+
@test wrapping_neg(typemin(N)) === zero(N)
263+
@test saturating_neg(typemin(N)) === zero(N)
264+
@test checked_neg(typemin(N)) === zero(N)
265+
266+
@test wrapping_neg(typemax(N)) === eps(N)
267+
@test saturating_neg(typemax(N)) === zero(N)
268+
@test_throws OverflowError checked_neg(typemax(N))
269+
270+
@test wrapping_neg(eps(N)) === typemax(N)
271+
@test saturating_neg(eps(N)) === zero(N)
272+
@test_throws OverflowError checked_neg(eps(N))
273+
end
274+
end
275+
276+
@testset "add" begin
277+
for N in (N0f8, N8f8, N0f16, N16f16, N0f32, N32f32, N0f64)
278+
@test wrapping_add(typemin(N), typemin(N)) === zero(N)
279+
@test saturating_add(typemin(N), typemin(N)) === zero(N)
280+
@test checked_add(typemin(N), typemin(N)) === zero(N)
281+
282+
@test wrapping_add(typemax(N), eps(N)) === wrapping_add(eps(N), typemax(N)) === zero(N)
283+
@test saturating_add(typemax(N), eps(N)) === saturating_add(eps(N), typemax(N)) === typemax(N)
284+
@test_throws OverflowError checked_add(typemax(N), eps(N))
285+
@test_throws OverflowError checked_add(eps(N), typemax(N))
286+
287+
@test wrapping_add(zero(N), eps(N)) === wrapping_add(eps(N), zero(N)) === eps(N)
288+
@test saturating_add(zero(N), eps(N)) === saturating_add(eps(N), zero(N)) === eps(N)
289+
@test checked_add(zero(N), eps(N)) === checked_add(eps(N), zero(N)) === eps(N)
290+
end
291+
end
292+
293+
@testset "sub" begin
294+
for N in (N0f8, N8f8, N0f16, N16f16, N0f32, N32f32, N0f64)
295+
@test wrapping_sub(typemin(N), typemin(N)) === zero(N)
296+
@test saturating_sub(typemin(N), typemin(N)) === zero(N)
297+
@test checked_sub(typemin(N), typemin(N)) === zero(N)
298+
299+
@test wrapping_sub(typemin(N), eps(N)) === typemax(N)
300+
@test saturating_sub(typemin(N), eps(N)) === typemin(N)
301+
@test_throws OverflowError checked_sub(typemin(N), eps(N))
302+
303+
@test wrapping_sub(eps(N), zero(N)) === eps(N)
304+
@test saturating_sub(eps(N), zero(N)) === eps(N)
305+
@test checked_sub(eps(N), zero(N)) === eps(N)
306+
end
307+
end
308+
259309
@testset "rounding" begin
260310
for T in (UInt8, UInt16, UInt32, UInt64)
261311
rs = vcat([ oneunit(T) << b - oneunit(T) << 1 for b = 1:bitwidth(T)],

0 commit comments

Comments
 (0)