Skip to content

Commit e0c1932

Browse files
authored
Use Int256 to reduce BigInts in FD operations. (#93)
* Use Int256 to avoid BigInt in FD operations. We do not here explicitly introduce support for FD{BitIntegers.Int256}, though that should work out of the box both before and after this PR. Rather, this PR _uses_ a (U)Int256 under the hood to prevent allocations from Int128 widening to BigInt in FD operations. * Further reduce BigInts by skipping a `rem()` in iseven * Bump patch version number
1 parent ddee978 commit e0c1932

File tree

2 files changed

+30
-9
lines changed

2 files changed

+30
-9
lines changed

Project.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
name = "FixedPointDecimals"
22
uuid = "fb4d412d-6eee-574d-9565-ede6634db7b0"
33
authors = ["Fengyang Wang <fengyang.wang.0@gmail.com>", "Curtis Vogt <curtis.vogt@gmail.com>"]
4-
version = "0.5.2"
4+
version = "0.5.3"
55

66
[deps]
7+
BitIntegers = "c3b6d118-76ef-56ca-8cc7-ebb389d030a1"
78
Parsers = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
89

910
[compat]
1011
Parsers = "2.7"
12+
BitIntegers = "0.3.1"
1113
julia = "1.6"
1214

1315
[extras]

src/FixedPointDecimals.jl

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export checked_abs, checked_add, checked_cld, checked_div, checked_fld,
3535
checked_mod, checked_mul, checked_neg, checked_rem, checked_sub
3636

3737
using Base: decompose, BitInteger
38+
39+
import BitIntegers # For 128-bit _widemul / _widen
3840
import Parsers
3941

4042
# floats that support fma and are roughly IEEE-like
@@ -118,6 +120,21 @@ function __init__()
118120
return
119121
end
120122

123+
# Custom widemul implementation to avoid the cost of widening to BigInt.
124+
# FD{Int128} operations should widen to 256 bits internally, rather than to a BigInt.
125+
const BitInteger128 = Union{Int128, UInt128}
126+
_widemul(x, y) = _widen(x) * _widen(y)
127+
_widemul(x::Signed,y::Unsigned) = _widen(x) * signed(_widen(y))
128+
_widemul(x::Unsigned,y::Signed) = signed(_widen(x)) * _widen(y)
129+
130+
# Custom widen implementation to avoid the cost of widening to BigInt.
131+
# FD{Int128} operations should widen to 256 bits internally, rather than to a BigInt.
132+
_widen(::Type{Int128}) = BitIntegers.Int256
133+
_widen(::Type{UInt128}) = BitIntegers.UInt256
134+
_widen(t::Type) = widen(t)
135+
_widen(x::T) where {T} = (_widen(T))(x)
136+
137+
121138
(::Type{T})(x::Real) where {T <: FD} = convert(T, x)
122139

123140
floattype(::Type{<:FD{T}}) where {T<:Union{Int8, UInt8, Int16, UInt16}} = Float32
@@ -157,7 +174,9 @@ function _round_to_nearest(quotient::T,
157174
divisor::T,
158175
::RoundingMode{M}=RoundNearest) where {T <: Integer, M}
159176
halfdivisor = divisor >> 1
160-
if iseven(divisor) && remainder == halfdivisor
177+
# PERF Note: Only need the last bit to check iseven, and default iseven(Int256)
178+
# allocates, so we truncate first.
179+
if iseven((divisor % Int8)) && remainder == halfdivisor
161180
# `:NearestTiesAway` will tie away from zero, e.g. -8.5 ->
162181
# -9. `:NearestTiesUp` will always ties towards positive
163182
# infinity. `:Nearest` will tie towards the nearest even
@@ -188,7 +207,7 @@ _round_to_nearest(q, r, d, m=RoundNearest) = _round_to_nearest(promote(q, r, d).
188207
# correctness test suite.
189208
function Base.:*(x::FD{T, f}, y::FD{T, f}) where {T, f}
190209
powt = coefficient(FD{T, f})
191-
quotient, remainder = fldmodinline(widemul(x.i, y.i), powt)
210+
quotient, remainder = fldmodinline(_widemul(x.i, y.i), powt)
192211
reinterpret(FD{T, f}, _round_to_nearest(quotient, remainder, powt))
193212
end
194213

@@ -416,7 +435,7 @@ function Base.checked_sub(x::T, y::T) where {T<:FD}
416435
end
417436
function Base.checked_mul(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f}
418437
powt = coefficient(FD{T, f})
419-
quotient, remainder = fldmodinline(widemul(x.i, y.i), powt)
438+
quotient, remainder = fldmodinline(_widemul(x.i, y.i), powt)
420439
v = _round_to_nearest(quotient, remainder, powt)
421440
typemin(T) <= v <= typemax(T) || Base.Checked.throw_overflowerr_binaryop(:*, x, y)
422441
return reinterpret(FD{T, f}, T(v))
@@ -474,7 +493,7 @@ checked_rdiv(x::FD, y::FD) = checked_rdiv(promote(x, y)...)
474493

475494
function checked_rdiv(x::FD{T,f}, y::FD{T,f}) where {T<:Integer,f}
476495
powt = coefficient(FD{T, f})
477-
quotient, remainder = fldmod(widemul(x.i, powt), y.i)
496+
quotient, remainder = fldmod(_widemul(x.i, powt), y.i)
478497
v = _round_to_nearest(quotient, remainder, y.i)
479498
typemin(T) <= v <= typemax(T) || Base.Checked.throw_overflowerr_binaryop(:/, x, y)
480499
return reinterpret(FD{T, f}, v)
@@ -484,8 +503,8 @@ end
484503
# FixedDecimal.
485504
function checked_rdiv(x::Integer, y::FD{T, f}) where {T<:Integer, f}
486505
powt = coefficient(FD{T, f})
487-
powtsq = widemul(powt, powt)
488-
quotient, remainder = fldmod(widemul(x, powtsq), y.i)
506+
powtsq = _widemul(powt, powt)
507+
quotient, remainder = fldmod(_widemul(x, powtsq), y.i)
489508
v = _round_to_nearest(quotient, remainder, y.i)
490509
typemin(T) <= v <= typemax(T) || Base.Checked.throw_overflowerr_binaryop(:/, x, y)
491510
reinterpret(FD{T, f}, v)
@@ -722,7 +741,7 @@ NOTE: This function is expensive, since it contains a while-loop, but it is actu
722741
This function does not have or depend on any side-effects.
723742
"""
724743
function max_exp10(::Type{T}) where {T <: Integer}
725-
W = widen(T)
744+
W = _widen(T)
726745
type_max = W(typemax(T))
727746

728747
powt = one(W)
@@ -759,4 +778,4 @@ value(fd::FD) = fd.i
759778
# for generic hashing
760779
Base.decompose(fd::FD) = decompose(Rational(fd))
761780

762-
end
781+
end # module

0 commit comments

Comments
 (0)