Skip to content

Commit b0e1bbe

Browse files
authored
Allow non-integer exponents (#5)
* Allow non-integer exponents * Check for type inference * Promote big exponents
1 parent 8f907f2 commit b0e1bbe

File tree

4 files changed

+159
-117
lines changed

4 files changed

+159
-117
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "MultiplesOfPi"
22
uuid = "b749d01f-fee9-4313-9f11-89ddf7ea9d58"
33
authors = ["Jishnu Bhattacharya", "Center for Space Science", "New York University Abu Dhabi"]
4-
version = "0.5.0"
4+
version = "0.5.1"
55

66
[compat]
77
julia = "1"

README.md

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,7 @@
88

99
# Introduction
1010

11-
This package exports the type `PiExpTimes` that satisfies `PiExpTimes(x, n) = x*π^n`. It also provides the constant `Pi` for convenience, defined as `PiExpTimes(1, 1)`, that behaves like `π` except it produces results with higher accuracy in certain trigonometric and algebraic contexts. In most scenarios the numbers `Pi` and `π` are interchangable.
12-
13-
```julia
14-
julia> Pi^2 == π^2
15-
true
16-
```
17-
18-
Expressing mathematical relations in terms of `Pi` instead of the more cumbersome `PiExpTimes` is usually cleaner, and is recommended unless it's specifically necessary to do otherwise. One such scenario is exponentiation, more on which is presented below.
11+
This package exports the type `PiExpTimes` that satisfies `PiExpTimes(x, n) = x*π^n`. It also provides the constant `Pi` for convenience, defined as `PiExpTimes(1, 1)`, that behaves like `π` except it produces results with higher accuracy in certain trigonometric and algebraic contexts. In most scenarios the numbers `Pi` and `π` are interchangable. Expressing mathematical relations in terms of `Pi` instead of the more cumbersome `PiExpTimes` is usually cleaner, and is recommended unless it's specifically necessary to do otherwise.
1912

2013
## Rationale
2114

src/MultiplesOfPi.jl

Lines changed: 60 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,26 @@ julia> PiExpTimes(2, 3)
1616
1717
See also: [`Pi`](@ref)
1818
"""
19-
struct PiExpTimes{T<:Real} <: AbstractIrrational
19+
struct PiExpTimes{T<:Real,N<:Real} <: AbstractIrrational
2020
x :: T
21-
n :: Int
22-
23-
PiExpTimes{T}(x::T, n::Int) where {T<:Real} = new(x, n)
24-
PiExpTimes{PiExpTimes{T}}(p::PiExpTimes{T}, n::Int) where {T<:Real} = new(p, n)
25-
PiExpTimes{PiExpTimes}(p::PiExpTimes, n::Int) = new(p, n)
26-
PiExpTimes{Irrational{:π}}(p::Irrational{:π}, n::Int) = new(p, n)
21+
n :: N
2722
end
2823

29-
PiExpTimes{T}(x::Real, n::Int) where {T<:Real} = PiExpTimes{T}(T(x), n)
30-
PiExpTimes{T}(::Irrational{:π}, n::Int) where {T<:Real} = PiExpTimes{T}(T(1), n + 1)
31-
PiExpTimes{T}(p::PiExpTimes, n::Int) where {T<:Real} = PiExpTimes{T}(p.x, p.n + n)
32-
PiExpTimes{T}(x::Real, n::Integer = 0) where {T<:Real} = PiExpTimes{T}(x, Int(n))
24+
PiExpTimes{T,N}(x::Real) where {T,N} = PiExpTimes{T,N}(T(x), zero(N))
25+
PiExpTimes{T,N}(p::PiExpTimes{S}) where {T,S,N} = PiExpTimes{T,N}(T(p.x), N(p.n))
26+
27+
PiExpTimes{PiExpTimes{T}}(p::PiExpTimes{T}, n::Real) where {T<:Real} = PiExpTimes{PiExpTimes{T}, typeof(n)}(p, n)
28+
PiExpTimes{PiExpTimes}(p::PiExpTimes, n::Real) = PiExpTimes{PiExpTimes, typeof(n)}(p, n)
29+
PiExpTimes{Irrational{:π}}(p::Irrational{:π}, n::Real) = PiExpTimes{Irrational{:π}, typeof(n)}(p, n)
3330

34-
PiExpTimes(::Irrational{:π}, n::Integer = 0) = PiExpTimes{Int}(1, Int(n) + 1)
35-
PiExpTimes(x::Real, n::Integer = 0) = PiExpTimes{typeof(x)}(x, Int(n))
31+
PiExpTimes{T}(x::Real, n::Real = 0) where {T<:Real} = PiExpTimes{T,typeof(n)}(T(x), n)
32+
PiExpTimes{T}(::Irrational{:π}, n::Real) where {T<:Real} = PiExpTimes{T,typeof(n)}(T(1), n + 1)
33+
PiExpTimes{T}(p::PiExpTimes, n::Real) where {T<:Real} = PiExpTimes{T,typeof(n)}(p.x, p.n + n)
34+
35+
PiExpTimes(::Irrational{:π}, n::Real = 0) = PiExpTimes{Int}(1, n + one(n))
3636
# avoid nesting
37-
PiExpTimes(p::PiExpTimes, n::Integer = 0) = PiExpTimes(p.x, p.n + n)
37+
PiExpTimes(p::PiExpTimes, n::Real = 0) = PiExpTimes(p.x, p.n + n)
38+
PiExpTimes(x::Real) = PiExpTimes(x, 0)
3839

3940
"""
4041
Pi
@@ -86,36 +87,39 @@ Base.:(==)(p::PiExpTimes, x::Real) = p == PiExpTimes(x, 0)
8687
Base.:(==)(x::Real, p::PiExpTimes) = p == x
8788

8889
simplify(p) = p
90+
simplify(p::PiExpTimes{Irrational{:π}}) = PiExpTimes(1, p.n + one(p.n))
8991
simplify(p::PiExpTimes{<:PiExpTimes}) = simplify(p.x) * Pi^(p.n)
9092

9193
for f in [:(<), :(==)]
9294
@eval Base.$f(p::PiExpTimes, ::Irrational{:π}) = $f(p, Pi)
9395
@eval Base.$f(::Irrational{:π}, p::PiExpTimes) = $f(Pi, p)
9496
end
9597

96-
Base.promote_rule(::Type{Irrational{:π}}, ::Type{PiExpTimes{T}}) where {T} = PiExpTimes{T}
97-
Base.promote_rule(::Type{PiExpTimes{T}}, ::Type{Irrational{:π}}) where {T} = PiExpTimes{T}
98+
Base.promote_rule(::Type{Irrational{:π}}, ::Type{PiExpTimes{T,N}}) where {T,N} = PiExpTimes{T,N}
99+
Base.promote_rule(::Type{PiExpTimes{T,N}}, ::Type{Irrational{:π}}) where {T,N} = PiExpTimes{T,N}
100+
101+
Base.promote_rule(::Type{PiExpTimes{T,N}}, ::Type{Bool}) where {T,N} = PiExpTimes{T,N}
102+
Base.promote_rule(::Type{PiExpTimes{T,N}}, ::Type{S}) where {T,S<:AbstractIrrational,N} = PiExpTimes{promote_type(S,T),N}
103+
Base.promote_rule(::Type{PiExpTimes{T,N}}, ::Type{S}) where {T,S<:Real,N} = PiExpTimes{promote_type(S,T),N}
104+
Base.promote_rule(::Type{PiExpTimes{T,N}}, ::Type{Float16}) where {T,N} = PiExpTimes{promote_type(Float16,T),N}
105+
Base.promote_rule(::Type{PiExpTimes{T,N}}, ::Type{Float32}) where {T,N} = PiExpTimes{promote_type(Float32,T),N}
98106

99-
Base.promote_rule(::Type{PiExpTimes{T}}, ::Type{Bool}) where {T} = PiExpTimes{T}
100-
Base.promote_rule(::Type{PiExpTimes{T}}, ::Type{S}) where {T,S<:AbstractIrrational} = PiExpTimes{promote_type(S,T)}
101-
Base.promote_rule(::Type{PiExpTimes{T}}, ::Type{S}) where {T,S<:Real} = PiExpTimes{promote_type(S,T)}
102-
Base.promote_rule(::Type{PiExpTimes{T}}, ::Type{Float16}) where {T} = PiExpTimes{promote_type(Float16,T)}
103-
Base.promote_rule(::Type{PiExpTimes{T}}, ::Type{Float32}) where {T} = PiExpTimes{promote_type(Float32,T)}
107+
Base.promote_rule(::Type{BigFloat}, ::Type{PiExpTimes{T,N}}) where {T,N} = PiExpTimes{promote_type(BigFloat,T),N}
104108

105-
Base.promote_rule(::Type{PiExpTimes{T}}, ::Type{PiExpTimes{S}}) where {T,S} = PiExpTimes{promote_type(T, S)}
109+
Base.promote_rule(::Type{PiExpTimes{T,N}}, ::Type{PiExpTimes{S,M}}) where {T,S,N,M} = PiExpTimes{promote_type(T, S),promote_type(M, N)}
106110

107-
Base.promote_rule(::Type{PiExpTimes{T}}, ::Type{Complex{S}}) where {T<:Real,S<:Real} = Complex{PiExpTimes{promote_type(T, S)}}
111+
Base.promote_rule(::Type{PiExpTimes{T,N}}, ::Type{Complex{S}}) where {T<:Real,S<:Real,N} = Complex{PiExpTimes{promote_type(T, S),N}}
112+
113+
Base.AbstractFloat(p::PiExpTimes{T,N}) where {T,N} = promote_type(T,N,Float64)(p)
108114

109115
for T in (:Float64,:Float32,:Float16)
110116
@eval begin
111117
function Base.$T(p::PiExpTimes)
112118
n = p.n
113-
if n == 0
119+
if iszero(n)
114120
return $T(p.x)
115-
elseif n > 0
116-
return $T(p.x*π^n)
117121
else
118-
return $T(p.x*inv(float(π))^-n)
122+
return $T(p.x*float(pi)^n)
119123
end
120124
end
121125
end
@@ -125,40 +129,41 @@ end
125129
# Convert π to BigFloat before multiplying
126130
function Base.BigFloat(p::PiExpTimes)
127131
n = p.n
128-
if n == 0
132+
if iszero(n)
129133
return BigFloat(p.x)
130-
elseif n > 0
131-
return BigFloat(p.x)*BigFloat(π)^n
132134
else
133-
return BigFloat(p.x)*inv(BigFloat(π))^-n
135+
return BigFloat(p.x*BigFloat(π)^n)
134136
end
135137
end
136138

137-
for f in (:iszero, :isfinite, :isnan)
138-
@eval Base.$f(p::PiExpTimes) = $f(p.x)
139-
end
139+
Base.iszero(p::PiExpTimes{<:Real,Int}) = iszero(p.x)
140+
Base.iszero(p::PiExpTimes) = iszero(float(p))
141+
Base.isfinite(p::PiExpTimes) = isfinite(float(p))
142+
Base.isnan(p::PiExpTimes) = isnan(float(p))
140143

141144
# Unfortunately Irrational numbers do not have a multiplicative identity of the same type,
142145
# so we make do with something that works
143-
Base.one(::Type{PiExpTimes}) = true
146+
Base.one(::Type{<:PiExpTimes}) = true
144147
Base.isone(p::PiExpTimes) = (x = simplify(p); isone(x.x) && iszero(x.n))
145148

146-
Base.zero(p::PiExpTimes{T}) where {T} = PiExpTimes{T}(zero(T), p.n)
147-
Base.zero(::Type{PiExpTimes{T}}) where {T} = PiExpTimes{T}(zero(T))
149+
Base.zero(p::PiExpTimes{T,N}) where {T,N} = PiExpTimes{T,N}(zero(T), p.n)
150+
Base.zero(::Type{PiExpTimes{T,N}}) where {T,N} = PiExpTimes{T,N}(zero(T), zero(N))
148151

149152
Base.sign(p::PiExpTimes) = sign(p.x)
150153
Base.signbit(p::PiExpTimes) = signbit(p.x)
151154

152155
# Define trigonometric functions
153156

154157
# this is type-unstable, but Union splitting does its magic
155-
@inline dividebypiexp(p::PiExpTimes, n) = p.n == n ? p.x : p.x * float(π)^(p.n-n)
158+
@inline dividebypiexp(p::PiExpTimes, n) = p.n == n ? p.x : p.x * π^float(p.n-n)
156159
@inline dividebypi(p) = dividebypiexp(p, 1)
157160

158-
Base.sin(p::PiExpTimes) = p.n == 0 ? sin(p.x) : sinpi(dividebypi(p))
159-
Base.cos(p::PiExpTimes) = p.n == 0 ? cos(p.x) : cospi(dividebypi(p))
161+
@inline _check_exp_apply(f, fzeroexp, p) = iszero(p.n) ? fzeroexp(p.x) : f(dividebypi(p))
162+
Base.sin(p::PiExpTimes) = _check_exp_apply(sinpi, sin, p)
163+
# Base.sin(p::PiExpTimes) = iszero(p.n) ? sin(p.x) : sinpi(dividebypi(p))
164+
Base.cos(p::PiExpTimes) = _check_exp_apply(cospi, cos, p)
160165
if VERSION > v"1.6.0"
161-
Base.sincos(p::PiExpTimes) = p.n == 0 ? sincos(p.x) : sincospi(dividebypi(p))
166+
Base.sincos(p::PiExpTimes) = _check_exp_apply(sincospi, sincos, p)
162167
else
163168
Base.sincos(p::PiExpTimes) = (sin(p),cos(p))
164169
end
@@ -170,7 +175,7 @@ end
170175

171176
@inline function Base.tan(p::PiExpTimes{T}) where {T}
172177
Tf = float(T)
173-
if simplify(p).n == 1
178+
if isone(simplify(p).n)
174179
r = abs(rem(p.x, one(p.x)))
175180
iszero(r) && return copysign(zero(Tf), p.x)
176181
2r == one(r) && return copysign(Tf(Inf), p.x)
@@ -224,7 +229,15 @@ end
224229
Base.:(*)(b::Bool, p::PiExpTimes) = ifelse(b, p, flipsign(zero(p), p.x))
225230
Base.:(/)(b::Bool, p::PiExpTimes) = PiExpTimes(b / p.x, -p.n)
226231

227-
Base.:(^)(p::PiExpTimes, n::Integer) = PiExpTimes(p.x^n, n*p.n)
232+
Base.:(^)(p::PiExpTimes, q::PiExpTimes) = PiExpTimes(float(p.x)^oftype(float(p.x), q), q*p.n)
233+
Base.:(^)(::Irrational{:π}, p::PiExpTimes) = Pi^p
234+
Base.:(^)(p::PiExpTimes, ::Irrational{:π}) = PiExpTimes(p.x^π, Pi*p.n)
235+
_default_exp(p, n) = PiExpTimes(p.x^n, n*p.n)
236+
# These methods need to be defined to handle ambiguities
237+
Base.:(^)(p::PiExpTimes, n::AbstractIrrational) = _default_exp(p, n)
238+
Base.:(^)(p::PiExpTimes, n::Rational) = _default_exp(p, n)
239+
Base.:(^)(p::PiExpTimes, n::Integer) = _default_exp(p, n)
240+
Base.:(^)(p::PiExpTimes, n::Real) = _default_exp(p, n)
228241

229242
# Rational divide
230243
Base.:(//)(p::PiExpTimes, q::PiExpTimes) = PiExpTimes(p.x//q.x, p.n - q.n)
@@ -236,6 +249,10 @@ Base.:(//)(::Irrational{:π}, p::PiExpTimes) = PiExpTimes(1//p.x, -p.n+1)
236249
# Pretty-printing
237250

238251
function Base.show(io::IO, p::PiExpTimes)
252+
if !isfinite(p)
253+
print(io, float(p))
254+
return
255+
end
239256
x = p.x
240257

241258
expstr(p) = isone(p.n) ? "Pi" : "Pi^"*string(p.n)

0 commit comments

Comments
 (0)