Skip to content

Commit 544c346

Browse files
committed
Add checked, wrapping and saturating arithmetic for add/sub/neg
The default arithmetic is still the wrapping arithmetic.
1 parent 33f2583 commit 544c346

File tree

3 files changed

+149
-4
lines changed

3 files changed

+149
-4
lines changed

src/FixedPointNumbers.jl

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

1212
import Statistics # for _mean_promote
1313

14-
using Base.Checked: checked_add, checked_sub, checked_div
14+
import Base.Checked: checked_neg, checked_add, checked_sub, checked_div
1515

1616
using Base: @pure
1717

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

3941
include("utilities.jl")
4042

@@ -179,6 +181,49 @@ floattype(::Type{Base.TwicePrecision{T}}) where T<:Union{Float16,Float32} = wide
179181

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

184+
# wrapping arithmetic
185+
wrapping_neg(x::X) where {X <: FixedPoint} = X(-x.i, 0)
186+
wrapping_add(x::X, y::X) where {X <: FixedPoint} = X(x.i + y.i, 0)
187+
wrapping_sub(x::X, y::X) where {X <: FixedPoint} = X(x.i - y.i, 0)
188+
189+
# saturating arithmetic
190+
saturating_neg(x::X) where {X <: FixedPoint} = X(~min(x.i - true, x.i), 0)
191+
saturating_neg(x::X) where {X <: FixedPoint{<:Unsigned}} = zero(X)
192+
193+
function saturating_add(x::X, y::X) where {X <: FixedPoint}
194+
r, f = Base.Checked.add_with_overflow(x.i, y.i)
195+
ifelse(f, ifelse(y.i < 0, typemin(X), typemax(X)), X(r, 0))
196+
end
197+
saturating_add(x::X, y::X) where {X <: FixedPoint{<:Unsigned}} = X(x.i + min(~x.i, y.i), 0)
198+
199+
function saturating_sub(x::X, y::X) where {X <: FixedPoint}
200+
r, f = Base.Checked.sub_with_overflow(x.i, y.i)
201+
ifelse(f, ifelse(y.i < 0, typemax(X), typemin(X)), X(r, 0))
202+
end
203+
saturating_sub(x::X, y::X) where {X <: FixedPoint{<:Unsigned}} = X(x.i - min(x.i, y.i), 0)
204+
205+
# checked arithmetic
206+
checked_neg(x::X) where {X <: FixedPoint} = X(checked_neg(x.i), 0)
207+
checked_add(x::X, y::X) where {X <: FixedPoint} = X(checked_add(x.i, y.i), 0)
208+
checked_sub(x::X, y::X) where {X <: FixedPoint} = X(checked_sub(x.i, y.i), 0)
209+
210+
# default arithmetic
211+
const DEFAULT_ARITHMETIC = :wrapping
212+
213+
for (op, name) in ((:-, :neg), )
214+
f = Symbol(DEFAULT_ARITHMETIC, :_, name)
215+
@eval begin
216+
$op(x::X) where {X <: FixedPoint} = $f(x)
217+
end
218+
end
219+
for (op, name) in ((:+, :add), (:-, :sub))
220+
f = Symbol(DEFAULT_ARITHMETIC, :_, name)
221+
@eval begin
222+
$op(x::X, y::X) where {X <: FixedPoint} = $f(x, y)
223+
end
224+
end
225+
226+
182227
function minmax(x::X, y::X) where {X <: FixedPoint}
183228
a, b = minmax(reinterpret(x), reinterpret(y))
184229
X(a,0), X(b,0)
@@ -228,12 +273,12 @@ for f in (:(==), :<, :<=, :div, :fld, :fld1)
228273
$f(x::X, y::X) where {X <: FixedPoint} = $f(x.i, y.i)
229274
end
230275
end
231-
for f in (:-, :~, :abs)
276+
for f in (:~, :abs)
232277
@eval begin
233278
$f(x::X) where {X <: FixedPoint} = X($f(x.i), 0)
234279
end
235280
end
236-
for f in (:+, :-, :rem, :mod, :mod1, :min, :max)
281+
for f in (:rem, :mod, :mod1, :min, :max)
237282
@eval begin
238283
$f(x::X, y::X) where {X <: FixedPoint} = X($f(x.i, y.i), 0)
239284
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
@@ -134,6 +135,55 @@ end
134135
end
135136
end
136137

138+
@testset "neg" begin
139+
for F in (Q0f7, Q7f8, Q0f15, Q15f16, Q0f31, Q31f32, Q0f63)
140+
@test wrapping_neg(typemin(F)) === typemin(F)
141+
@test saturating_neg(typemin(F)) === typemax(F)
142+
@test_throws OverflowError checked_neg(typemin(F))
143+
144+
@test wrapping_neg(typemax(F)) === typemin(F) + eps(F)
145+
@test saturating_neg(typemax(F)) === typemin(F) + eps(F)
146+
@test checked_neg(typemax(F)) === typemin(F) + eps(F)
147+
148+
@test wrapping_neg(eps(F)) === zero(F) - eps(F)
149+
@test saturating_neg(eps(F)) === zero(F) - eps(F)
150+
@test checked_neg(eps(F)) === zero(F) - eps(F)
151+
end
152+
end
153+
154+
@testset "add" begin
155+
for F in (Q0f7, Q7f8, Q0f15, Q15f16, Q0f31, Q31f32, Q0f63)
156+
@test wrapping_add(typemin(F), typemin(F)) === zero(F)
157+
@test saturating_add(typemin(F), typemin(F)) === typemin(F)
158+
@test_throws OverflowError checked_add(typemin(F), typemin(F))
159+
160+
@test wrapping_add(typemax(F), eps(F)) === wrapping_add(eps(F), typemax(F)) === typemin(F)
161+
@test saturating_add(typemax(F), eps(F)) === saturating_add(eps(F), typemax(F)) === typemax(F)
162+
@test_throws OverflowError checked_add(typemax(F), eps(F))
163+
@test_throws OverflowError checked_add(eps(F), typemax(F))
164+
165+
@test wrapping_add(zero(F), eps(F)) === wrapping_add(eps(F), zero(F)) === eps(F)
166+
@test saturating_add(zero(F), eps(F)) === saturating_add(eps(F), zero(F)) === eps(F)
167+
@test checked_add(zero(F), eps(F)) === checked_add(eps(F), zero(F)) === eps(F)
168+
end
169+
end
170+
171+
@testset "sub" begin
172+
for F in (Q0f7, Q7f8, Q0f15, Q15f16, Q0f31, Q31f32, Q0f63)
173+
@test wrapping_sub(typemin(F), typemin(F)) === zero(F)
174+
@test saturating_sub(typemin(F), typemin(F)) === zero(F)
175+
@test checked_sub(typemin(F), typemin(F)) === zero(F)
176+
177+
@test wrapping_sub(typemin(F), eps(F)) === typemax(F)
178+
@test saturating_sub(typemin(F), eps(F)) === typemin(F)
179+
@test_throws OverflowError checked_sub(typemin(F), eps(F))
180+
181+
@test wrapping_sub(eps(F), zero(F)) === eps(F)
182+
@test saturating_sub(eps(F), zero(F)) === eps(F)
183+
@test checked_sub(eps(F), zero(F)) === eps(F)
184+
end
185+
end
186+
137187
@testset "rounding" begin
138188
for T in (Int8, Int16, Int32, Int64)
139189
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})
@@ -282,6 +283,55 @@ end
282283
end
283284
end
284285

286+
@testset "neg" begin
287+
for N in (N0f8, N8f8, N0f16, N16f16, N0f32, N32f32, N0f64)
288+
@test wrapping_neg(typemin(N)) === zero(N)
289+
@test saturating_neg(typemin(N)) === zero(N)
290+
@test checked_neg(typemin(N)) === zero(N)
291+
292+
@test wrapping_neg(typemax(N)) === eps(N)
293+
@test saturating_neg(typemax(N)) === zero(N)
294+
@test_throws OverflowError checked_neg(typemax(N))
295+
296+
@test wrapping_neg(eps(N)) === typemax(N)
297+
@test saturating_neg(eps(N)) === zero(N)
298+
@test_throws OverflowError checked_neg(eps(N))
299+
end
300+
end
301+
302+
@testset "add" begin
303+
for N in (N0f8, N8f8, N0f16, N16f16, N0f32, N32f32, N0f64)
304+
@test wrapping_add(typemin(N), typemin(N)) === zero(N)
305+
@test saturating_add(typemin(N), typemin(N)) === zero(N)
306+
@test checked_add(typemin(N), typemin(N)) === zero(N)
307+
308+
@test wrapping_add(typemax(N), eps(N)) === wrapping_add(eps(N), typemax(N)) === zero(N)
309+
@test saturating_add(typemax(N), eps(N)) === saturating_add(eps(N), typemax(N)) === typemax(N)
310+
@test_throws OverflowError checked_add(typemax(N), eps(N))
311+
@test_throws OverflowError checked_add(eps(N), typemax(N))
312+
313+
@test wrapping_add(zero(N), eps(N)) === wrapping_add(eps(N), zero(N)) === eps(N)
314+
@test saturating_add(zero(N), eps(N)) === saturating_add(eps(N), zero(N)) === eps(N)
315+
@test checked_add(zero(N), eps(N)) === checked_add(eps(N), zero(N)) === eps(N)
316+
end
317+
end
318+
319+
@testset "sub" begin
320+
for N in (N0f8, N8f8, N0f16, N16f16, N0f32, N32f32, N0f64)
321+
@test wrapping_sub(typemin(N), typemin(N)) === zero(N)
322+
@test saturating_sub(typemin(N), typemin(N)) === zero(N)
323+
@test checked_sub(typemin(N), typemin(N)) === zero(N)
324+
325+
@test wrapping_sub(typemin(N), eps(N)) === typemax(N)
326+
@test saturating_sub(typemin(N), eps(N)) === typemin(N)
327+
@test_throws OverflowError checked_sub(typemin(N), eps(N))
328+
329+
@test wrapping_sub(eps(N), zero(N)) === eps(N)
330+
@test saturating_sub(eps(N), zero(N)) === eps(N)
331+
@test checked_sub(eps(N), zero(N)) === eps(N)
332+
end
333+
end
334+
285335
@testset "rounding" begin
286336
for T in (UInt8, UInt16, UInt32, UInt64)
287337
rs = vcat([ oneunit(T) << b - oneunit(T) << 1 for b = 1:bitwidth(T)],

0 commit comments

Comments
 (0)