/
StaticNumbers.jl
300 lines (248 loc) · 12.9 KB
/
StaticNumbers.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
module StaticNumbers
import Base.Broadcast: broadcasted, DefaultArrayStyle
export Static, static,
StaticBool, StaticInteger, StaticReal, StaticNumber, StaticOrInt, StaticOrBool,
@staticnumbers, @generate_static_methods, unstatic
# We no longer use Requires.jl, but have a macro to include the glue instead.
macro glue_to(mod)
src = read(joinpath(@__DIR__, string(mod, "_glue.jl")), String)
esc(Meta.parse("begin\n$src\nend")) # kinda like include("xxx_glue.jl"), but runs everytning in the current scope.
end
const StaticError = ErrorException("Illegal type parameter for Static.")
"""
A `StaticInteger` is an `Integer` whose value is stored in the type, and which
contains no runtime data.
"""
struct StaticInteger{X} <: Integer
function StaticInteger{X}() where {X}
X isa Integer && !(X isa Static) && isimmutable(X) || throw(StaticError)
new{X}()
end
end
# These cause inambiguity with (::Type{T})(x::SomeIntegerType) where T<:Integer in Base
# @inline StaticInteger{X}(x::Number) where {X} = x==X ? StaticInteger{X}() : throw(InexactError(:StaticInteger, StaticInteger{X}, x))
# @inline StaticInteger(x::Number) = StaticInteger{Integer(x)}()
@inline StaticInteger{X}(x::Int) where {X} = x==X ? StaticInteger{X}() : throw(InexactError(:StaticInteger, StaticInteger{X}, x))
@inline StaticInteger(x::Int) = StaticInteger{x}()
"""
A `StaticReal` is a `Real` whose value is stored in the type, and which
contains no runtime data.
"""
struct StaticReal{X} <: Real
function StaticReal{X}() where {X}
X isa Real && !(X isa Integer) && !(X isa Static) && isimmutable(X) || throw(StaticError)
new{X}()
end
end
# These cause inambiguity with (::Type{T})(z::Complex) where T<:Integer in Base
# @inline StaticReal{X}(x::Number) where {X} = x==X ? StaticReal{X}() : throw(InexactError(:StaticReal, StaticReal{X}, x))
# @inline StaticReal(x::Number) = StaticReal{Real(x)}()
@inline StaticReal{X}(x::Real) where {X} = x==X ? StaticReal{X}() : throw(InexactError(:StaticReal, StaticReal{X}, x))
@inline StaticReal(x::Real) = StaticReal{x}()
"""
A `StaticNumber` is a `Number` whose value is stored in the type, and which
contains no runtime data.
"""
struct StaticNumber{X} <: Number
function StaticNumber{X}() where {X}
X isa Number && !(X isa Real) && !(X isa Static) && isimmutable(X) || throw(StaticError)
new{X}()
end
end
@inline StaticNumber{X}(x::Number) where {X} = x==X ? StaticNumber{X}() : throw(InexactError(:StaticNumber, StaticReal{X}, x))
@inline StaticNumber(x::Number) = StaticNumber{x}()
"""
`Static{X}` is short-hand for the `Union` of `StaticInteger{X}`, `StaticReal{X}`
and `StaticNumber{X}`.
"""
const Static{X} = Union{StaticInteger{X}, StaticReal{X}, StaticNumber{X}}
# We'll define this constructor, but not recommend it.
Static{X}() where X = static(X)
# This is the recommended constructor.
"""
`static(X)` is shorthand for `StaticInteger{X}()`, `StaticReal{X}()` or `StaticNumber{X}()`,
depending on the type of `X`.
"""
@inline static(x::Integer) = StaticInteger{x}()
@inline static(x::Real) = StaticReal{x}()
@inline static(x::Number) = StaticNumber{x}()
@inline static(x::Bool) = x ? StaticInteger{true}() : StaticInteger{false}() # help inference
@inline static(x::StaticInteger) = x
@inline static(x::StaticReal) = x
@inline static(x::StaticNumber) = x
# Don't convert `Irrational`s to `Static``, since they are already "static" to the compiler.
@inline static(x::AbstractIrrational) = x
Base.Irrational{X}(::StaticReal{Y}) where {X, Y} = Irrational{X}(Y)
# There's no point crating a Val{Static{X}} since any function that would accept
# it should treat it as equivalent to Val{X}.
# But maybe it is a bad idea to add a method to a @pure function?
# @inline Base.Val(::Static{X}) where X = Val(X)
"""
`StaticNumbers.map(f, t::Tuple)` works the same as `Base.map(f, t)`, but does not stop
in-lining after a certain tuple length. Long tuples will cause code blowup.
"""
# Code copied from julia/base/tuples.jl and slightly modified
@inline map(f::F, t::Tuple{}) where {F} = ()
@inline map(f::F, t::Tuple) where {F} = (f(t[1]), map(f, Base.tail(t))...)
@inline map(f::F, t::Tuple{}, s::Tuple{}) where {F} = ()
@inline map(f::F, t::Tuple, s::Tuple) where {F} = (f(t[1],s[1]), map(f, Base.tail(t), Base.tail(s))...)
@inline heads(ts::Tuple...) = map(first, ts)
@inline tails(ts::Tuple...) = map(Base.tail, ts)
@inline map(f, ::Tuple{}...) = ()
@inline map(f::F, t1::Tuple{Any,Vararg{Any,N}}, t2::Tuple{Any,Vararg{Any,N}}, ts::Tuple{Any,Vararg{Any,N}}...) where {F,N} = (f(heads(t1, t2, ts...)...), map(f, tails(t1, t2, ts...)...)...)
@inline map(args...) = Base.map(args...) # fallback for non-tuples
"""
`ntuple(f, static(n))` yields the tuple `(f(static(1)), f(static(2)), ..., f(static(n)))`.
This will ususally yield the same result as `ntuple(f, n)` but may make the compiler try
even harder to constant-propagate the integer input into `f`.
"""
# Recursive version only worked for tuple lengths up to 32, so resorting to @generated.
@generated function Base.ntuple(f::F, ::StaticInteger{N}) where {F,N}
(N::Int >= 0) || throw(ArgumentError(string("tuple length should be ≥ 0, got ", N)))
v = Expr(:tuple, ( :( f(StaticInteger{$i}()) ) for i in 1:N )... )
:( Base.@_inline_meta; $v )
end
"""
`StaticOrInt` is the type union of `Int` and `StaticInteger`
(Functions that take only `Int` may be too restrictive.)
"""
const StaticOrInt = Union{StaticInteger, Int}
# Promotion
# We need to override promote and promote_typeof because they don't even call
# promote_rule for all-same types.
Base.promote(::ST, ys::ST...) where {ST <: Static{X}} where {X} = ntuple(i->X, static(1+length(ys)))
Base.promote_type(::Type{ST}, ::Type{ST}) where {ST <: Static{X}} where {X} = typeof(X)
Base.promote_typeof(::ST, ::ST...) where {ST <: Static{X}} where {X} = typeof(X)
# To avoid infinite recursion, we need this:
Base.promote_type(::Type{<:Static{X}}, T::Type...) where {X} = promote_type(typeof(X), promote_type(T...))
# Loop over all three types specifically, instead of dispatching on the Union.
for ST in (StaticInteger, StaticReal, StaticNumber)
Base.promote_rule(::Type{ST{X}}, ::Type{T}) where {X,T<:Number} = promote_type(typeof(X), T)
# Constructors
(::Type{Complex{T}})(::ST{X}) where {T<:Real, X} = Complex{T}(X)
(::Type{Rational{T}})(::ST{X}) where {T<:Integer, X} = Rational{T}(X)
end
# TODO: Constructors to avoid Static{Static}
# Some of the more common constructors that do not default to `convert`
# Note: We cannot have a (::Type{T})(x::Static) where {T<:Number} constructor
# instead of all of these, because of ambiguities with user-defined types.
for T in (:Bool, :Integer, :AbstractFloat, :Unsigned, :Signed,
:BigInt, :Int128, :Int16, :Int32, :Int64, :Int8,
:UInt128, :UInt16, :UInt32, :UInt64, :UInt8,
:BigFloat, :Float16, :Float32, :Float64)
@eval Base.$T(::Static{X}) where {X} = $T(X)
end
# big(x) still defaults to convert.
# Single-argument functions that do not already work.
# Note: We're not attempting to support every function in Base.
# TODO: Should have a macro for easily extending support.
for fun in (:-, :zero, :one, :oneunit, :trailing_zeros, :decompose)
@eval Base.$fun(::Static{X}) where X = Base.$fun(X)
end
for fun in (:trunc, :floor, :ceil, :round, :isnan)
@eval Base.$fun(::Union{StaticReal{X}, StaticNumber{X}}) where {X} = Base.$fun(X)
end
for fun in (:zero, :one, :oneunit)
@eval Base.$fun(::Type{<:Static{X}}) where {X} = Base.$fun(typeof(X))
end
Base.widen(::Static{X}) where {X} = X isa Bool ? X : widen(X)
# It's a pity there's no AbstractBool supertype.
"StaticBool is a shorthand for Union{StaticInteger{false}, StaticInteger{true}}"
const StaticBool = Union{StaticInteger{false}, StaticInteger{true}}
"StaticOrBool can be either a `Bool` or a `StaticBool`"
const StaticOrBool = Union{StaticBool, Bool}
Base.:!(x::StaticBool) = !Bool(x)
# false is a strong zero
for T in (Integer, Rational, Real, Number, Complex{<:Real}, Complex{Bool}, StaticInteger, StaticReal, StaticNumber)
Base.:*(::StaticInteger{false}, y::T) = zero(y)
Base.:*(x::T, ::StaticInteger{false}) = zero(x)
end
Base.:*(::StaticInteger{false}, ::StaticInteger{false}) = false # disambig
# Handle static(Inf)*false
Base.:*(x::Bool, ::StaticReal{Y}) where Y = x*Y
Base.:*(::StaticReal{X}, y::Bool) where X = X*y
# Until https://github.com/JuliaLang/julia/pull/32117 is merged
Base.:*(::StaticInteger{false}, ::AbstractIrrational) = 0.0
Base.:*(::AbstractIrrational, ::StaticInteger{false}) = 0.0
# For complex-valued inputs, there's no auto-convert to floating-point.
# We only support a limited subset of functions, which the user can extend
# as needed.
# TODO: Should have a macro for making functions accept Static input.
for fun in (:abs, :abs2, :cos, :sin, :exp, :log, :isinf, :isfinite, :isnan)
@eval Base.$fun(::StaticNumber{X}) where {X} = Base.$fun(X)
end
Base.sign(::StaticInteger{X}) where {X} = Base.sign(X) # work around problem with Bool
Base.sign(::StaticReal{X}) where {X} = Base.sign(X)
# Other functions that do not already work
Base.:(<<)(::StaticInteger{X}, y::UInt64) where {X} = X << y
Base.:(>>)(::StaticInteger{X}, y::UInt64) where {X} = X >> y
# Two-argument functions that have methods in promotion.jl that give no_op_err:
for f in (:+, :-, :*, :/, :^)
@eval Base.$f(::ST, ::ST) where {ST <: Static{X}} where {X} = $f(X,X)
end
# ...where simplifications are possible:
# Note: We allow creation of specific static numbers, like 1 and 0 (as an exception)
# since this cannot lead to the set of static numbers growing uncontrollably.
Base.:&(x::ST, ::ST) where {ST<:StaticInteger} = x
Base.:|(x::ST, ::ST) where {ST<:StaticInteger} = x
Base.xor(::ST, ::ST) where {ST<:StaticInteger} = static(zero(ST))
Base.rem(::ST, ::ST) where {ST<:Static{X}} where {X} = (X==0 || isinf(X)) ? X isa AbstractFloat ? static(oftype(X, NaN)) : throw(DivideError()) : static(zero(X))
Base.mod(::ST, ::ST) where {ST<:Static{X}} where {X} = (X==0 || isinf(X)) ? X isa AbstractFloat ? static(oftype(X, NaN)) : throw(DivideError()) : static(zero(X))
Base.div(::ST, ::ST) where {ST<:Static{X}} where {X} = (X == 0 || isinf(X)) ? ( X isa Integer ? throw(DivideError()) : oftype(X, NaN) ) : static(one(X)) # Needed for Julia > 1.3
Base.:<(::ST, ::ST) where {ST<:StaticReal{X}} where {X} = false
Base.:<=(::ST, ::ST) where {ST<:StaticReal{X}} where {X} = true
# Bypass promotion in comparisons involving static unsigned integers
for fun in (:(<), :(<=))
@eval Base.$fun(::StaticInteger{X}, y::Integer) where {X} = $fun(X, y)
@eval Base.$fun(x::Integer, ::StaticInteger{Y}) where {Y} = $fun(x, Y)
@eval Base.$fun(::StaticInteger{X}, y::BigInt) where {X} = $fun(X, y)
@eval Base.$fun(x::BigInt, ::StaticInteger{Y}) where {Y} = $fun(x, Y)
@eval Base.$fun(::StaticInteger{X}, ::StaticInteger{Y}) where {X,Y} = $fun(X, Y)
end
Base.:(==)(x::AbstractIrrational, ::StaticReal{Y}) where {Y} = x == Y
Base.:(==)(::StaticReal{X}, y::AbstractIrrational) where {X} = X == y
# Three-argument function that gives no_op_err
Base.fma(::ST, ::ST, ::ST) where {ST<:Static{X}} where {X} = fma(X,X,X)
# Static powers using Base.literal_pow.
# This avoids DomainError in some cases?
for T in (Bool, Int32, Int64, Float32, Float64, ComplexF32, ComplexF64, Irrational)
Base.:^(x::T, ::StaticInteger{p}) where {p} = Base.literal_pow(^, x, Val(p))
end
Base.:^(x::Static{X}, ::StaticInteger{p}) where {X,p} = Base.literal_pow(^, X, Val(p))
Base.:^(x::ST, ::ST) where {ST<:StaticInteger{p}} where {p} = Base.literal_pow(^, x, Val(p)) #disambig
Base.:^(::Irrational{:ℯ}, p::StaticInteger) = exp(p) #disambig
# For brevity, all `Static` numbers are displayed as `static(X)`, rather than, for
# example, `StaticInteger{X}()`. It is possible to discern between the different
# types of `Static` by looking at `X`.
# To get the default behaviour back, run:
# methods(Base.show, (IO, Static{X} where X)) |> first |> Base.delete_method
function Base.show(io::IO, x::Static{X}) where X
print(io, "static(")
show(io, X)
print(io, ")")
end
for f in (:isfinite, :isnan, :isinf)
@eval Base.$f(::StaticReal{X}) where {X} = $f(X)
end
# Dont promote when it's better to treat real and imaginary parts separately
Base.:/(::StaticNumber{X}, y::Real) where {X} = X/y
Base.:*(::StaticNumber{X}, y::Real) where {X} = X*y
Base.:*(x::Real, ::StaticNumber{Y}) where {Y} = x*Y
@inline function Base.setindex(x::Tuple, v, i::StaticInteger)
@boundscheck 1 <= i <= length(x) || throw(BoundsError(x, i))
return ntuple(n -> n == i ? v : x[n], static(length(x)))
end
Base.oftype(::Static{X}, y) where {X} = oftype(X, y)
"""
`unstatic(x)` returns a non-static version of `x`.
This function is rarely needed, as most operations on a static number (e.g. `x+0`)
will yield a non-static result.
"""
unstatic(x) = x
unstatic(::Static{X}) where {X} = X
include("generate_static_methods.jl")
include("stat_macro.jl")
include("LengthRanges.jl")
include("trystatic.jl")
include("deprecated.jl")
end # module