1use super::value::{Endpoint, Ival, IvalClass, classify};
2use crate::{
3 interval::core::endpoint_unary,
4 mpfr::{
5 mpfr_cos, mpfr_cosu, mpfr_div, mpfr_floor_inplace, mpfr_get_exp, mpfr_pi,
6 mpfr_round_inplace, mpfr_sin, mpfr_sinu, mpfr_tan, mpfr_tanu, zero,
7 },
8};
9use rug::{Assign, Float, float::Round};
10
11const RANGE_REDUCE_PRECISION_CAP: u32 = 1 << 20;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14enum PeriodClass {
15 TooWide,
16 NearZero,
17 RangeReduce,
18}
19
20fn classify_ival_periodic(x: &Ival, period_quarter_bitlen: i64) -> PeriodClass {
21 let (xlo, xhi) = (x.lo.as_float(), x.hi.as_float());
22
23 if xlo.is_infinite() || xhi.is_infinite() {
24 return PeriodClass::TooWide;
25 }
26
27 let (lo_exp, hi_exp) = (mpfr_get_exp(xlo), mpfr_get_exp(xhi));
28 if lo_exp < period_quarter_bitlen && hi_exp < period_quarter_bitlen {
29 return PeriodClass::NearZero;
30 }
31
32 let lo_ulp = lo_exp.saturating_sub(xlo.prec() as i64);
33 let hi_ulp = hi_exp.saturating_sub(xhi.prec() as i64);
34
35 if lo_ulp > 0 || hi_ulp > 0 {
36 if xlo == xhi {
37 PeriodClass::RangeReduce
38 } else {
39 PeriodClass::TooWide
40 }
41 } else {
42 PeriodClass::RangeReduce
43 }
44}
45
46fn range_reduce_precision(xlo: &Float, xhi: &Float, curr_prec: u32) -> u32 {
47 let lo = (mpfr_get_exp(xlo) + 2 * (xlo.prec() as i64)).max(curr_prec as i64) as u32;
48 let hi = (mpfr_get_exp(xhi) + 2 * (xhi.prec() as i64)).max(curr_prec as i64) as u32;
49 lo.max(hi).min(RANGE_REDUCE_PRECISION_CAP).max(curr_prec)
50}
51
52fn ival_div_pi(x: &Ival, prec: u32, round_fn: fn(&mut Float) -> bool) -> (Float, Float) {
53 let (mut pi_lo, mut pi_hi) = (zero(prec), zero(prec));
54 mpfr_pi(&mut pi_lo, Round::Down);
55 mpfr_pi(&mut pi_hi, Round::Up);
56
57 let (mut q_lo, mut q_hi) = (zero(prec), zero(prec));
58 mpfr_div(x.lo.as_float(), &pi_hi, &mut q_lo, Round::Down);
59 mpfr_div(x.hi.as_float(), &pi_lo, &mut q_hi, Round::Up);
60 round_fn(&mut q_lo);
61 round_fn(&mut q_hi);
62 (q_lo, q_hi)
63}
64
65fn ival_div_n_half(
66 x: &Ival,
67 n: u64,
68 prec: u32,
69 round_fn: fn(&mut Float) -> bool,
70) -> (Float, Float) {
71 let n_half = Float::with_val(prec, n) / 2u32;
72
73 let (mut q_lo, mut q_hi) = (zero(prec), zero(prec));
74 mpfr_div(x.lo.as_float(), &n_half, &mut q_lo, Round::Down);
75 mpfr_div(x.hi.as_float(), &n_half, &mut q_hi, Round::Up);
76 round_fn(&mut q_lo);
77 round_fn(&mut q_hi);
78 (q_lo, q_hi)
79}
80
81fn bfeven(x: &Float) -> bool {
82 let prec = x.prec();
83 let mut t = Float::with_val(prec, x);
84 t /= 2;
85 mpfr_floor_inplace(&mut t);
86 t *= 2;
87 t == *x
88}
89
90#[inline]
91fn bfodd(x: &Float) -> bool {
92 !bfeven(x)
93}
94
95fn bfsub_is_one(a: &Float, b: &Float) -> bool {
96 let prec = a.prec().max(b.prec());
97 let mut d = Float::with_val(prec, b);
98 d -= a;
99 d == 1
100}
101
102fn period_quarter_bitlen(n: u64, divisor: u64) -> i64 {
103 let quarter = n / divisor;
104 if quarter > 0 {
105 (quarter.ilog2() + 1) as i64
106 } else {
107 0
108 }
109}
110
111fn endpoint_min<F>(f: &F, lo: &Endpoint, hi: &Endpoint, out: &mut Float) -> bool
112where
113 F: Fn(&Float, &mut Float, Round) -> bool,
114{
115 let prec = out.prec();
116 let mut tmp = zero(prec);
117
118 let imm_lo = endpoint_unary(f, lo, out, Round::Down);
119 let imm_hi = endpoint_unary(f, hi, &mut tmp, Round::Down);
120
121 if tmp < *out {
122 out.assign(&tmp);
123 imm_hi
124 } else if tmp == *out {
125 imm_lo || imm_hi
126 } else {
127 imm_lo
128 }
129}
130
131fn endpoint_max<F>(f: &F, lo: &Endpoint, hi: &Endpoint, out: &mut Float) -> bool
132where
133 F: Fn(&Float, &mut Float, Round) -> bool,
134{
135 let prec = out.prec();
136 let mut tmp = zero(prec);
137
138 let imm_lo = endpoint_unary(f, lo, out, Round::Up);
139 let imm_hi = endpoint_unary(f, hi, &mut tmp, Round::Up);
140
141 if tmp > *out {
142 out.assign(&tmp);
143 imm_hi
144 } else if tmp == *out {
145 imm_lo || imm_hi
146 } else {
147 imm_lo
148 }
149}
150
151impl Ival {
152 pub fn cos_assign(&mut self, x: &Ival) {
154 self.err = x.err;
155 let (xlo, xhi) = (x.lo.as_float(), x.hi.as_float());
156
157 match classify_ival_periodic(x, 1) {
158 PeriodClass::TooWide => {
159 self.lo.as_float_mut().assign(-1);
160 self.hi.as_float_mut().assign(1);
161 self.lo.immovable = false;
162 self.hi.immovable = false;
163 }
164 PeriodClass::NearZero => match classify(x, false) {
165 IvalClass::Neg => self.monotonic_assign(&mpfr_cos, x),
166 IvalClass::Pos => self.comonotonic_assign(&mpfr_cos, x),
167 IvalClass::Mix => {
168 self.set_prec(x.prec());
169 self.lo.immovable =
170 endpoint_min(&mpfr_cos, &x.lo, &x.hi, self.lo.as_float_mut());
171 self.hi.as_float_mut().assign(1);
172 self.hi.immovable = false;
173 }
174 },
175 PeriodClass::RangeReduce => {
176 let prec = range_reduce_precision(xlo, xhi, x.prec());
177 let (a, b) = ival_div_pi(x, prec, mpfr_floor_inplace);
178
179 if a == b && bfeven(&a) {
180 self.comonotonic_assign(&mpfr_cos, x);
181 } else if a == b && bfodd(&a) {
182 self.monotonic_assign(&mpfr_cos, x);
183 } else if bfsub_is_one(&a, &b) && bfeven(&a) {
184 self.set_prec(x.prec());
185 self.lo.as_float_mut().assign(-1);
186 self.lo.immovable = false;
187 self.hi.immovable =
188 endpoint_max(&mpfr_cos, &x.lo, &x.hi, self.hi.as_float_mut());
189 } else if bfsub_is_one(&a, &b) && bfodd(&a) {
190 self.set_prec(x.prec());
191 self.lo.immovable =
192 endpoint_min(&mpfr_cos, &x.lo, &x.hi, self.lo.as_float_mut());
193 self.hi.as_float_mut().assign(1);
194 self.hi.immovable = false;
195 } else {
196 self.lo.as_float_mut().assign(-1);
197 self.hi.as_float_mut().assign(1);
198 self.lo.immovable = false;
199 self.hi.immovable = false;
200 }
201 }
202 }
203 }
204
205 pub fn sin_assign(&mut self, x: &Ival) {
207 self.err = x.err;
208 let (xlo, xhi) = (x.lo.as_float(), x.hi.as_float());
209
210 match classify_ival_periodic(x, 1) {
211 PeriodClass::TooWide => {
212 self.lo.as_float_mut().assign(-1);
213 self.hi.as_float_mut().assign(1);
214 self.lo.immovable = false;
215 self.hi.immovable = false;
216 }
217 PeriodClass::NearZero => {
218 self.monotonic_assign(&mpfr_sin, x);
219 }
220 PeriodClass::RangeReduce => {
221 let prec = range_reduce_precision(xlo, xhi, x.prec());
222 let (a, b) = ival_div_pi(x, prec, mpfr_round_inplace);
223
224 if a == b && bfeven(&a) {
225 self.monotonic_assign(&mpfr_sin, x);
226 } else if a == b && bfodd(&a) {
227 self.comonotonic_assign(&mpfr_sin, x);
228 } else if bfsub_is_one(&a, &b) && bfodd(&a) {
229 self.set_prec(x.prec());
230 self.lo.as_float_mut().assign(-1);
231 self.lo.immovable = false;
232 self.hi.immovable =
233 endpoint_max(&mpfr_sin, &x.lo, &x.hi, self.hi.as_float_mut());
234 } else if bfsub_is_one(&a, &b) && bfeven(&a) {
235 self.set_prec(x.prec());
236 self.lo.immovable =
237 endpoint_min(&mpfr_sin, &x.lo, &x.hi, self.lo.as_float_mut());
238 self.hi.as_float_mut().assign(1);
239 self.hi.immovable = false;
240 } else {
241 self.lo.as_float_mut().assign(-1);
242 self.hi.as_float_mut().assign(1);
243 self.lo.immovable = false;
244 self.hi.immovable = false;
245 }
246 }
247 }
248 }
249
250 pub fn tan_assign(&mut self, x: &Ival) {
252 let (xlo, xhi) = (x.lo.as_float(), x.hi.as_float());
253 let immovable = x.lo.immovable && x.hi.immovable;
254
255 match classify_ival_periodic(x, 0) {
256 PeriodClass::TooWide => {
257 self.lo.as_float_mut().assign(f64::NEG_INFINITY);
258 self.hi.as_float_mut().assign(f64::INFINITY);
259 self.lo.immovable = immovable;
260 self.hi.immovable = immovable;
261 self.err.partial = true;
262 self.err.total = x.err.total;
263 }
264 PeriodClass::NearZero => {
265 self.monotonic_assign(&mpfr_tan, x);
266 self.err = x.err;
267 }
268 PeriodClass::RangeReduce => {
269 let prec = range_reduce_precision(xlo, xhi, x.prec());
270 let (a, b) = ival_div_pi(x, prec, mpfr_round_inplace);
271
272 if a == b {
273 self.monotonic_assign(&mpfr_tan, x);
274 self.err = x.err;
275 } else {
276 self.lo.as_float_mut().assign(f64::NEG_INFINITY);
277 self.hi.as_float_mut().assign(f64::INFINITY);
278 self.lo.immovable = immovable;
279 self.hi.immovable = immovable;
280 self.err.partial = true;
281 self.err.total = x.err.total;
282 }
283 }
284 }
285 }
286
287 pub(crate) fn cosu_assign(&mut self, x: &Ival, n: u64) {
288 self.err = x.err;
289 let (xlo, xhi) = (x.lo.as_float(), x.hi.as_float());
290 let period_qtr = period_quarter_bitlen(n, 4);
291 let cosu = |x: &Float, out: &mut Float, rnd: Round| mpfr_cosu(x, n, out, rnd);
292
293 match classify_ival_periodic(x, period_qtr) {
294 PeriodClass::TooWide => {
295 self.lo.as_float_mut().assign(-1);
296 self.hi.as_float_mut().assign(1);
297 self.lo.immovable = false;
298 self.hi.immovable = false;
299 }
300 PeriodClass::NearZero => match classify(x, false) {
301 IvalClass::Neg => self.monotonic_assign(&cosu, x),
302 IvalClass::Pos => self.comonotonic_assign(&cosu, x),
303 IvalClass::Mix => {
304 self.set_prec(x.prec());
305 self.lo.immovable = endpoint_min(&cosu, &x.lo, &x.hi, self.lo.as_float_mut());
306 self.hi.as_float_mut().assign(1);
307 self.hi.immovable = false;
308 }
309 },
310 PeriodClass::RangeReduce => {
311 let prec = range_reduce_precision(xlo, xhi, x.prec());
312 let (a, b) = ival_div_n_half(x, n, prec, mpfr_floor_inplace);
313
314 if a == b && bfeven(&a) {
315 self.comonotonic_assign(&cosu, x);
316 } else if a == b && bfodd(&a) {
317 self.monotonic_assign(&cosu, x);
318 } else if bfsub_is_one(&a, &b) && bfeven(&a) {
319 self.set_prec(x.prec());
320 self.lo.as_float_mut().assign(-1);
321 self.lo.immovable = false;
322 self.hi.immovable = endpoint_max(&cosu, &x.lo, &x.hi, self.hi.as_float_mut());
323 } else if bfsub_is_one(&a, &b) && bfodd(&a) {
324 self.set_prec(x.prec());
325 self.lo.immovable = endpoint_min(&cosu, &x.lo, &x.hi, self.lo.as_float_mut());
326 self.hi.as_float_mut().assign(1);
327 self.hi.immovable = false;
328 } else {
329 self.lo.as_float_mut().assign(-1);
330 self.hi.as_float_mut().assign(1);
331 self.lo.immovable = false;
332 self.hi.immovable = false;
333 }
334 }
335 }
336 }
337
338 pub(crate) fn sinu_assign(&mut self, x: &Ival, n: u64) {
339 self.err = x.err;
340 let (xlo, xhi) = (x.lo.as_float(), x.hi.as_float());
341 let period_qtr = period_quarter_bitlen(n, 4);
342 let sinu = |x: &Float, out: &mut Float, rnd: Round| mpfr_sinu(x, n, out, rnd);
343
344 match classify_ival_periodic(x, period_qtr) {
345 PeriodClass::TooWide => {
346 self.lo.as_float_mut().assign(-1);
347 self.hi.as_float_mut().assign(1);
348 self.lo.immovable = false;
349 self.hi.immovable = false;
350 }
351 PeriodClass::NearZero => {
352 self.monotonic_assign(&sinu, x);
353 }
354 PeriodClass::RangeReduce => {
355 let prec = range_reduce_precision(xlo, xhi, x.prec());
356 let (a, b) = ival_div_n_half(x, n, prec, mpfr_round_inplace);
357
358 if a == b && bfeven(&a) {
359 self.monotonic_assign(&sinu, x);
360 } else if a == b && bfodd(&a) {
361 self.comonotonic_assign(&sinu, x);
362 } else if bfsub_is_one(&a, &b) && bfodd(&a) {
363 self.set_prec(x.prec());
364 self.lo.as_float_mut().assign(-1);
365 self.lo.immovable = false;
366 self.hi.immovable = endpoint_max(&sinu, &x.lo, &x.hi, self.hi.as_float_mut());
367 } else if bfsub_is_one(&a, &b) && bfeven(&a) {
368 self.set_prec(x.prec());
369 self.lo.immovable = endpoint_min(&sinu, &x.lo, &x.hi, self.lo.as_float_mut());
370 self.hi.as_float_mut().assign(1);
371 self.hi.immovable = false;
372 } else {
373 self.lo.as_float_mut().assign(-1);
374 self.hi.as_float_mut().assign(1);
375 self.lo.immovable = false;
376 self.hi.immovable = false;
377 }
378 }
379 }
380 }
381
382 pub(crate) fn tanu_assign(&mut self, x: &Ival, n: u64) {
383 let (xlo, xhi) = (x.lo.as_float(), x.hi.as_float());
384 let period_qtr = period_quarter_bitlen(n, 8);
385 let immovable = x.lo.immovable && x.hi.immovable;
386 let tanu = |x: &Float, out: &mut Float, rnd: Round| mpfr_tanu(x, n, out, rnd);
387
388 match classify_ival_periodic(x, period_qtr) {
389 PeriodClass::TooWide => {
390 self.lo.as_float_mut().assign(f64::NEG_INFINITY);
391 self.hi.as_float_mut().assign(f64::INFINITY);
392 self.lo.immovable = immovable;
393 self.hi.immovable = immovable;
394 self.err.partial = true;
395 self.err.total = x.err.total;
396 }
397 PeriodClass::NearZero => {
398 self.monotonic_assign(&tanu, x);
399 self.err = x.err;
400 }
401 PeriodClass::RangeReduce => {
402 let prec = range_reduce_precision(xlo, xhi, x.prec());
403 let (a, b) = ival_div_n_half(x, n, prec, mpfr_round_inplace);
404
405 if a == b {
406 self.monotonic_assign(&tanu, x);
407 self.err = x.err;
408 } else {
409 self.lo.as_float_mut().assign(f64::NEG_INFINITY);
410 self.hi.as_float_mut().assign(f64::INFINITY);
411 self.lo.immovable = immovable;
412 self.hi.immovable = immovable;
413 self.err.partial = true;
414 self.err.total = x.err.total;
415 }
416 }
417 }
418 }
419}