Skip to content

Commit dc0d4d3

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

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

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

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

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

133+
@testset "neg" begin
134+
for F in (Q0f7, Q7f8, Q0f15, Q15f16, Q0f31, Q31f32, Q0f63)
135+
@test wrapping_neg(typemin(F)) === typemin(F)
136+
@test saturating_neg(typemin(F)) === typemax(F)
137+
@test_throws OverflowError checked_neg(typemin(F))
138+
139+
@test wrapping_neg(typemax(F)) === typemin(F) + eps(F)
140+
@test saturating_neg(typemax(F)) === typemin(F) + eps(F)
141+
@test checked_neg(typemax(F)) === typemin(F) + eps(F)
142+
143+
@test wrapping_neg(eps(F)) === zero(F) - eps(F)
144+
@test saturating_neg(eps(F)) === zero(F) - eps(F)
145+
@test checked_neg(eps(F)) === zero(F) - eps(F)
146+
end
147+
end
148+
149+
@testset "add" begin
150+
for F in (Q0f7, Q7f8, Q0f15, Q15f16, Q0f31, Q31f32, Q0f63)
151+
@test wrapping_add(typemin(F), typemin(F)) === zero(F)
152+
@test saturating_add(typemin(F), typemin(F)) === typemin(F)
153+
@test_throws OverflowError checked_add(typemin(F), typemin(F))
154+
155+
@test wrapping_add(typemax(F), eps(F)) === wrapping_add(eps(F), typemax(F)) === typemin(F)
156+
@test saturating_add(typemax(F), eps(F)) === saturating_add(eps(F), typemax(F)) === typemax(F)
157+
@test_throws OverflowError checked_add(typemax(F), eps(F))
158+
@test_throws OverflowError checked_add(eps(F), typemax(F))
159+
160+
@test wrapping_add(zero(F), eps(F)) === wrapping_add(eps(F), zero(F)) === eps(F)
161+
@test saturating_add(zero(F), eps(F)) === saturating_add(eps(F), zero(F)) === eps(F)
162+
@test checked_add(zero(F), eps(F)) === checked_add(eps(F), zero(F)) === eps(F)
163+
end
164+
end
165+
166+
@testset "sub" begin
167+
for F in (Q0f7, Q7f8, Q0f15, Q15f16, Q0f31, Q31f32, Q0f63)
168+
@test wrapping_sub(typemin(F), typemin(F)) === zero(F)
169+
@test saturating_sub(typemin(F), typemin(F)) === zero(F)
170+
@test checked_sub(typemin(F), typemin(F)) === zero(F)
171+
172+
@test wrapping_sub(typemin(F), eps(F)) === typemax(F)
173+
@test saturating_sub(typemin(F), eps(F)) === typemin(F)
174+
@test_throws OverflowError checked_sub(typemin(F), eps(F))
175+
176+
@test wrapping_sub(eps(F), zero(F)) === eps(F)
177+
@test saturating_sub(eps(F), zero(F)) === eps(F)
178+
@test checked_sub(eps(F), zero(F)) === eps(F)
179+
end
180+
end
181+
132182
@testset "rounding" begin
133183
for T in (Int8, Int16, Int32, Int64)
134184
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})
@@ -276,6 +277,55 @@ end
276277
end
277278
end
278279

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

0 commit comments

Comments
 (0)