Skip to content

Commit 1bbf9de

Browse files
committed
added PiExpTimes to support numbers of the form x*pi^n for integer exponents
1 parent 5a98d42 commit 1bbf9de

File tree

4 files changed

+946
-219
lines changed

4 files changed

+946
-219
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.2.1"
4+
version = "0.3.0"
55

66
[compat]
77
julia = "1"

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,5 @@ pkg> add MultiplesOfPi
9191
# Related packages
9292

9393
- [IrrationalExpressions.jl](https://github.com/jishnub/IrrationalExpressions.jl.git)
94-
- [Tau.jl](https://github.com/JuliaMath/Tau.jl)
94+
- [Tau.jl](https://github.com/JuliaMath/Tau.jl)
95+
- [UnitfulAngles.jl](https://github.com/yakir12/UnitfulAngles.jl)

src/MultiplesOfPi.jl

Lines changed: 227 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,100 @@
11
module MultiplesOfPi
22

3+
export PiExpTimes
34
export PiTimes
45
export Pi
56

67
"""
7-
PiTimes(x)
8+
PiExpTimes(x)
89
9-
Construct a number that behaves as `π*x` for a real `x`
10+
Construct a number that behaves as `x*π^n` for a real `x` and an integer `n`
1011
"""
11-
struct PiTimes{T<:Real} <: AbstractIrrational
12+
struct PiExpTimes{N,T<:Real} <: AbstractIrrational
1213
x :: T
14+
function PiExpTimes{N,T}(x) where {N,T<:Real}
15+
@assert N isa Integer
16+
new{N,T}(T(x))
17+
end
1318
end
1419

20+
# Type T is inferred from x
21+
PiExpTimes{N}(x::T) where {N,T<:Real} = PiExpTimes{N,T}(x)
22+
PiExpTimes{0}(x::Real) = x
23+
PiExpTimes{0}(p::PiExpTimes) = p
24+
25+
PiExpTimes{N}(p::PiExpTimes{M}) where {N,M} = PiExpTimes{N+M}(p.x)
26+
PiExpTimes{N,T}(p::PiExpTimes{N,T}) where {N,T<:Real} = PiExpTimes{2N}(p.x)
27+
PiExpTimes{N,T}(p::PiExpTimes{N}) where {N,T<:Real} = PiExpTimes{2N}(T(p.x))
28+
29+
PiExpTimes{N}(::Irrational{:π}) where {N} = PiExpTimes{N+1}(1)
30+
PiExpTimes{N,Irrational{:π}}(::Irrational{:π}) where {N} = PiExpTimes{N+1}(1)
31+
32+
"""
33+
PiTimes(x)
34+
35+
Construct a number that behaves as `x*π` for a real `x`. `PiTimes` is an alias for
36+
`PiExpTimes{1}`
37+
"""
38+
const PiTimes{T<:Real} = PiExpTimes{1,T}
39+
1540
"""
1641
Pi
1742
1843
The number `PiTimes(1)`, numerically equivalent to `pi`
1944
"""
2045
const Pi = PiTimes(1)
2146

22-
Base.:(<)(p::PiTimes,q::PiTimes) = p.x < q.x
23-
Base.:(==)(p::PiTimes,q::PiTimes) = p.x == q.x
24-
Base.:(==)(p::PiTimes,y::Real) = float(p) == y
25-
Base.:(==)(y::Real,p::PiTimes) = float(p) == y
47+
Base.:(<)(p::PiExpTimes{N},q::PiExpTimes{N}) where {N} = p.x < q.x
48+
function Base.:(<)(p::PiExpTimes{M},q::PiExpTimes{N}) where {M,N}
49+
p.x == q.x ? M < N : float(p) < float(q)
50+
end
51+
52+
Base.:(<)(p::PiTimes,::Irrational{:π}) = p.x < one(p.x)
53+
Base.:(<)(::Irrational{:π},p::PiTimes) = p.x > one(p.x)
54+
55+
Base.:(==)(p::PiExpTimes{N},q::PiExpTimes{N}) where {N} = p.x == q.x
56+
function Base.:(==)(p::PiExpTimes,q::PiExpTimes)
57+
iszero(p.x) && iszero(q.x) ? true : float(p) == float(q)
58+
end
59+
Base.:(==)(p::PiExpTimes,y::Real) = float(p) == y
60+
Base.:(==)(y::Real,p::PiExpTimes) = float(p) == y
61+
62+
Base.:(==)(::Irrational{:π},p::PiExpTimes) = false
63+
Base.:(==)(p::PiExpTimes,::Irrational{:π}) = false
2664

2765
Base.:(==)(p::PiTimes,::Irrational{:π}) = isone(p.x)
2866
Base.:(==)(::Irrational{:π},p::PiTimes) = isone(p.x)
2967

3068
for T in (:BigFloat,:Float64,:Float32,:Float16)
31-
@eval Base.$T(p::PiTimes) = π*$T(p.x)
69+
@eval begin
70+
function Base.$T(p::PiExpTimes{N}) where {N}
71+
if N >= 0
72+
y = foldl(*,(pi for i=1:N),init=one($T))
73+
return y*$T(p.x)
74+
else
75+
y = foldl(/,(pi for i=1:-N),init=one($T))
76+
return y*$T(p.x)
77+
end
78+
end
79+
Base.$T(p::PiExpTimes{0}) = $T(p.x)
80+
end
3281
end
3382

3483
for f in (:iszero,:isfinite,:isnan)
35-
@eval Base.$f(p::PiTimes) = $f(p.x)
84+
@eval Base.$f(p::PiExpTimes) = $f(p.x)
3685
end
3786

3887
# Unfortunately Irrational numbers do not have a multiplicative identity of the same type,
3988
# so we make do with something that works
40-
Base.one(::PiTimes{T}) where {T} = one(PiTimes{T})
41-
Base.one(::Type{PiTimes{T}}) where {T} = true
89+
Base.one(::T) where {T<:PiExpTimes} = one(T)
90+
Base.one(::Type{<:PiExpTimes}) = true
91+
Base.one(::Type{<:PiExpTimes{0,T}}) where {T} = one(T)
4292

43-
Base.zero(::PiTimes{T}) where {T} = zero(PiTimes{T})
44-
Base.zero(::Type{PiTimes{T}}) where {T} = PiTimes(zero(T))
93+
Base.zero(::T) where {T<:PiExpTimes} = zero(T)
94+
Base.zero(::Type{PiExpTimes{N,T}}) where {N,T} = PiExpTimes{N}(zero(T))
95+
96+
Base.sign(p::PiExpTimes) = sign(p.x)
97+
Base.signbit(p::PiExpTimes) = signbit(p.x)
4598

4699
# Define trigonometric functions
47100

@@ -51,6 +104,9 @@ Base.zero(::Type{PiTimes{T}}) where {T} = PiTimes(zero(T))
51104

52105
@inline Base.cis(p::PiTimes) = Complex(cos(p),sin(p))
53106

107+
Base.sinc(p::PiExpTimes) = sinc(float(p))
108+
Base.sinc(p::PiExpTimes{0}) = sinc(p.x)
109+
54110
function Base.tan(p::PiTimes{T}) where {T}
55111
iszero(p.x) && return p.x
56112
r = abs(rem(p.x,one(p.x)))
@@ -62,69 +118,191 @@ end
62118

63119
# Hyperbolic functions
64120

65-
function Base.tanh(z::Complex{PiTimes{T}}) where {T}
121+
function Base.tanh(z::Complex{<:PiTimes})
66122
iszero(real(z)) && return im*tan(imag(z))
67123
tanh(float(z))
68124
end
69125

70126
# Arithmetic operators
71127

72-
Base.:(+)(p1::PiTimes,p2::PiTimes) = PiTimes(p1.x + p2.x)
128+
Base.:(+)(p1::PiExpTimes{N},p2::PiExpTimes{N}) where {N} = PiExpTimes{N}(p1.x + p2.x)
129+
Base.:(+)(p1::PiExpTimes{0},p2::PiExpTimes{0}) = p1.x + p2.x
130+
73131
Base.:(+)(p::PiTimes,::Irrational{:π}) = PiTimes(p.x + one(p.x))
74132
Base.:(+)(::Irrational{:π},p::PiTimes) = PiTimes(p.x + one(p.x))
75133

76-
Base.:(-)(p::PiTimes) = PiTimes(-p.x)
77-
Base.:(-)(p1::PiTimes,p2::PiTimes) = PiTimes(p1.x-p2.x)
134+
Base.:(-)(p::PiExpTimes{N}) where {N} = PiExpTimes{N}(-p.x)
135+
Base.:(-)(p::PiExpTimes{0}) = -p.x
136+
137+
Base.:(-)(p1::PiExpTimes{N},p2::PiExpTimes{N}) where {N} = PiExpTimes{N}(p1.x-p2.x)
138+
Base.:(-)(p1::PiExpTimes{0},p2::PiExpTimes{0}) = p1.x-p2.x
139+
78140
Base.:(-)(p::PiTimes,::Irrational{:π}) = PiTimes(p.x - one(p.x))
79141
Base.:(-)(::Irrational{:π},p::PiTimes) = PiTimes(one(p.x) - p.x)
80142

81-
Base.:(/)(p1::PiTimes,p2::PiTimes) = p1.x/p2.x
82-
Base.:(/)(p1::PiTimes,y::Real) = PiTimes(p1.x/y)
143+
Base.:(/)(p1::PiExpTimes{N},p2::PiExpTimes{N}) where {N} = p1.x/p2.x
144+
Base.:(/)(p1::PiExpTimes{M},p2::PiExpTimes{N}) where {M,N} = PiExpTimes{M-N}(p1.x/p2.x)
83145

84-
Base.:(/)(::Irrational{:π},p::PiTimes) = inv(float(p.x))
85-
Base.:(/)(p::PiTimes,::Irrational{:π}) = float(p.x)
146+
Base.:(/)(y::Real,p::PiExpTimes) = y*inv(p)
147+
Base.:(/)(p1::PiExpTimes{N},y::Real) where {N} = PiExpTimes{N}(p1.x/y)
86148

87-
Base.:(*)(p1::PiTimes,y::Real) = PiTimes(p1.x*y)
88-
Base.:(*)(y::Real,p1::PiTimes) = PiTimes(p1.x*y)
89-
Base.:(*)(p1::PiTimes,p2::PiTimes) = float(p1) * float(p2)
90-
Base.:(*)(z::Complex{Bool},p::PiTimes) = Complex(real(z)*p,imag(z)*p)
91-
Base.:(*)(p::PiTimes,z::Complex{Bool}) = Complex(real(z)*p,imag(z)*p)
92-
Base.:(*)(x::Bool,p::PiTimes) = ifelse(x,p,zero(p))
93-
Base.:(*)(p::PiTimes,x::Bool) = ifelse(x,p,zero(p))
149+
Base.:(/)(::Irrational{:π},p::PiExpTimes{N}) where {N} = PiExpTimes{1-N}(inv(p.x))
150+
Base.:(/)(::Irrational{:π},p::PiTimes) = inv(p.x)
151+
Base.:(/)(p::PiExpTimes{N},::Irrational{:π}) where {N} = PiExpTimes{N-1}(p.x)
152+
Base.:(/)(p::PiTimes,::Irrational{:π})= p.x
94153

95-
for op in Symbol[:+,:-,:/,:*]
96-
@eval Base.$op(p::PiTimes,x::AbstractIrrational) = $op(Float64(p),Float64(x))
97-
@eval Base.$op(x::AbstractIrrational,p::PiTimes) = $op(Float64(x),Float64(p))
154+
function Base.:(/)(p::PiExpTimes{N},z::Complex{<:PiExpTimes{M}}) where {M,N}
155+
are,aim = p.x, zero(p.x)
156+
bre,bim = real(z).x, imag(z).x
157+
Complex(are,aim)/Complex(bre,bim) * PiExpTimes{N-M}(1)
98158
end
99159

100-
Base.:(//)(p::PiTimes,n) = PiTimes(p.x//n)
160+
function Base.:(/)(p::PiExpTimes{N},z::Complex{<:PiExpTimes{N}}) where {N}
161+
are,aim = p.x, zero(p.x)
162+
bre,bim = real(z).x, imag(z).x
163+
Complex(are,aim)/Complex(bre,bim)
164+
end
101165

102-
# Conversion and promotion
166+
function Base.:(/)(a::Complex{<:PiExpTimes{N}},b::Complex{<:Real}) where {N}
167+
are,aim = real(a).x, imag(a).x
168+
bre,bim = reim(b)
169+
Complex(are,aim)/Complex(bre,bim) * PiExpTimes{N}(1)
170+
end
171+
172+
function Base.:(/)(a::Complex{<:PiExpTimes{N}},b::Complex{<:PiExpTimes{M}}) where {M,N}
173+
are,aim = real(a).x, imag(a).x
174+
bre,bim = real(b).x, imag(b).x
175+
Complex(are,aim)/Complex(bre,bim) * PiExpTimes{N-M}(1)
176+
end
177+
178+
function Base.:(/)(a::Complex{<:PiExpTimes{N}},b::Complex{<:PiExpTimes{N}}) where {N}
179+
are,aim = real(a).x, imag(a).x
180+
bre,bim = real(b).x, imag(b).x
181+
Complex(are,aim)/Complex(bre,bim)
182+
end
183+
184+
Base.inv(p::PiExpTimes{N}) where {N} = PiExpTimes{-N}(inv(p.x))
103185

104-
for t in (Int8, Int16, Int32, Int64, Int128, Bool, UInt8, UInt16, UInt32, UInt64, UInt128)
105-
@eval Base.promote_rule(::Type{PiTimes{Float16}}, ::Type{PiTimes{$t}}) = PiTimes{Float16}
106-
@eval Base.promote_rule(::Type{PiTimes{$t}},::Type{PiTimes{Float16}}) = PiTimes{Float16}
186+
Base.:(*)(p1::PiExpTimes{N},y::Real) where {N} = PiExpTimes{N}(p1.x*y)
187+
Base.:(*)(y::Real,p1::PiExpTimes{N}) where {N} = PiExpTimes{N}(p1.x*y)
188+
189+
Base.:(*)(b::Bool,p::PiExpTimes) = ifelse(b,p,flipsign(zero(p),p.x))
190+
Base.:(*)(p::PiExpTimes,b::Bool) = ifelse(b,p,flipsign(zero(p),p.x))
191+
192+
function Base.:(*)(p1::PiExpTimes{M},p2::PiExpTimes{N}) where {M,N}
193+
PiExpTimes{M+N}(p1.x*p2.x)
194+
end
195+
196+
Base.:(*)(z::Complex{Bool},p::PiExpTimes) = Complex(real(z)*p,imag(z)*p)
197+
Base.:(*)(p::PiExpTimes,z::Complex{Bool}) = Complex(real(z)*p,imag(z)*p)
198+
199+
Base.:(*)(::Irrational{:π},p::PiExpTimes) = PiTimes(p)
200+
Base.:(*)(p::PiExpTimes,::Irrational{:π}) = PiTimes(p)
201+
202+
# Not type-stable!
203+
Base.:(^)(p::PiExpTimes{N},n::Integer) where {N} = PiExpTimes{N*n}(p.x^n)
204+
205+
for op in Symbol[:/,:*]
206+
@eval Base.$op(p::PiExpTimes,x::AbstractIrrational) = $op(Float64(p),Float64(x))
207+
@eval Base.$op(x::AbstractIrrational,p::PiExpTimes) = $op(Float64(x),Float64(p))
208+
end
209+
210+
# Rational divide
211+
Base.:(//)(p::PiExpTimes{N}, n::Real) where {N} = PiExpTimes{N}(p.x//n)
212+
Base.:(//)(n::Real, p::PiExpTimes{N}) where {N} = PiExpTimes{-N}(n//p.x)
213+
Base.:(//)(p::PiExpTimes{M},q::PiExpTimes{N}) where {M,N} = PiExpTimes{M-N}(p.x//q.x)
214+
Base.:(//)(p::PiExpTimes{N},q::PiExpTimes{N}) where {N} = p.x//q.x
215+
216+
Base.:(//)(::Irrational{:π},p::PiExpTimes{N}) where {N} = PiExpTimes{1-N}(1//p.x)
217+
Base.:(//)(p::PiExpTimes{N},::Irrational{:π}) where {N} = PiExpTimes{N-1}(p.x//1)
218+
Base.:(//)(::Irrational{:π},p::PiTimes) = 1//p.x
219+
Base.:(//)(p::PiTimes,::Irrational{:π}) = p.x//1
220+
221+
Base.:(//)(p::Complex{<:PiExpTimes},q::PiExpTimes) = Complex(real(p)//q,imag(p)//q)
222+
223+
function Base.:(//)(p::PiExpTimes{N},z::Complex{<:PiExpTimes{M}}) where {M,N}
224+
are,aim = p.x, zero(p.x)
225+
bre,bim = real(z).x, imag(z).x
226+
Complex(are,aim)//Complex(bre,bim) * PiExpTimes{N-M}(1)
227+
end
228+
229+
function Base.:(//)(p::PiExpTimes{N},z::Complex{<:PiExpTimes{N}}) where {N}
230+
are,aim = p.x, zero(p.x)
231+
bre,bim = real(z).x, imag(z).x
232+
Complex(are,aim)//Complex(bre,bim)
107233
end
108234

109-
for t1 in (Float32, Float64)
110-
for t2 in (Int8, Int16, Int32, Int64, Bool, UInt8, UInt16, UInt32, UInt64)
111-
@eval begin
112-
Base.promote_rule(::Type{PiTimes{$t1}}, ::Type{PiTimes{$t2}}) = PiTimes{$t1}
113-
Base.promote_rule(::Type{PiTimes{$t2}}, ::Type{PiTimes{$t1}}) = PiTimes{$t1}
114-
end
115-
end
235+
function Base.:(//)(a::Complex{<:PiExpTimes{N}},b::Complex{<:PiExpTimes{M}}) where {M,N}
236+
are,aim = real(a).x, imag(a).x
237+
bre,bim = real(b).x, imag(b).x
238+
Complex(are,aim)//Complex(bre,bim) * PiExpTimes{N-M}(1)
239+
end
240+
241+
function Base.:(//)(a::Complex{<:PiExpTimes{N}},b::Complex{<:PiExpTimes{N}}) where {N}
242+
are,aim = real(a).x, imag(a).x
243+
bre,bim = real(b).x, imag(b).x
244+
Complex(are,aim)//Complex(bre,bim)
245+
end
246+
247+
# Conversion and promotion
248+
function Base.promote_rule(::Type{PiExpTimes{N,R}},::Type{PiExpTimes{N,S}}) where {N,R,S}
249+
PiExpTimes{N,promote_type(R,S)}
116250
end
117251

118252
Base.promote_rule(::Type{PiTimes{T}}, ::Type{Irrational{:π}}) where {T} = PiTimes{T}
119253
Base.promote_rule(::Type{Irrational{:π}}, ::Type{PiTimes{T}}) where {T} = PiTimes{T}
120254

121-
Base.convert(::Type{PiTimes},x::Real) = PiTimes(x/π)
122-
Base.convert(::Type{PiTimes{T}},x::Real) where {T} = PiTimes{T}(x/π)
123-
Base.convert(::Type{PiTimes{T}},p::PiTimes) where {T} = PiTimes{T}(p.x)
255+
function Base.promote_rule(::Type{Complex{PiTimes{T}}}, ::Type{Irrational{:π}}) where {T}
256+
Complex{PiTimes{T}}
257+
end
258+
function Base.promote_rule(::Type{Irrational{:π}}, ::Type{Complex{PiTimes{T}}}) where {T}
259+
Complex{PiTimes{T}}
260+
end
124261

125-
function Base.show(io::IO,p::PiTimes)
126-
pxstr = isone(p.x) ? "" : string(p.x)
127-
str = iszero(p) ? string(p.x) : pxstr*"Pi"
262+
# The choices for conversion aren't unique as π^n can not be computed
263+
# for negative integers. Here we resort to
264+
function Base.convert(::Type{PiExpTimes{N}},x::Real) where {N}
265+
den = N < 0 ? π^float(N) : π^N
266+
PiExpTimes{N}(x/den)
267+
end
268+
function Base.convert(::Type{PiExpTimes{N,T}},x::Real) where {N,T}
269+
den = N < 0 ? π^float(N) : π^N
270+
PiExpTimes{N}(T(x/den))
271+
end
272+
function Base.convert(::Type{PiExpTimes{N}},::Irrational{:π}) where {N}
273+
den = N < 1 ? π^float(N-1) : π^(N-1)
274+
PiExpTimes{N}(1/den)
275+
end
276+
function Base.convert(::Type{PiExpTimes{N,T}},::Irrational{:π}) where {N,T}
277+
den = N < 1 ? π^float(N-1) : π^(N-1)
278+
PiExpTimes{N}(T(1/den))
279+
end
280+
function Base.convert(::Type{PiExpTimes{N}},p::PiExpTimes{M}) where {M,N}
281+
den = N < M ? π^float(N-M) : π^(N-M)
282+
PiExpTimes{N}(p.x/den)
283+
end
284+
function Base.convert(::Type{PiExpTimes{N,T}},p::PiExpTimes{M}) where {M,N,T}
285+
den = N < M ? π^float(N-M) : π^(N-M)
286+
PiExpTimes{N}(T(p.x/den))
287+
end
288+
Base.convert(::Type{PiExpTimes{N}},p::PiExpTimes{N}) where {N} = p
289+
Base.convert(::Type{PiExpTimes{N,T}},p::PiExpTimes{N}) where {N,T} = PiExpTimes{N}(T(p.x))
290+
Base.convert(::Type{PiExpTimes{N,T}},p::PiExpTimes{N,T}) where {N,T} = p
291+
292+
function Base.show(io::IO,p::PiExpTimes{N}) where {N}
293+
x = p.x
294+
function expstr(p)
295+
ifelse(isone(N),"Pi","Pi^"*string(N))
296+
end
297+
298+
xstrexp(x,p) = isone(x) ? expstr(p) : string(x)*"*"*expstr(p)
299+
xstrexp(x::Integer,p) = isone(x) ? expstr(p) : string(x)*expstr(p)
300+
xstrexp(x::AbstractFloat,p) = isone(x) ? expstr(p) :
301+
isinf(x) || isnan(x) ? string(x) :
302+
string(x)*"*"*expstr(p)
303+
xstrexp(x::Rational,p) = "("*string(x)*")"*expstr(p)
304+
305+
str = iszero(x) ? string(x) : xstrexp(x,p)
128306
print(io,str)
129307
end
130308

0 commit comments

Comments
 (0)