1use crate::defs::WORD_BIT_SIZE;
4use crate::Consts;
5use crate::ExactNum;
6use crate::RoundingMode;
7use crate::NAN;
8use alloc::vec::Vec;
9
10pub const CHEBYSHEV_MAX_DEGREE: usize = 256;
12
13fn work_p(p: usize) -> usize {
14 p.saturating_add(WORD_BIT_SIZE)
15}
16
17fn finite(x: &ExactNum) -> bool {
18 !x.is_nan() && !x.is_inf()
19}
20
21fn map_to_unit(
23 x: &ExactNum,
24 a: &ExactNum,
25 b: &ExactNum,
26 p: usize,
27 rm: RoundingMode,
28) -> Option<ExactNum> {
29 if !finite(x) || !finite(a) || !finite(b) {
30 return None;
31 }
32 if a.cmp(b) != Some(-1) {
33 return None;
34 }
35 let two = ExactNum::from_u8(2, p);
36 let num = two.mul(x, p, rm).sub(a, p, rm).sub(b, p, rm);
37 let den = b.sub(a, p, rm);
38 if den.is_zero() {
39 return None;
40 }
41 Some(num.div(&den, p, rm))
42}
43
44fn map_from_unit(t: &ExactNum, a: &ExactNum, b: &ExactNum, p: usize, rm: RoundingMode) -> ExactNum {
45 let two = ExactNum::from_u8(2, p);
46 let mid = a.add(b, p, rm).div(&two, p, rm);
47 let half = b.sub(a, p, rm).div(&two, p, rm);
48 mid.add(&half.mul(t, p, rm), p, rm)
49}
50
51fn unit_node(k: usize, n: usize, p: usize, rm: RoundingMode, cc: &mut Consts) -> ExactNum {
53 let pi = cc.pi(p, rm);
54 let num = ExactNum::from_u32((2 * k + 1) as u32, p);
55 let den = ExactNum::from_u32((2 * n) as u32, p);
56 let theta = pi.mul(&num, p, rm).div(&den, p, rm);
57 theta.cos(p, rm, cc)
58}
59
60fn node_cos_j(
62 j: usize,
63 k: usize,
64 n: usize,
65 p: usize,
66 rm: RoundingMode,
67 cc: &mut Consts,
68) -> ExactNum {
69 if j == 0 {
70 return ExactNum::from_u8(1, p);
71 }
72 let pi = cc.pi(p, rm);
73 let num = ExactNum::from_u32((j * (2 * k + 1)) as u32, p);
74 let den = ExactNum::from_u32((2 * n) as u32, p);
75 let theta = pi.mul(&num, p, rm).div(&den, p, rm);
76 theta.cos(p, rm, cc)
77}
78
79pub fn chebyshev_coeffs<F>(
87 mut f: F,
88 n: usize,
89 a: &ExactNum,
90 b: &ExactNum,
91 p: usize,
92 rm: RoundingMode,
93 cc: &mut Consts,
94) -> Option<Vec<ExactNum>>
95where
96 F: FnMut(&ExactNum, usize, RoundingMode, &mut Consts) -> ExactNum,
97{
98 if n == 0 || n > CHEBYSHEV_MAX_DEGREE {
99 return None;
100 }
101 if !finite(a) || !finite(b) || a.cmp(b) != Some(-1) {
102 return None;
103 }
104 let wrk = work_p(p);
105 let mut fx = Vec::with_capacity(n);
106 for k in 0..n {
107 let t = unit_node(k, n, wrk, RoundingMode::None, cc);
108 let x = map_from_unit(&t, a, b, wrk, RoundingMode::None);
109 let y = f(&x, wrk, RoundingMode::None, cc);
110 if !finite(&y) {
111 return None;
112 }
113 fx.push(y);
114 }
115 let n_f = ExactNum::from_u32(n as u32, wrk);
116 let two = ExactNum::from_u8(2, wrk);
117 let mut coeffs = Vec::with_capacity(n);
118 for j in 0..n {
119 let mut s = ExactNum::new(wrk);
120 for k in 0..n {
121 let w = node_cos_j(j, k, n, wrk, RoundingMode::None, cc);
122 s = s.add(
123 &fx[k].mul(&w, wrk, RoundingMode::None),
124 wrk,
125 RoundingMode::None,
126 );
127 }
128 let scale = if j == 0 {
129 ExactNum::from_u8(1, wrk).div(&n_f, wrk, RoundingMode::None)
130 } else {
131 two.div(&n_f, wrk, RoundingMode::None)
132 };
133 let mut c = s.mul(&scale, wrk, RoundingMode::None);
134 let _ = c.set_precision(p, rm);
135 coeffs.push(c);
136 }
137 Some(coeffs)
138}
139
140pub fn clenshaw(coeffs: &[ExactNum], x: &ExactNum, p: usize, rm: RoundingMode) -> ExactNum {
145 if coeffs.is_empty() {
146 return ExactNum::new(p);
147 }
148 if !finite(x) || coeffs.iter().any(|c| !finite(c)) {
149 return NAN;
150 }
151 let wrk = work_p(p);
152 let two = ExactNum::from_u8(2, wrk);
153 let mut b1 = ExactNum::new(wrk);
154 let mut b2 = ExactNum::new(wrk);
155 for c in coeffs.iter().skip(1).rev() {
156 let t = two
157 .mul(x, wrk, RoundingMode::None)
158 .mul(&b1, wrk, RoundingMode::None)
159 .sub(&b2, wrk, RoundingMode::None)
160 .add(c, wrk, RoundingMode::None);
161 b2 = b1;
162 b1 = t;
163 }
164 let mut y = x
165 .mul(&b1, wrk, RoundingMode::None)
166 .sub(&b2, wrk, RoundingMode::None)
167 .add(&coeffs[0], wrk, RoundingMode::None);
168 let _ = y.set_precision(p, rm);
169 y
170}
171
172pub fn chebyshev_eval(
177 coeffs: &[ExactNum],
178 x: &ExactNum,
179 a: &ExactNum,
180 b: &ExactNum,
181 p: usize,
182 rm: RoundingMode,
183) -> ExactNum {
184 match map_to_unit(x, a, b, p, rm) {
185 Some(t) => clenshaw(coeffs, &t, p, rm),
186 None => NAN,
187 }
188}
189
190pub fn chebyshev_error_bound(coeffs: &[ExactNum], p: usize) -> ExactNum {
195 let mut acc = ExactNum::new(p);
196 for c in coeffs.iter().skip(1) {
197 acc = acc.add(&c.abs(), p, RoundingMode::ToEven);
198 }
199 acc
200}
201
202#[cfg(test)]
204fn chebyshev_sum_direct(coeffs: &[ExactNum], x: &ExactNum, p: usize, rm: RoundingMode) -> ExactNum {
205 if coeffs.is_empty() {
206 return ExactNum::new(p);
207 }
208 let wrk = work_p(p);
209 let one = ExactNum::from_u8(1, wrk);
210 let two = ExactNum::from_u8(2, wrk);
211 let mut t_prev = one;
212 let mut acc = coeffs[0].clone();
213 let _ = acc.set_precision(wrk, RoundingMode::None);
214 if coeffs.len() == 1 {
215 let _ = acc.set_precision(p, rm);
216 return acc;
217 }
218 let mut t_cur = x.clone();
219 let _ = t_cur.set_precision(wrk, RoundingMode::None);
220 acc = acc.add(
221 &coeffs[1].mul(&t_cur, wrk, RoundingMode::None),
222 wrk,
223 RoundingMode::None,
224 );
225 for c in coeffs.iter().skip(2) {
226 let t_next = two
227 .mul(x, wrk, RoundingMode::None)
228 .mul(&t_cur, wrk, RoundingMode::None)
229 .sub(&t_prev, wrk, RoundingMode::None);
230 acc = acc.add(
231 &c.mul(&t_next, wrk, RoundingMode::None),
232 wrk,
233 RoundingMode::None,
234 );
235 t_prev = t_cur;
236 t_cur = t_next;
237 }
238 let _ = acc.set_precision(p, rm);
239 acc
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use crate::Consts;
246
247 const CHEBYSHEV_EXP_TERMS: usize = 20;
249 const CHEBYSHEV_EXP_ERR_DIGITS: isize = 15;
251
252 fn gold_p() -> (usize, RoundingMode) {
253 (256, RoundingMode::ToEven)
254 }
255
256 #[test]
257 fn chebyshev_exp_nodes_clenshaw() {
258 let (p, rm) = gold_p();
259 let mut cc = Consts::new().expect("consts");
260 let a = ExactNum::from_i64(-1, p);
261 let b = ExactNum::from_u8(1, p);
262 let coeffs = chebyshev_coeffs(
263 |x, p, rm, cc| x.exp(p, rm, cc),
264 CHEBYSHEV_EXP_TERMS,
265 &a,
266 &b,
267 p,
268 rm,
269 &mut cc,
270 )
271 .expect("coeffs");
272 assert_eq!(coeffs.len(), CHEBYSHEV_EXP_TERMS);
273
274 let ten = ExactNum::from_u8(10, p);
275 let tol = ExactNum::from_u8(1, p).div(&ten.powsi(CHEBYSHEV_EXP_ERR_DIGITS, p, rm), p, rm);
276 for &xi in &[-1i64, 0, 1] {
277 let x = ExactNum::from_i64(xi, p);
278 let approx = chebyshev_eval(&coeffs, &x, &a, &b, p, rm);
279 let exact = x.exp(p, rm, &mut cc);
280 let err = approx.sub(&exact, p, rm).abs();
281 assert!(
282 err.is_zero() || err.cmp(&tol) == Some(-1),
283 "exp({}) error not < 10^{{-15}}",
284 xi
285 );
286 }
287
288 let node_slack =
289 ExactNum::from_u8(1, p).ldexp(-((p as i32) - (crate::WORD_BIT_SIZE as i32)), p, rm);
290 for k in 0..CHEBYSHEV_EXP_TERMS {
291 let t = unit_node(k, CHEBYSHEV_EXP_TERMS, p, rm, &mut cc);
292 let x = map_from_unit(&t, &a, &b, p, rm);
293 let approx = chebyshev_eval(&coeffs, &x, &a, &b, p, rm);
294 let exact = x.exp(p, rm, &mut cc);
295 let err = approx.sub(&exact, p, rm).abs();
296 assert!(
297 err.is_zero() || err.cmp(&node_slack) == Some(-1),
298 "node {k} interpolant farther than a working word from exp"
299 );
300 }
301
302 const IDENTITY_NODES: usize = 4;
303 let id_c = chebyshev_coeffs(
304 |x, _p, _rm, _cc| x.clone(),
305 IDENTITY_NODES,
306 &a,
307 &b,
308 p,
309 rm,
310 &mut cc,
311 )
312 .expect("identity coeffs");
313 let one = ExactNum::from_u8(1, p);
314 assert_eq!(id_c[1].cmp(&one), Some(0));
315 for k in 0..IDENTITY_NODES {
316 let t = unit_node(k, IDENTITY_NODES, p, rm, &mut cc);
317 let x = map_from_unit(&t, &a, &b, p, rm);
318 let approx = chebyshev_eval(&id_c, &x, &a, &b, p, rm);
319 let err = approx.sub(&x, p, rm).abs();
320 assert!(
321 err.is_zero() || err.cmp(&node_slack) == Some(-1),
322 "identity node {k} farther than a working word from x"
323 );
324 }
325
326 let c = [ExactNum::from_u8(1, p), ExactNum::from_u8(2, p), ExactNum::from_u8(3, p)];
327 let half = ExactNum::from_u8(1, p).div(&ExactNum::from_u8(2, p), p, rm);
328 let via_clenshaw = clenshaw(&c, &half, p, rm);
329 let via_direct = chebyshev_sum_direct(&c, &half, p, rm);
330 assert_eq!(via_clenshaw.cmp(&half), Some(0));
331 assert_eq!(via_direct.cmp(&half), Some(0));
332 assert_eq!(via_clenshaw.cmp(&via_direct), Some(0));
333
334 assert!(
335 chebyshev_coeffs(|x, p, rm, cc| x.exp(p, rm, cc), 0, &a, &b, p, rm, &mut cc).is_none()
336 );
337 assert!(chebyshev_error_bound(&c, p).cmp(&ExactNum::from_u8(5, p)) == Some(0));
338 }
339}