Skip to content

Commit f67c819

Browse files
dpsanderslbenet
authored andcommitted
Faster trig functions (#235)
* Speed up sin. sin(bigfloat interval) doesn't work; put back the old function * Rewrite tan to use quadrant * Add back old version of sin and specialise new version to Interval{Float64} * Add back old version of tan and specialize new version to Float64 * Comment out testset for large arguments that is no longer valid * Fast version of cos for Float64 * Include half_pi in quadrant 0 * Kick CI * Remove find_quadrants(x::Float64) which is no longer used (replaced by quadrant) * Uncomment and correct tests for trig of large arguments
1 parent 4835e16 commit f67c819

File tree

2 files changed

+128
-10
lines changed

2 files changed

+128
-10
lines changed

src/intervals/trigonometric.jl

Lines changed: 125 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ range_atan(::Type{T}) where {T<:Real} = Interval(-(pi_interval(T).hi), pi_interv
2121
half_range_atan(::Type{T}) where {T} = (temp = half_pi(T); Interval(-(temp.hi), temp.hi) )
2222
pos_range_atan(::Type{T}) where {T<:Real} = Interval(zero(T), pi_interval(T).hi)
2323

24+
const halfpi = pi / 2.0
2425

2526
"""Finds the quadrant(s) corresponding to a given floating-point
2627
number. The quadrants are labelled as 0 for x ∈ [0, π/2], etc.
@@ -37,13 +38,6 @@ function find_quadrants(x::T) where {T}
3738
return SVector(floor(temp.lo), floor(temp.hi))
3839
end
3940

40-
function find_quadrants(x::Float64)
41-
temp = multiply_by_positive_constant(x, one_over_half_pi_interval)
42-
# x / half_pi(Float64)
43-
44-
return SVector(floor(temp.lo), floor(temp.hi))
45-
end
46-
4741
function sin(a::Interval{T}) where T
4842
isempty(a) && return a
4943

@@ -89,6 +83,60 @@ function sin(a::Interval{T}) where T
8983
end
9084
end
9185

86+
function quadrant(x::Float64)
87+
x_mod2pi = rem2pi(x, RoundNearest) # gives result in [-pi, pi]
88+
89+
x_mod2pi < -halfpi && return (2, x_mod2pi)
90+
x_mod2pi < 0 && return (3, x_mod2pi)
91+
x_mod2pi <= halfpi && return (0, x_mod2pi)
92+
93+
return 1, x_mod2pi
94+
end
95+
96+
97+
function sin(a::Interval{Float64})
98+
99+
T = Float64
100+
101+
isempty(a) && return a
102+
103+
whole_range = Interval{T}(-1, 1)
104+
105+
diam(a) > two_pi(T).lo && return whole_range
106+
107+
lo_quadrant, lo = quadrant(a.lo)
108+
hi_quadrant, hi = quadrant(a.hi)
109+
110+
lo, hi = a.lo, a.hi # should be able to use the modulo version of a, but doesn't seem to work
111+
112+
# Different cases depending on the two quadrants:
113+
if lo_quadrant == hi_quadrant
114+
a.hi - a.lo > pi_interval(T).lo && return whole_range #
115+
116+
if lo_quadrant == 1 || lo_quadrant == 2
117+
# negative slope
118+
return @round(sin(hi), sin(lo))
119+
else
120+
return @round(sin(lo), sin(hi))
121+
end
122+
123+
elseif lo_quadrant==3 && hi_quadrant==0
124+
return @round(sin(lo), sin(hi))
125+
126+
elseif lo_quadrant==1 && hi_quadrant==2
127+
return @round(sin(hi), sin(lo))
128+
129+
elseif ( lo_quadrant == 0 || lo_quadrant==3 ) && ( hi_quadrant==1 || hi_quadrant==2 )
130+
return @round(min(sin(lo), sin(hi)), 1)
131+
132+
elseif ( lo_quadrant == 1 || lo_quadrant==2 ) && ( hi_quadrant==3 || hi_quadrant==0 )
133+
return @round(-1, max(sin(lo), sin(hi)))
134+
135+
else #if( lo_quadrant == 0 && hi_quadrant==3 ) || ( lo_quadrant == 2 && hi_quadrant==1 )
136+
return whole_range
137+
end
138+
end
139+
92140

93141
function cos(a::Interval{T}) where T
94142
isempty(a) && return a
@@ -131,6 +179,50 @@ function cos(a::Interval{T}) where T
131179
end
132180
end
133181

182+
function cos(a::Interval{Float64})
183+
184+
T = Float64
185+
186+
isempty(a) && return a
187+
188+
whole_range = Interval{Float64}(-one(T), one(T))
189+
190+
diam(a) > two_pi(T).lo && return whole_range
191+
192+
lo_quadrant, lo = quadrant(a.lo)
193+
hi_quadrant, hi = quadrant(a.hi)
194+
195+
lo, hi = a.lo, a.hi
196+
197+
# Different cases depending on the two quadrants:
198+
if lo_quadrant == hi_quadrant # Interval limits in the same quadrant
199+
a.hi - a.lo > pi_interval(T).lo && return whole_range
200+
201+
if lo_quadrant == 2 || lo_quadrant == 3
202+
# positive slope
203+
return @round(cos(lo), cos(hi))
204+
else
205+
return @round(cos(hi), cos(lo))
206+
end
207+
208+
elseif lo_quadrant == 2 && hi_quadrant==3
209+
return @round(cos(a.lo), cos(a.hi))
210+
211+
elseif lo_quadrant == 0 && hi_quadrant==1
212+
return @round(cos(a.hi), cos(a.lo))
213+
214+
elseif ( lo_quadrant == 2 || lo_quadrant==3 ) && ( hi_quadrant==0 || hi_quadrant==1 )
215+
return @round(min(cos(a.lo), cos(a.hi)), 1)
216+
217+
elseif ( lo_quadrant == 0 || lo_quadrant==1 ) && ( hi_quadrant==2 || hi_quadrant==3 )
218+
return @round(-1, max(cos(a.lo), cos(a.hi)))
219+
220+
else #if ( lo_quadrant == 3 && hi_quadrant==2 ) || ( lo_quadrant == 1 && hi_quadrant==0 )
221+
return whole_range
222+
end
223+
end
224+
225+
134226
function find_quadrants_tan(x::T) where {T}
135227
temp = atomic(Interval{T}, x) / half_pi(x)
136228

@@ -166,6 +258,32 @@ function tan(a::Interval{T}) where T
166258
return @round(tan(a.lo), tan(a.hi))
167259
end
168260

261+
function tan(a::Interval{Float64})
262+
263+
T = Float64
264+
265+
isempty(a) && return a
266+
267+
diam(a) > pi_interval(T).lo && return entireinterval(a)
268+
269+
lo_quadrant, lo = quadrant(a.lo)
270+
hi_quadrant, hi = quadrant(a.hi)
271+
272+
lo_quadrant_mod = mod(lo_quadrant, 2)
273+
hi_quadrant_mod = mod(hi_quadrant, 2)
274+
275+
if lo_quadrant_mod == 0 && hi_quadrant_mod == 1
276+
return entireinterval(a) # crosses singularity
277+
278+
elseif lo_quadrant_mod == hi_quadrant_mod && hi_quadrant != lo_quadrant
279+
# must cross singularity
280+
return entireinterval(a)
281+
282+
end
283+
284+
return @round(tan(a.lo), tan(a.hi))
285+
end
286+
169287
function asin(a::Interval{T}) where T
170288

171289
domain = Interval(-one(T), one(T))

test/interval_tests/trig.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,9 @@ end
187187
x = Interval(2.)^1000 # this is a thin interval
188188
@test diam(x) == 0.0
189189

190-
@test sin(x) == -1..1
191-
@test cos(x) == -1..1
192-
@test_skip tan(x) == Interval(-0.16125837995065806, -0.16125837995065803)
190+
@test sin(x) == Interval(-0.15920170308624246, -0.15920170308624243)
191+
@test cos(x) == Interval(0.9872460775989135, 0.9872460775989136)
192+
@test tan(x) == Interval(-0.16125837995065806, -0.16125837995065803)
193193

194194
x = Interval(prevfloat(∞), ∞)
195195
@test sin(x) == -1..1

0 commit comments

Comments
 (0)