Skip to content

Commit 9ca0d9d

Browse files
authored
Specialize multiplication for Fixed (#220)
This specializes most of the multiplication for `Fixed` and avoids floating point operations. A major change is that the rounding mode is changed from `RoundNearestTiesUp` to `RoundNearest`. The existing `RoundNearestTiesUp` and `RoundDown` modes are now supported by the new unexported function `mul_with_rounding`. This also improves `rem`. Unlike multiplication for `Normed`, the wrapping arithmetic is the default for `Fixed`.
1 parent 134646f commit 9ca0d9d

File tree

3 files changed

+84
-18
lines changed

3 files changed

+84
-18
lines changed

src/fixed.jl

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -95,19 +95,20 @@ function _convert(::Type{F}, x::Rational) where {T, f, F <: Fixed{T,f}}
9595
end
9696
end
9797

98-
# unchecked arithmetic
99-
100-
# with truncation:
101-
#*(x::Fixed{T,f}, y::Fixed{T,f}) = Fixed{T,f}(Base.widemul(x.i,y.i)>>f,0)
102-
# with rounding up:
103-
*(x::Fixed{T,f}, y::Fixed{T,f}) where {T,f} = Fixed{T,f}((Base.widemul(x.i,y.i) + (one(widen(T)) << (f-1)))>>f,0)
104-
105-
/(x::Fixed{T,f}, y::Fixed{T,f}) where {T,f} = Fixed{T,f}(div(convert(widen(T), x.i) << f, y.i), 0)
106-
107-
108-
rem(x::Integer, ::Type{Fixed{T,f}}) where {T,f} = Fixed{T,f}(rem(x,T)<<f,0)
109-
rem(x::Real, ::Type{Fixed{T,f}}) where {T,f} = Fixed{T,f}(rem(Integer(trunc(x)),T)<<f + rem(Integer(round(rem(x,1)*(one(widen1(T))<<f))),T),0)
110-
98+
rem(x::F, ::Type{F}) where {F <: Fixed} = x
99+
function rem(x::Fixed, ::Type{F}) where {T, f, F <: Fixed{T,f}}
100+
f2 = nbitsfrac(typeof(x))
101+
y = round(@exp2(f - f2) * reinterpret(x))
102+
reinterpret(F, _unsafe_trunc(T, y))
103+
end
104+
rem(x::Integer, ::Type{F}) where {T, f, F <: Fixed{T,f}} = F(_unsafe_trunc(T, x) << f, 0)
105+
function rem(x::Real, ::Type{F}) where {T, f, F <: Fixed{T,f}}
106+
y = _unsafe_trunc(promote_type(Int64, T), round(x * @exp2(f)))
107+
reinterpret(F, _unsafe_trunc(T, y))
108+
end
109+
function rem(x::BigFloat, ::Type{F}) where {T, f, F <: Fixed{T,f}}
110+
reinterpret(F, _unsafe_trunc(T, round(x * @exp2(f))))
111+
end
111112

112113
(::Type{Tf})(x::Fixed{T,f}) where {Tf <: AbstractFloat, T, f} = Tf(Tf(x.i) * Tf(@exp2(-f)))
113114
Base.Float16(x::Fixed{T,f}) where {T, f} = Float16(Float32(x))
@@ -118,6 +119,60 @@ function Base.Rational(x::Fixed{T,f}) where {T, f}
118119
f < bitwidth(T)-1 ? x.i//rawone(x) : x.i//(one(widen1(T))<<f)
119120
end
120121

122+
function div_2f(x::T, ::Val{f}) where {T, f}
123+
xf = x & (T(-1) >>> (bitwidth(T) - f - 1))
124+
half = oneunit(T) << (f - 1)
125+
c = half - (xf === half)
126+
(x + c) >> f
127+
end
128+
div_2f(x::T, ::Val{0}) where {T} = x
129+
130+
# wrapping arithmetic
131+
function wrapping_mul(x::F, y::F) where {T <: Union{Int8,Int16,Int32,Int64}, f, F <: Fixed{T,f}}
132+
z = widemul(x.i, y.i)
133+
F(div_2f(z, Val(Int(f))) % T, 0)
134+
end
135+
136+
# saturating arithmetic
137+
function saturating_mul(x::F, y::F) where {T <: Union{Int8,Int16,Int32,Int64}, f, F <: Fixed{T,f}}
138+
if f >= bitwidth(T) - 1
139+
z = min(widemul(x.i, y.i), widen1(typemax(T)) << f)
140+
else
141+
z = clamp(widemul(x.i, y.i), widen1(typemin(T)) << f, widen1(typemax(T)) << f)
142+
end
143+
F(div_2f(z, Val(Int(f))) % T, 0)
144+
end
145+
146+
# checked arithmetic
147+
function checked_mul(x::F, y::F) where {T <: Union{Int8,Int16,Int32,Int64}, f, F <: Fixed{T,f}}
148+
z = widemul(x.i, y.i)
149+
if f < 1
150+
m = widen1(typemax(T)) + 0x1
151+
n = widen1(typemin(T))
152+
else
153+
half = widen1(oneunit(T)) << (f - 1)
154+
m = widen1(typemax(T)) << f + half
155+
n = widen1(typemin(T)) << f - half
156+
end
157+
(n <= z) & (z < m) || throw_overflowerror(:*, x, y)
158+
F(div_2f(z, Val(Int(f))) % T, 0)
159+
end
160+
161+
function mul_with_rounding(x::F, y::F, ::RoundingMode{:Nearest}) where {F <: Fixed}
162+
wrapping_mul(x, y)
163+
end
164+
function mul_with_rounding(x::F, y::F, ::RoundingMode{:NearestTiesUp}) where
165+
{T <: Union{Int8,Int16,Int32,Int64}, f, F <: Fixed{T, f}}
166+
z = widemul(x.i, y.i)
167+
F(((z + (oftype(z, 1) << f >>> 1)) >> f) % T, 0)
168+
end
169+
function mul_with_rounding(x::F, y::F, ::RoundingMode{:Down}) where
170+
{T <: Union{Int8,Int16,Int32,Int64}, f, F <: Fixed{T, f}}
171+
F((widemul(x.i, y.i) >> f) % T, 0)
172+
end
173+
174+
/(x::Fixed{T,f}, y::Fixed{T,f}) where {T,f} = Fixed{T,f}(div(convert(widen(T), x.i) << f, y.i), 0)
175+
121176
function trunc(x::Fixed{T,f}) where {T, f}
122177
f == 0 && return x
123178
f == bitwidth(T) && return zero(x) # TODO: remove this line

test/fixed.jl

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,10 @@ end
263263
end
264264

265265
@testset "type modulus" begin
266+
@test Q0f7(0.2) % Q0f7 === Q0f7(0.2)
267+
@test Q1f14(1.2) % Q0f15 === Q0f15(-0.8)
268+
@test Q1f14(1.2) % Q0f7 === Q0f7(-0.8)
269+
266270
T = Fixed{Int8,7}
267271
for i = -1.0:0.1:typemax(T)
268272
@test i % T === T(i)
@@ -276,6 +280,9 @@ end
276280
end
277281
@test ( 65.2 % T).i == round(Int, 65.2*512) % Int16
278282
@test (-67.2 % T).i == round(Int, -67.2*512) % Int16
283+
284+
@test -1 % Q0f7 === Q0f7(-1)
285+
@test -2 % Q0f7 === Q0f7(0)
279286
end
280287

281288
@testset "neg" begin
@@ -379,9 +386,6 @@ end
379386
@test saturating_mul(typemax(F), zero(F)) === zero(F)
380387
@test checked_mul(typemax(F), zero(F)) === zero(F)
381388

382-
# FIXME: Both the rhs and lhs of the following tests may be inaccurate due to `rem`
383-
F === Fixed{Int128,127} && continue
384-
385389
@test wrapping_mul(F(-1), typemax(F)) === -typemax(F)
386390
@test saturating_mul(F(-1), typemax(F)) === -typemax(F)
387391
@test checked_mul(F(-1), typemax(F)) === -typemax(F)
@@ -405,6 +409,13 @@ end
405409
@test all(((x, y),) -> !(typemin(F) <= fmul(x, y) <= typemax(F)) ||
406410
wrapping_mul(x, y) === checked_mul(x, y), xys)
407411
end
412+
413+
FixedPointNumbers.mul_with_rounding(1.5Q6f1, 0.5Q6f1, RoundNearest) === 1.0Q6f1
414+
FixedPointNumbers.mul_with_rounding(1.5Q6f1, -0.5Q6f1, RoundNearest) === -1.0Q6f1
415+
FixedPointNumbers.mul_with_rounding(1.5Q6f1, 0.5Q6f1, RoundNearestTiesUp) === 1.0Q6f1
416+
FixedPointNumbers.mul_with_rounding(1.5Q6f1, -0.5Q6f1, RoundNearestTiesUp) === -0.5Q6f1
417+
FixedPointNumbers.mul_with_rounding(1.5Q6f1, 0.5Q6f1, RoundDown) === 0.5Q6f1
418+
FixedPointNumbers.mul_with_rounding(1.5Q6f1, -0.5Q6f1, RoundDown) === -1.0Q6f1
408419
end
409420

410421
@testset "rounding" begin

test/normed.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,8 @@ end
268268
@test (65.2 % N6f10).i == round(Int, 65.2*1023) % UInt16
269269
@test (-0.3 % N6f10).i == round(Int, -0.3*1023) % UInt16
270270

271-
@test 1 % N0f8 == 1
272-
@test 2 % N0f8 == N0f8(0.996)
271+
@test 1 % N0f8 === N0f8(1)
272+
@test 2 % N0f8 === N0f8(0.996)
273273

274274
# issue #150
275275
@test all(f -> 1.0f0 % Normed{UInt32,f} == oneunit(Normed{UInt32,f}), 1:32)

0 commit comments

Comments
 (0)