Skip to content

Commit 4dccf82

Browse files
committed
Backport partially "Specialize multiplication for Fixed (JuliaMath#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 6013b7c commit 4dccf82

File tree

3 files changed

+60
-19
lines changed

3 files changed

+60
-19
lines changed

src/fixed.jl

+43-13
Original file line numberDiff line numberDiff line change
@@ -92,19 +92,20 @@ function _convert(::Type{F}, x::Rational) where {T, f, F <: Fixed{T,f}}
9292
end
9393
end
9494

95-
# unchecked arithmetic
96-
97-
# with truncation:
98-
#*(x::Fixed{T,f}, y::Fixed{T,f}) = Fixed{T,f}(Base.widemul(x.i,y.i)>>f,0)
99-
# with rounding up:
100-
*(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)
101-
102-
/(x::Fixed{T,f}, y::Fixed{T,f}) where {T,f} = Fixed{T,f}(div(convert(widen(T), x.i) << f, y.i), 0)
103-
104-
105-
rem(x::Integer, ::Type{Fixed{T,f}}) where {T,f} = Fixed{T,f}(rem(x,T)<<f,0)
106-
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)
107-
95+
rem(x::F, ::Type{F}) where {F <: Fixed} = x
96+
function rem(x::Fixed, ::Type{F}) where {T, f, F <: Fixed{T,f}}
97+
f2 = nbitsfrac(typeof(x))
98+
y = round(@exp2(f - f2) * reinterpret(x))
99+
reinterpret(F, _unsafe_trunc(T, y))
100+
end
101+
rem(x::Integer, ::Type{F}) where {T, f, F <: Fixed{T,f}} = F(_unsafe_trunc(T, x) << f, 0)
102+
function rem(x::Real, ::Type{F}) where {T, f, F <: Fixed{T,f}}
103+
y = _unsafe_trunc(promote_type(Int64, T), round(x * @exp2(f)))
104+
reinterpret(F, _unsafe_trunc(T, y))
105+
end
106+
function rem(x::BigFloat, ::Type{F}) where {T, f, F <: Fixed{T,f}}
107+
reinterpret(F, _unsafe_trunc(T, round(x * @exp2(f))))
108+
end
108109

109110
(::Type{Tf})(x::Fixed{T,f}) where {Tf <: AbstractFloat, T, f} = Tf(Tf(x.i) * Tf(@exp2(-f)))
110111
Base.Float16(x::Fixed{T,f}) where {T, f} = Float16(Float32(x))
@@ -115,6 +116,35 @@ function Base.Rational(x::Fixed{T,f}) where {T, f}
115116
f < bitwidth(T)-1 ? x.i//rawone(x) : x.i//(one(widen1(T))<<f)
116117
end
117118

119+
function div_2f(x::T, ::Val{f}) where {T, f}
120+
xf = x & (T(-1) >>> (bitwidth(T) - f - 1))
121+
half = oneunit(T) << (f - 1)
122+
c = half - (xf === half)
123+
(x + c) >> f
124+
end
125+
div_2f(x::T, ::Val{0}) where {T} = x
126+
127+
# wrapping arithmetic
128+
function wrapping_mul(x::F, y::F) where {T <: Union{Int8,Int16,Int32,Int64}, f, F <: Fixed{T,f}}
129+
z = widemul(x.i, y.i)
130+
F(div_2f(z, Val(Int(f))) % T, 0)
131+
end
132+
133+
function mul_with_rounding(x::F, y::F, ::RoundingMode{:Nearest}) where {F <: Fixed}
134+
wrapping_mul(x, y)
135+
end
136+
function mul_with_rounding(x::F, y::F, ::RoundingMode{:NearestTiesUp}) where
137+
{T <: Union{Int8,Int16,Int32,Int64}, f, F <: Fixed{T, f}}
138+
z = widemul(x.i, y.i)
139+
F(((z + (oftype(z, 1) << f >>> 1)) >> f) % T, 0)
140+
end
141+
function mul_with_rounding(x::F, y::F, ::RoundingMode{:Down}) where
142+
{T <: Union{Int8,Int16,Int32,Int64}, f, F <: Fixed{T, f}}
143+
F((widemul(x.i, y.i) >> f) % T, 0)
144+
end
145+
146+
/(x::Fixed{T,f}, y::Fixed{T,f}) where {T,f} = Fixed{T,f}(div(convert(widen(T), x.i) << f, y.i), 0)
147+
118148
function trunc(x::Fixed{T,f}) where {T, f}
119149
f == 0 && return x
120150
f == bitwidth(T) && return zero(x) # TODO: remove this line

test/fixed.jl

+15-4
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,10 @@ end
241241
end
242242

243243
@testset "type modulus" begin
244+
@test Q0f7(0.2) % Q0f7 === Q0f7(0.2)
245+
@test Q1f14(1.2) % Q0f15 === Q0f15(-0.8)
246+
@test Q1f14(1.2) % Q0f7 === Q0f7(-0.8)
247+
244248
T = Fixed{Int8,7}
245249
for i = -1.0:0.1:typemax(T)
246250
@test i % T === T(i)
@@ -254,16 +258,16 @@ end
254258
end
255259
@test ( 65.2 % T).i == round(Int, 65.2*512) % Int16
256260
@test (-67.2 % T).i == round(Int, -67.2*512) % Int16
261+
262+
@test -1 % Q0f7 === Q0f7(-1)
263+
@test -2 % Q0f7 === Q0f7(0)
257264
end
258265

259266
@testset "mul" begin
260-
wrapping_mul(x::F, y::F) where {F <: Fixed} = x * y
267+
wrapping_mul = FixedPointNumbers.wrapping_mul
261268
for F in target(Fixed; ex = :thin)
262269
@test wrapping_mul(typemax(F), zero(F)) === zero(F)
263270

264-
# FIXME: Both the rhs and lhs of the following tests may be inaccurate due to `rem`
265-
F === Fixed{Int128,127} && continue
266-
267271
@test wrapping_mul(F(-1), typemax(F)) === -typemax(F)
268272

269273
@test wrapping_mul(typemin(F), typemax(F)) === big(typemin(F)) * big(typemax(F)) % F
@@ -276,6 +280,13 @@ end
276280
fmul(x, y) = float(x) * float(y) # note that precision(Float32) < 32
277281
@test all(((x, y),) -> wrapping_mul(x, y) === fmul(x, y) % F, xys)
278282
end
283+
284+
FixedPointNumbers.mul_with_rounding(1.5Q6f1, 0.5Q6f1, RoundNearest) === 1.0Q6f1
285+
FixedPointNumbers.mul_with_rounding(1.5Q6f1, -0.5Q6f1, RoundNearest) === -1.0Q6f1
286+
FixedPointNumbers.mul_with_rounding(1.5Q6f1, 0.5Q6f1, RoundNearestTiesUp) === 1.0Q6f1
287+
FixedPointNumbers.mul_with_rounding(1.5Q6f1, -0.5Q6f1, RoundNearestTiesUp) === -0.5Q6f1
288+
FixedPointNumbers.mul_with_rounding(1.5Q6f1, 0.5Q6f1, RoundDown) === 0.5Q6f1
289+
FixedPointNumbers.mul_with_rounding(1.5Q6f1, -0.5Q6f1, RoundDown) === -1.0Q6f1
279290
end
280291

281292
@testset "rounding" begin

test/normed.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,8 @@ end
252252
@test (65.2 % N6f10).i == round(Int, 65.2*1023) % UInt16
253253
@test (-0.3 % N6f10).i == round(Int, -0.3*1023) % UInt16
254254

255-
@test 1 % N0f8 == 1
256-
@test 2 % N0f8 == N0f8(0.996)
255+
@test 1 % N0f8 === N0f8(1)
256+
@test 2 % N0f8 === N0f8(0.996)
257257

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

0 commit comments

Comments
 (0)