Skip to content

Commit 65c6363

Browse files
committed
Move arithmetic functions into submodule FixedPointArithmetic
1 parent 2604b5a commit 65c6363

File tree

7 files changed

+437
-305
lines changed

7 files changed

+437
-305
lines changed

src/FixedPointNumbers.jl

Lines changed: 28 additions & 199 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@ import Base: ==, <, <=, -, +, *, /, ~, isapprox,
1111

1212
import Random: Random, AbstractRNG, SamplerType, rand!
1313

14-
import Base.Checked: checked_neg, checked_abs, checked_add, checked_sub, checked_mul,
15-
checked_div, checked_fld, checked_cld, checked_rem, checked_mod
16-
1714
using Base: @pure
1815

1916
"""
@@ -35,14 +32,11 @@ export
3532
# "special" typealiases
3633
# Q and N typealiases are exported in separate source files
3734
# Functions
38-
scaledual,
39-
wrapping_neg, wrapping_abs, wrapping_add, wrapping_sub, wrapping_mul,
40-
wrapping_div, wrapping_fld, wrapping_cld, wrapping_rem, wrapping_mod,
41-
saturating_neg, saturating_abs, saturating_add, saturating_sub, saturating_mul,
42-
saturating_div, saturating_fld, saturating_cld, saturating_rem, saturating_mod,
43-
wrapping_fdiv, saturating_fdiv, checked_fdiv
35+
scaledual
4436

4537
include("utilities.jl")
38+
using .Utilities
39+
import .Utilities: floattype, rawone, nbitsfrac, rawtype, signbits, nbitsint, scaledual
4640

4741
# reinterpretation
4842
reinterpret(x::FixedPoint) = x.i
@@ -57,18 +51,6 @@ rawtype(::Type{X}) where {T, X <: FixedPoint{T}} = T
5751
signbits(::Type{X}) where {T, X <: FixedPoint{T}} = T <: Unsigned ? 0 : 1
5852
nbitsint(::Type{X}) where {X <: FixedPoint} = bitwidth(X) - nbitsfrac(X) - signbits(X)
5953

60-
# construction using the (approximate) intended value, i.e., N0f8
61-
*(x::Real, ::Type{X}) where {X <: FixedPoint} = _convert(X, x)
62-
wrapping_mul(x::Real, ::Type{X}) where {X <: FixedPoint} = x % X
63-
saturating_mul(x::Real, ::Type{X}) where {X <: FixedPoint} = clamp(x, X)
64-
checked_mul(x::Real, ::Type{X}) where {X <: FixedPoint} = _convert(X, x)
65-
66-
# type modulus
67-
rem(x::Real, ::Type{X}) where {X <: FixedPoint} = _rem(x, X)
68-
wrapping_rem(x::Real, ::Type{X}) where {X <: FixedPoint} = _rem(x, X)
69-
saturating_rem(x::Real, ::Type{X}) where {X <: FixedPoint} = _rem(x, X)
70-
checked_rem(x::Real, ::Type{X}) where {X <: FixedPoint} = _rem(x, X)
71-
7254
# constructor-style conversions
7355
(::Type{X})(x::X) where {X <: FixedPoint} = x
7456
(::Type{X})(x::Number) where {X <: FixedPoint} = _convert(X, x)
@@ -139,9 +121,6 @@ zero(::Type{X}) where {X <: FixedPoint} = X(zero(rawtype(X)), 0)
139121
oneunit(::Type{X}) where {X <: FixedPoint} = X(rawone(X), 0)
140122
one(::Type{X}) where {X <: FixedPoint} = oneunit(X)
141123

142-
# for Julia v1.0, which does not fold `div_float` before inlining
143-
inv_rawone(x) = (@generated) ? (y = 1.0 / rawone(x); :($y)) : 1.0 / rawone(x)
144-
145124
# traits
146125
eps(::Type{X}) where {X <: FixedPoint} = X(oneunit(rawtype(X)), 0)
147126
typemax(::Type{T}) where {T <: FixedPoint} = T(typemax(rawtype(T)), 0)
@@ -192,164 +171,12 @@ RGB{Float32}
192171
193172
`RGB` itself is not a subtype of `AbstractFloat`, but unlike `RGB{N0f8}` operations with `RGB{Float32}` are not subject to integer overflow.
194173
"""
195-
floattype(::Type{T}) where {T <: AbstractFloat} = T # fallback (we want a MethodError if no method producing AbstractFloat is defined)
196-
floattype(::Type{T}) where {T <: Union{ShortInts, Bool}} = Float32
197-
floattype(::Type{T}) where {T <: Integer} = Float64
198-
floattype(::Type{T}) where {T <: LongInts} = BigFloat
199-
floattype(::Type{T}) where {I <: Integer, T <: Rational{I}} = typeof(zero(I)/oneunit(I))
200-
floattype(::Type{<:AbstractIrrational}) = Float64
201174
floattype(::Type{X}) where {T <: ShortInts, X <: FixedPoint{T}} = Float32
202175
floattype(::Type{X}) where {T <: Integer, X <: FixedPoint{T}} = Float64
203176
floattype(::Type{X}) where {T <: LongInts, X <: FixedPoint{T}} = BigFloat
204177

205-
# Non-Real types
206-
floattype(::Type{Complex{T}}) where T = Complex{floattype(T)}
207-
floattype(::Type{Base.TwicePrecision{Float64}}) = Float64 # wider would be nice, but hardware support is paramount
208-
floattype(::Type{Base.TwicePrecision{T}}) where T<:Union{Float16,Float32} = widen(T)
209-
210178
float(x::FixedPoint) = convert(floattype(x), x)
211179

212-
# wrapping arithmetic
213-
wrapping_neg(x::X) where {X <: FixedPoint} = X(-x.i, 0)
214-
wrapping_abs(x::X) where {X <: FixedPoint} = X(abs(x.i), 0)
215-
wrapping_add(x::X, y::X) where {X <: FixedPoint} = X(x.i + y.i, 0)
216-
wrapping_sub(x::X, y::X) where {X <: FixedPoint} = X(x.i - y.i, 0)
217-
wrapping_mul(x::X, y::X) where {X <: FixedPoint} = (float(x) * float(y)) % X
218-
function wrapping_fdiv(x::X, y::X) where {X <: FixedPoint}
219-
z = floattype(X)(x.i) / floattype(X)(y.i)
220-
isfinite(z) ? z % X : zero(X)
221-
end
222-
function wrapping_div(x::X, y::X, r::RoundingMode = RoundToZero) where {T, X <: FixedPoint{T}}
223-
z = round(floattype(X)(x.i) / floattype(X)(y.i), r)
224-
isfinite(z) || return zero(T)
225-
if T <: Unsigned
226-
_unsafe_trunc(T, z)
227-
else
228-
z > typemax(T) ? typemin(T) : _unsafe_trunc(T, z)
229-
end
230-
end
231-
wrapping_fld(x::X, y::X) where {X <: FixedPoint} = wrapping_div(x, y, RoundDown)
232-
wrapping_cld(x::X, y::X) where {X <: FixedPoint} = wrapping_div(x, y, RoundUp)
233-
wrapping_rem(x::X, y::X, r::RoundingMode = RoundToZero) where {T, X <: FixedPoint{T}} =
234-
X(x.i - wrapping_div(x, y, r) * y.i, 0)
235-
wrapping_mod(x::X, y::X) where {X <: FixedPoint} = wrapping_rem(x, y, RoundDown)
236-
237-
# saturating arithmetic
238-
saturating_neg(x::X) where {X <: FixedPoint} = X(~min(x.i - true, x.i), 0)
239-
saturating_neg(x::X) where {X <: FixedPoint{<:Unsigned}} = zero(X)
240-
241-
saturating_abs(x::X) where {X <: FixedPoint} =
242-
X(ifelse(signbit(abs(x.i)), typemax(x.i), abs(x.i)), 0)
243-
244-
saturating_add(x::X, y::X) where {X <: FixedPoint} =
245-
X(x.i + ifelse(x.i < 0, max(y.i, typemin(x.i) - x.i), min(y.i, typemax(x.i) - x.i)), 0)
246-
saturating_add(x::X, y::X) where {X <: FixedPoint{<:Unsigned}} = X(x.i + min(~x.i, y.i), 0)
247-
248-
saturating_sub(x::X, y::X) where {X <: FixedPoint} =
249-
X(x.i - ifelse(x.i < 0, min(y.i, x.i - typemin(x.i)), max(y.i, x.i - typemax(x.i))), 0)
250-
saturating_sub(x::X, y::X) where {X <: FixedPoint{<:Unsigned}} = X(x.i - min(x.i, y.i), 0)
251-
252-
saturating_mul(x::X, y::X) where {X <: FixedPoint} = clamp(float(x) * float(y), X)
253-
254-
saturating_fdiv(x::X, y::X) where {X <: FixedPoint} =
255-
clamp(floattype(X)(x.i) / floattype(X)(y.i), X)
256-
257-
function saturating_div(x::X, y::X, r::RoundingMode = RoundToZero) where {T, X <: FixedPoint{T}}
258-
z = round(floattype(X)(x.i) / floattype(X)(y.i), r)
259-
isnan(z) && return zero(T)
260-
if T <: Unsigned
261-
isfinite(z) ? _unsafe_trunc(T, z) : typemax(T)
262-
else
263-
_unsafe_trunc(T, clamp(z, typemin(T), typemax(T)))
264-
end
265-
end
266-
saturating_fld(x::X, y::X) where {X <: FixedPoint} = saturating_div(x, y, RoundDown)
267-
saturating_cld(x::X, y::X) where {X <: FixedPoint} = saturating_div(x, y, RoundUp)
268-
function saturating_rem(x::X, y::X, r::RoundingMode = RoundToZero) where {T, X <: FixedPoint{T}}
269-
T <: Unsigned && r isa RoundingMode{:Up} && return zero(X)
270-
X(x.i - saturating_div(x, y, r) * y.i, 0)
271-
end
272-
saturating_mod(x::X, y::X) where {X <: FixedPoint} = saturating_rem(x, y, RoundDown)
273-
274-
# checked arithmetic
275-
checked_neg(x::X) where {X <: FixedPoint} = checked_sub(zero(X), x)
276-
function checked_abs(x::X) where {X <: FixedPoint}
277-
abs(x.i) >= 0 || throw_overflowerror_abs(x)
278-
X(abs(x.i), 0)
279-
end
280-
function checked_add(x::X, y::X) where {X <: FixedPoint}
281-
r, f = Base.Checked.add_with_overflow(x.i, y.i)
282-
z = X(r, 0) # store first
283-
f && throw_overflowerror(:+, x, y)
284-
z
285-
end
286-
function checked_sub(x::X, y::X) where {X <: FixedPoint}
287-
r, f = Base.Checked.sub_with_overflow(x.i, y.i)
288-
z = X(r, 0) # store first
289-
f && throw_overflowerror(:-, x, y)
290-
z
291-
end
292-
function checked_mul(x::X, y::X) where {X <: FixedPoint}
293-
z = float(x) * float(y)
294-
typemin(X) - eps(X)/2 <= z < typemax(X) + eps(X)/2 || throw_overflowerror(:*, x, y)
295-
z % X
296-
end
297-
function checked_fdiv(x::X, y::X) where {T, X <: FixedPoint{T}}
298-
y === zero(X) && throw(DivideError())
299-
z = floattype(X)(x.i) / floattype(X)(y.i)
300-
if T <: Unsigned
301-
z < typemax(X) + eps(X)/2 || throw_overflowerror(:/, x, y)
302-
else
303-
typemin(X) - eps(X)/2 <= z < typemax(X) + eps(X)/2 || throw_overflowerror(:/, x, y)
304-
end
305-
z % X
306-
end
307-
function checked_div(x::X, y::X, r::RoundingMode = RoundToZero) where {T, X <: FixedPoint{T}}
308-
y === zero(X) && throw(DivideError())
309-
z = round(floattype(X)(x.i) / floattype(X)(y.i), r)
310-
if T <: Signed
311-
z <= typemax(T) || throw_overflowerror_div(r, x, y)
312-
end
313-
_unsafe_trunc(T, z)
314-
end
315-
checked_fld(x::X, y::X) where {X <: FixedPoint} = checked_div(x, y, RoundDown)
316-
checked_cld(x::X, y::X) where {X <: FixedPoint} = checked_div(x, y, RoundUp)
317-
function checked_rem(x::X, y::X, r::RoundingMode = RoundToZero) where {T, X <: FixedPoint{T}}
318-
y === zero(X) && throw(DivideError())
319-
fx, fy = floattype(X)(x.i), floattype(X)(y.i)
320-
z = fx - round(fx / fy, r) * fy
321-
if T <: Unsigned && r isa RoundingMode{:Up}
322-
z >= zero(z) || throw_overflowerror_rem(r, x, y)
323-
end
324-
X(_unsafe_trunc(T, z), 0)
325-
end
326-
checked_mod(x::X, y::X) where {X <: FixedPoint} = checked_rem(x, y, RoundDown)
327-
328-
# default arithmetic
329-
const DEFAULT_ARITHMETIC = :wrapping
330-
331-
for (op, name) in ((:-, :neg), (:abs, :abs))
332-
f = Symbol(DEFAULT_ARITHMETIC, :_, name)
333-
@eval begin
334-
$op(x::X) where {X <: FixedPoint} = $f(x)
335-
end
336-
end
337-
for (op, name) in ((:+, :add), (:-, :sub), (:*, :mul))
338-
f = Symbol(DEFAULT_ARITHMETIC, :_, name)
339-
@eval begin
340-
$op(x::X, y::X) where {X <: FixedPoint} = $f(x, y)
341-
end
342-
end
343-
# force checked arithmetic
344-
/(x::X, y::X) where {X <: FixedPoint} = checked_fdiv(x, y)
345-
div(x::X, y::X, r::RoundingMode = RoundToZero) where {X <: FixedPoint} = checked_div(x, y, r)
346-
fld(x::X, y::X) where {X <: FixedPoint} = checked_div(x, y, RoundDown)
347-
cld(x::X, y::X) where {X <: FixedPoint} = checked_div(x, y, RoundUp)
348-
rem(x::X, y::X) where {X <: FixedPoint} = checked_rem(x, y, RoundToZero)
349-
rem(x::X, y::X, ::RoundingMode{:Down}) where {X <: FixedPoint} = checked_rem(x, y, RoundDown)
350-
rem(x::X, y::X, ::RoundingMode{:Up}) where {X <: FixedPoint} = checked_rem(x, y, RoundUp)
351-
mod(x::X, y::X) where {X <: FixedPoint} = checked_rem(x, y, RoundDown)
352-
353180
function minmax(x::X, y::X) where {X <: FixedPoint}
354181
a, b = minmax(reinterpret(x), reinterpret(y))
355182
X(a,0), X(b,0)
@@ -518,6 +345,31 @@ include("normed.jl")
518345
include("deprecations.jl")
519346
const UF = (N0f8, N6f10, N4f12, N2f14, N0f16)
520347

348+
include("arithmetic/arithmetic.jl")
349+
using .FixedPointArithmetic
350+
# re-export
351+
for name in names(FixedPointArithmetic.Wrapping)
352+
@eval export $name
353+
end
354+
for name in names(FixedPointArithmetic.Saturating)
355+
@eval export $name
356+
end
357+
for name in names(FixedPointArithmetic.Checked)
358+
@eval export $name
359+
end
360+
361+
# construction using the (approximate) intended value, i.e., N0f8
362+
*(x::Real, ::Type{X}) where {X <: FixedPoint} = _convert(X, x)
363+
Wrapping.wrapping_mul(x::Real, ::Type{X}) where {X <: FixedPoint} = x % X
364+
Saturating.saturating_mul(x::Real, ::Type{X}) where {X <: FixedPoint} = clamp(x, X)
365+
Checked.checked_mul(x::Real, ::Type{X}) where {X <: FixedPoint} = _convert(X, x)
366+
367+
# type modulus
368+
rem(x::Real, ::Type{X}) where {X <: FixedPoint} = _rem(x, X)
369+
Wrapping.wrapping_rem(x::Real, ::Type{X}) where {X<:FixedPoint} = _rem(x, X)
370+
Saturating.saturating_rem(x::Real, ::Type{X}) where {X<:FixedPoint} = _rem(x, X)
371+
Checked.checked_rem(x::Real, ::Type{X}) where {X<:FixedPoint} = _rem(x, X)
372+
521373
# Promotions
522374
promote_rule(::Type{X}, ::Type{Tf}) where {X <: FixedPoint, Tf <: AbstractFloat} =
523375
promote_type(floattype(X), Tf)
@@ -585,29 +437,6 @@ scaledual(::Type{Tdual}, x::AbstractArray{T}) where {Tdual, T <: FixedPoint} =
585437
throw(ArgumentError(String(take!(io))))
586438
end
587439

588-
@noinline function throw_overflowerror(op::Symbol, @nospecialize(x), @nospecialize(y))
589-
io = IOBuffer()
590-
print(io, x, ' ', op, ' ', y, " overflowed for type ")
591-
showtype(io, typeof(x))
592-
throw(OverflowError(String(take!(io))))
593-
end
594-
@noinline function throw_overflowerror_abs(@nospecialize(x))
595-
io = IOBuffer()
596-
print(io, "abs(", x, ") overflowed for type ")
597-
showtype(io, typeof(x))
598-
throw(OverflowError(String(take!(io))))
599-
end
600-
@noinline function throw_overflowerror_div(r::RoundingMode, @nospecialize(x), @nospecialize(y))
601-
io = IOBuffer()
602-
op = r === RoundUp ? "cld(" : r === RoundDown ? "fld(" : "div("
603-
print(io, op, x, ", ", y, ") overflowed for type ", rawtype(x))
604-
throw(OverflowError(String(take!(io))))
605-
end
606-
@noinline function throw_overflowerror_rem(r::RoundingMode, @nospecialize(x), @nospecialize(y))
607-
io = IOBuffer()
608-
print(io, "rem(", x, ", ", y, ", ", r, ") overflowed for type ", typeof(x))
609-
throw(OverflowError(String(take!(io))))
610-
end
611440

612441
function Random.rand(r::AbstractRNG, ::SamplerType{X}) where X <: FixedPoint
613442
X(rand(r, rawtype(X)), 0)

0 commit comments

Comments
 (0)