Skip to content

Commit 2673a8f

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 1189820 commit 2673a8f

File tree

3 files changed

+59
-18
lines changed

3 files changed

+59
-18
lines changed

src/fixed.jl

Lines changed: 43 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,35 @@ 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+
function mul_with_rounding(x::F, y::F, ::RoundingMode{:Nearest}) where {F <: Fixed}
137+
wrapping_mul(x, y)
138+
end
139+
function mul_with_rounding(x::F, y::F, ::RoundingMode{:NearestTiesUp}) where
140+
{T <: Union{Int8,Int16,Int32,Int64}, f, F <: Fixed{T, f}}
141+
z = widemul(x.i, y.i)
142+
F(((z + (oftype(z, 1) << f >>> 1)) >> f) % T, 0)
143+
end
144+
function mul_with_rounding(x::F, y::F, ::RoundingMode{:Down}) where
145+
{T <: Union{Int8,Int16,Int32,Int64}, f, F <: Fixed{T, f}}
146+
F((widemul(x.i, y.i) >> f) % T, 0)
147+
end
148+
149+
/(x::Fixed{T,f}, y::Fixed{T,f}) where {T,f} = Fixed{T,f}(div(convert(widen(T), x.i) << f, y.i), 0)
150+
121151
function trunc(x::Fixed{T,f}) where {T, f}
122152
f == 0 && return x
123153
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
@@ -246,6 +246,10 @@ end
246246
end
247247

248248
@testset "type modulus" begin
249+
@test Q0f7(0.2) % Q0f7 === Q0f7(0.2)
250+
@test Q1f14(1.2) % Q0f15 === Q0f15(-0.8)
251+
@test Q1f14(1.2) % Q0f7 === Q0f7(-0.8)
252+
249253
T = Fixed{Int8,7}
250254
for i = -1.0:0.1:typemax(T)
251255
@test i % T === T(i)
@@ -259,16 +263,16 @@ end
259263
end
260264
@test ( 65.2 % T).i == round(Int, 65.2*512) % Int16
261265
@test (-67.2 % T).i == round(Int, -67.2*512) % Int16
266+
267+
@test -1 % Q0f7 === Q0f7(-1)
268+
@test -2 % Q0f7 === Q0f7(0)
262269
end
263270

264271
@testset "mul" begin
265272
wrapping_mul = FixedPointNumbers.wrapping_mul
266273
for F in target(Fixed; ex = :thin)
267274
@test wrapping_mul(typemax(F), zero(F)) === zero(F)
268275

269-
# FIXME: Both the rhs and lhs of the following tests may be inaccurate due to `rem`
270-
F === Fixed{Int128,127} && continue
271-
272276
@test wrapping_mul(F(-1), typemax(F)) === -typemax(F)
273277

274278
@test wrapping_mul(typemin(F), typemax(F)) === big(typemin(F)) * big(typemax(F)) % F
@@ -281,6 +285,13 @@ end
281285
fmul(x, y) = float(x) * float(y) # note that precision(Float32) < 32
282286
@test all(((x, y),) -> wrapping_mul(x, y) === fmul(x, y) % F, xys)
283287
end
288+
289+
FixedPointNumbers.mul_with_rounding(1.5Q6f1, 0.5Q6f1, RoundNearest) === 1.0Q6f1
290+
FixedPointNumbers.mul_with_rounding(1.5Q6f1, -0.5Q6f1, RoundNearest) === -1.0Q6f1
291+
FixedPointNumbers.mul_with_rounding(1.5Q6f1, 0.5Q6f1, RoundNearestTiesUp) === 1.0Q6f1
292+
FixedPointNumbers.mul_with_rounding(1.5Q6f1, -0.5Q6f1, RoundNearestTiesUp) === -0.5Q6f1
293+
FixedPointNumbers.mul_with_rounding(1.5Q6f1, 0.5Q6f1, RoundDown) === 0.5Q6f1
294+
FixedPointNumbers.mul_with_rounding(1.5Q6f1, -0.5Q6f1, RoundDown) === -1.0Q6f1
284295
end
285296

286297
@testset "rounding" begin

test/normed.jl

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

260-
@test 1 % N0f8 == 1
261-
@test 2 % N0f8 == N0f8(0.996)
260+
@test 1 % N0f8 === N0f8(1)
261+
@test 2 % N0f8 === N0f8(0.996)
262262

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

0 commit comments

Comments
 (0)