Skip to content

Commit dd9c83d

Browse files
authored
Merge pull request #21 from SymbolicML/fast-fractions
Create `FixedRational` type for faster dimensions
2 parents 8cbe4db + 1b8602e commit dd9c83d

File tree

5 files changed

+56
-3
lines changed

5 files changed

+56
-3
lines changed

src/DynamicQuantities.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module DynamicQuantities
33
export Quantity, Dimensions, DimensionError, ustrip, dimension, valid
44
export ulength, umass, utime, ucurrent, utemperature, uluminosity, uamount
55

6+
include("fixed_rational.jl")
67
include("types.jl")
78
include("utils.jl")
89
include("math.jl")

src/fixed_rational.jl

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
FixedRational{T,den}
3+
4+
A rational number with a fixed denominator. Significantly
5+
faster than `Rational{T}`, as it never needs to compute
6+
the `gcd` apart from when printing.
7+
"""
8+
struct FixedRational{T<:Integer,den} <: Real
9+
num::T
10+
global unsafe_fixed_rational(num::Integer, ::Type{T}, ::Val{den}) where {T,den} = new{T,den}(num)
11+
end
12+
13+
"""
14+
denom(F::FixedRational)
15+
16+
Since `den` can be a different type than `T`, this function
17+
is used to get the denominator as a `T`.
18+
"""
19+
denom(::Type{F}) where {T,den,F<:FixedRational{T,den}} = convert(T, den)
20+
21+
# But, for Val(den), we need to use the same type as at init.
22+
# Otherwise, we would have type instability.
23+
val_denom(::Type{F}) where {T,den,F<:FixedRational{T,den}} = Val(den)
24+
25+
Base.eltype(::Type{F}) where {T,den,F<:FixedRational{T,den}} = T
26+
27+
(::Type{F})(x::Integer) where {F<:FixedRational} = unsafe_fixed_rational(x * denom(F), eltype(F), val_denom(F))
28+
(::Type{F})(x::Rational) where {F<:FixedRational} = unsafe_fixed_rational(widemul(x.num, denom(F)) ÷ x.den, eltype(F), val_denom(F))
29+
30+
Base.:*(l::F, r::F) where {F<:FixedRational} = unsafe_fixed_rational(widemul(l.num, r.num) ÷ denom(F), eltype(F), val_denom(F))
31+
Base.:+(l::F, r::F) where {F<:FixedRational} = unsafe_fixed_rational(l.num + r.num, eltype(F), val_denom(F))
32+
Base.:-(l::F, r::F) where {F<:FixedRational} = unsafe_fixed_rational(l.num - r.num, eltype(F), val_denom(F))
33+
Base.:-(x::F) where {F<:FixedRational} = unsafe_fixed_rational(-x.num, eltype(F), val_denom(F))
34+
Base.inv(x::F) where {F<:FixedRational} = unsafe_fixed_rational(widemul(denom(F), denom(F)) ÷ x.num, eltype(F), val_denom(F))
35+
36+
Base.:(==)(x::F, y::F) where {F<:FixedRational} = x.num == y.num
37+
Base.iszero(x::FixedRational) = iszero(x.num)
38+
Base.isinteger(x::F) where {F<:FixedRational} = iszero(x.num % denom(F))
39+
Base.convert(::Type{F}, x::Integer) where {F<:FixedRational} = unsafe_fixed_rational(x * denom(F), eltype(F), val_denom(F))
40+
Base.convert(::Type{F}, x::Rational) where {F<:FixedRational} = F(x)
41+
Base.convert(::Type{Rational}, x::F) where {F<:FixedRational} = Rational{eltype(F)}(x.num, denom(F))
42+
Base.convert(::Type{AF}, x::F) where {AF<:AbstractFloat,F<:FixedRational} = convert(AF, x.num) / convert(AF, denom(F))
43+
Base.round(::Type{T}, x::F) where {T,F<:FixedRational} = div(convert(T, x.num), convert(T, denom(F)), RoundNearest)
44+
Base.promote(x, y::F) where {F<:FixedRational} = promote(x, convert(Rational, y))
45+
Base.promote(x::F, y) where {F<:FixedRational} = promote(convert(Rational, x), y)
46+
Base.show(io::IO, x::F) where {F<:FixedRational} = show(io, convert(Rational, x))
47+
Base.zero(::Type{F}) where {F<:FixedRational} = unsafe_fixed_rational(0, eltype(F), val_denom(F))
48+
49+
tryrationalize(::Type{F}, x::F) where {F<:FixedRational} = x
50+
tryrationalize(::Type{F}, x::Union{Rational,Integer}) where {F<:FixedRational} = convert(F, x)
51+
tryrationalize(::Type{F}, x) where {F<:FixedRational} = unsafe_fixed_rational(round(eltype(F), x * denom(F)), eltype(F), val_denom(F))

src/types.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const DEFAULT_DIM_TYPE = Rational{Int16}
1+
const DEFAULT_DIM_TYPE = FixedRational{Int32, 2^4 * 3^2 * 5^2 * 7}
22
const DEFAULT_VALUE_TYPE = Float64
33

44
"""

test/test_unitful.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import DynamicQuantities
2+
using DynamicQuantities: DEFAULT_DIM_TYPE, DEFAULT_VALUE_TYPE
23
import Unitful
34
import Unitful: @u_str
45
import Ratios: SimpleRatio
@@ -15,7 +16,7 @@ risapprox(x::Unitful.Quantity, y::Unitful.Quantity; kws...) =
1516

1617
factor_for_preferred_units = 1e-3
1718

18-
for T in [Float16, Float32, Float64], R in [Rational{Int16}, Rational{Int32}, SimpleRatio{Int}, SimpleRatio{SafeInt16}]
19+
for T in [DEFAULT_VALUE_TYPE, Float16, Float32, Float64], R in [DEFAULT_DIM_TYPE, Rational{Int16}, Rational{Int32}, SimpleRatio{Int}, SimpleRatio{SafeInt16}]
1920
x = DynamicQuantities.Quantity(T(0.2*factor_for_preferred_units), R, length=1, amount=2, current=-1 // 2, luminosity=2 // 5)
2021
x_unitful = T(0.2)u"m*mol^2*A^(-1//2)*cd^(2//5)"
2122

test/unittests.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ using Test
66

77
@testset "Basic utilities" begin
88

9-
for T in [Float16, Float32, Float64], R in [Rational{Int16}, Rational{Int32}, SimpleRatio{Int}, SimpleRatio{SafeInt16}]
9+
for T in [DEFAULT_VALUE_TYPE, Float16, Float32, Float64], R in [DEFAULT_DIM_TYPE, Rational{Int16}, Rational{Int32}, SimpleRatio{Int}, SimpleRatio{SafeInt16}]
1010
x = Quantity(T(0.2), R, length=1, mass=2.5)
1111

1212
@test typeof(x).parameters[1] == T

0 commit comments

Comments
 (0)