1use std::f64::consts::PI;
54
55use crate::{Error, MAX_DEGREE, Result, numerics::EULER};
56
57const INVALID: &str = "noninteger real degree in (0,128], |order|<=130 and -1<x<=1 required";
59const NONFINITE: &str = "non-finite real-degree Legendre result or derivative";
60
61const MAX_REAL_DEGREE_ORDER: i32 = MAX_DEGREE + 2;
64
65const TOLERANCE: f64 = f64::EPSILON / 4.0;
67const MAX_TERMS: u32 = 20_000;
69const BIG: f64 = 1.340_780_792_994_259_7e154;
71const BIG_EXPONENT: i32 = 512;
72const REGULAR_REACH: f64 = 0.5;
74const SINGULAR_REACH: f64 = 3.0;
76const SQRT_PI: f64 = 1.772_453_850_905_516;
77const CENTRAL_REACH: f64 = 2.5;
81const CENTRAL_LIMIT: f64 = 0.5;
82const CENTRAL_LIMIT_POSITIVE: f64 = 0.25;
83const CENTRAL_DEGREE: f64 = 16.0;
84const CENTRAL_ORDER: i32 = 20;
85
86const INV_FACTORIAL: [f64; 131] = [
88 1.0e0,
89 1.0e0,
90 5.0e-1,
91 1.666_666_666_666_666_6e-1,
92 4.166_666_666_666_666_4e-2,
93 8.333_333_333_333_333e-3,
94 1.388_888_888_888_889e-3,
95 1.984_126_984_126_984e-4,
96 2.480_158_730_158_73e-5,
97 2.755_731_922_398_589_3e-6,
98 2.755_731_922_398_589e-7,
99 2.505_210_838_544_172e-8,
100 2.087_675_698_786_81e-9,
101 1.605_904_383_682_161_3e-10,
102 1.147_074_559_772_972_5e-11,
103 7.647_163_731_819_816e-13,
104 4.779_477_332_387_385e-14,
105 2.811_457_254_345_520_6e-15,
106 1.561_920_696_858_622_5e-16,
107 8.220_635_246_624_33e-18,
108 4.110_317_623_312_165e-19,
109 1.957_294_106_339_126_3e-20,
110 8.896_791_392_450_574e-22,
111 3.868_170_170_630_684e-23,
112 1.611_737_571_096_118_4e-24,
113 6.446_950_284_384_474e-26,
114 2.479_596_263_224_797_6e-27,
115 9.183_689_863_795_546e-29,
116 3.279_889_237_069_838e-30,
117 1.130_996_288_644_771_6e-31,
118 3.769_987_628_815_905_4e-33,
119 1.216_125_041_553_518e-34,
120 3.800_390_754_854_743_4e-36,
121 1.151_633_562_077_195e-37,
122 3.387_157_535_521_162e-39,
123 9.677_592_958_631_89e-41,
124 2.688_220_266_286_636_3e-42,
125 7.265_460_179_153_071e-44,
126 1.911_963_205_040_282e-45,
127 4.902_469_756_513_544e-47,
128 1.225_617_439_128_385_8e-48,
129 2.989_310_827_142_404_6e-50,
130 7.117_406_731_291_439e-52,
131 1.655_210_867_742_195_1e-53,
132 3.761_842_881_232_261_6e-55,
133 8.359_650_847_182_804e-57,
134 1.817_315_401_561_479e-58,
135 3.866_628_513_960_594e-60,
136 8.055_476_070_751_236e-62,
137 1.643_974_708_316_579e-63,
138 3.287_949_416_633_158e-65,
139 6.446_959_640_457_172e-67,
140 1.239_799_930_857_148_6e-68,
141 2.339_245_152_560_657_6e-70,
142 4.331_935_467_704_922e-72,
143 7.876_246_304_918_039e-74,
144 1.406_472_554_449_649_8e-75,
145 2.467_495_709_560_789_3e-77,
146 4.254_302_947_518_602e-79,
147 7.210_682_961_895_936_5e-81,
148 1.201_780_493_649_322_6e-82,
149 1.970_131_956_802_168_2e-84,
150 3.177_632_188_390_594_2e-86,
151 5.043_860_616_493_007e-88,
152 7.881_032_213_270_323e-90,
153 1.212_466_494_349_280_4e-91,
154 1.837_070_445_983_758_1e-93,
155 2.741_896_188_035_46e-95,
156 4.032_200_276_522_735_3e-97,
157 5.843_768_516_699_616e-99,
158 8.348_240_738_142_31e-101,
159 1.175_808_554_667_930_8e-102,
160 1.633_067_437_038_793e-104,
161 2.237_078_680_875_058_7e-106,
162 3.023_079_298_479_809e-108,
163 4.030_772_397_973_079e-110,
164 5.303_647_892_069_84e-112,
165 6.887_854_405_285_506e-114,
166 8.830_582_570_878_855e-116,
167 1.117_795_262_136_564e-117,
168 1.397_244_077_670_705e-119,
169 1.724_992_688_482_351_7e-121,
170 2.103_649_620_100_429e-123,
171 2.534_517_614_578_83e-125,
172 3.017_282_874_498_607_3e-127,
173 3.549_744_558_233_655_4e-129,
174 4.127_609_951_434_483e-131,
175 4.744_379_254_522_394_6e-133,
176 5.391_340_061_957_266_6e-135,
177 6.057_685_462_873_333e-137,
178 6.730_761_625_414_815e-139,
179 7.396_441_346_609_687e-141,
180 8.039_610_159_358_355e-143,
181 8.644_742_106_836_94e-145,
182 9.196_534_156_209_511e-147,
183 9.680_562_269_694_223e-149,
184 1.008_391_903_093_148_2e-150,
185 1.039_579_281_539_328e-152,
186 1.060_795_185_244_212_2e-154,
187 1.071_510_288_125_467e-156,
188 1.071_510_288_125_467e-158,
189 1.060_901_275_371_749_4e-160,
190 1.040_099_289_580_146_5e-162,
191 1.009_805_135_514_705_3e-164,
192 9.709_664_764_564_474e-167,
193 9.247_299_775_775_69e-169,
194 8.723_867_712_995_935e-171,
195 8.153_147_395_323_303e-173,
196 7.549_210_551_225_28e-175,
197 6.925_881_239_656_22e-177,
198 6.296_255_672_414_745e-179,
199 5.672_302_407_580_852e-181,
200 5.064_555_721_054_332e-183,
201 4.481_907_717_747_196_5e-185,
202 3.931_497_998_023_856_7e-187,
203 3.418_693_911_325_092_7e-189,
204 2.947_149_923_556_114_4e-191,
205 2.518_931_558_594_969_7e-193,
206 2.134_687_761_521_160_7e-195,
207 1.793_855_261_782_488_2e-197,
208 1.494_879_384_818_74e-199,
209 1.235_437_508_114_661_2e-201,
210 1.012_653_695_175_951_7e-203,
211 8.232_956_871_349_201e-206,
212 6.639_481_347_862_259e-208,
213 5.311_585_078_289_807e-210,
214 4.215_543_712_928_419e-212,
215 3.319_325_758_211_353e-214,
216 2.593_223_248_602_619_6e-216,
217 2.010_250_580_312_108_2e-218,
218 1.546_346_600_240_083_3e-220,
219];
220
221const RGAMMA_TAYLOR: [f64; 21] = [
223 std::f64::consts::FRAC_2_SQRT_PI, -4.117_452_644_528_31e-2,
225 -5.266_544_355_255_445e-1,
226 1.751_020_260_439_345_7e-1,
227 5.096_686_024_770_607_4e-2,
228 -4.215_516_936_853_560_4e-2,
229 6.612_897_826_824_127e-3,
230 2.120_731_442_572_938e-3,
231 -1.110_730_254_594_890_6e-3,
232 1.523_576_207_674_768_8e-4,
233 2.535_520_492_381_416_5e-5,
234 -1.389_680_571_791_375_6e-5,
235 2.156_203_290_514_172_4e-6,
236 5.794_264_054_052_672_6e-8,
237 -8.913_551_118_311_116e-8,
238 1.710_346_941_591_537_4e-8,
239 -9.313_686_445_241_901e-10,
240 -2.680_474_103_349_662_3e-10,
241 7.458_932_233_316_326e-11,
242 -8.012_807_061_414_718e-12,
243 -8.382_343_033_451_855e-14,
244];
245
246#[derive(Clone, Copy, Debug)]
249struct Scaled {
250 mantissa: f64,
251 exponent: i32,
252}
253
254impl Scaled {
255 fn new(value: f64, exponent: i32) -> Self {
256 let (mantissa, shift) = frexp(value);
257 Self {
258 mantissa,
259 exponent: exponent + shift,
260 }
261 }
262
263 fn mul(self, other: Self) -> Self {
264 Self::new(
265 self.mantissa * other.mantissa,
266 self.exponent + other.exponent,
267 )
268 }
269
270 fn scale(self, factor: f64) -> Self {
271 Self::new(self.mantissa * factor, self.exponent)
272 }
273
274 fn add(self, other: Self) -> Self {
275 if other.mantissa == 0.0 {
276 return self;
277 }
278 if self.mantissa == 0.0 {
279 return other;
280 }
281 let (high, low) = if self.exponent >= other.exponent {
282 (self, other)
283 } else {
284 (other, self)
285 };
286 let aligned = scalbn(low.mantissa, low.exponent - high.exponent);
287 Self::new(high.mantissa + aligned, high.exponent)
288 }
289
290 fn to_f64(self) -> f64 {
291 scalbn(self.mantissa, self.exponent)
292 }
293}
294
295const EXPONENT_MASK: u64 = 0x7ff << 52;
296
297#[inline]
300fn frexp(value: f64) -> (f64, i32) {
301 let bits = value.to_bits();
302 let biased = (bits & EXPONENT_MASK) >> 52;
303 if biased == 0 || biased == 0x7ff {
304 return libm::frexp(value);
305 }
306 let mantissa = f64::from_bits((bits & !EXPONENT_MASK) | (0x3fe << 52));
307 (
309 mantissa,
310 i32::from(u16::try_from(biased).unwrap_or(0)) - 1022,
311 )
312}
313
314#[inline]
317fn scalbn(value: f64, exponent: i32) -> f64 {
318 if (-1000..=1000).contains(&exponent) {
319 let power = f64::from_bits(u64::from((exponent + 1023).unsigned_abs()) << 52);
320 let result = value * power;
321 if result == 0.0 || result.abs() >= f64::MIN_POSITIVE {
322 return result;
323 }
324 }
325 libm::scalbn(value, exponent)
326}
327
328fn nonfinite() -> Error {
329 Error::SpecialFunction(NONFINITE.into())
330}
331
332#[allow(clippy::cast_possible_truncation)] fn small_integer(value: f64) -> i32 {
336 value as i32
337}
338
339fn floor_small(value: f64) -> f64 {
341 f64::from(small_integer(value))
342}
343
344fn nearest_integer(value: f64) -> i32 {
346 let whole = small_integer(value);
347 if value - f64::from(whole) >= 0.5 {
348 whole + 1
349 } else {
350 whole
351 }
352}
353
354pub(crate) fn ferrers_real_degree<const DERIVATIVE: bool>(
362 v: f64,
363 m: i32,
364 x: f64,
365) -> Result<(f64, f64)> {
366 if !(v > 0.0 && v < f64::from(MAX_DEGREE))
367 || v - floor_small(v) == 0.0
368 || m.unsigned_abs() > MAX_REAL_DEGREE_ORDER.unsigned_abs()
369 || !(x > -1.0 && x <= 1.0)
370 {
371 return Err(Error::InvalidInput(INVALID.into()));
372 }
373 if x >= 1.0 {
374 return endpoint::<DERIVATIVE>(v, m);
375 }
376 let order = m.abs();
377 let y = x.abs();
378 let central_limit = if x > 0.0 {
379 CENTRAL_LIMIT_POSITIVE
380 } else {
381 CENTRAL_LIMIT
382 };
383 let (value, slope) = if v < CENTRAL_DEGREE
384 && order <= CENTRAL_ORDER
385 && y <= central_limit
386 && (v + 0.5) * y <= CENTRAL_REACH
387 {
388 central::<DERIVATIVE>(v, order, x)?
389 } else if x >= 0.0 {
390 regular(v, order, y)?
391 } else {
392 return reflected::<DERIVATIVE>(v, m, y);
393 };
394 if m > 0 {
395 let ratio = gamma_ratio(v, order, None).scale(if order % 2 == 1 { -1.0 } else { 1.0 });
396 let slope = if DERIVATIVE { slope.mul(ratio) } else { slope };
397 return finish::<DERIVATIVE>(value.mul(ratio), slope, y);
398 }
399 finish::<DERIVATIVE>(value, slope, y)
400}
401
402#[cfg(test)]
408pub(crate) fn ferrers_route_switches(v: f64, m: i32) -> Vec<[f64; 2]> {
409 let around = |x: f64| {
410 [
411 x * (1.0 - 4.0 * f64::EPSILON),
412 x * (1.0 + 4.0 * f64::EPSILON),
413 ]
414 };
415 let mut switches = Vec::new();
416 if v < CENTRAL_DEGREE && m.abs() <= CENTRAL_ORDER {
417 let reach = CENTRAL_REACH / (v + 0.5);
418 switches.push(around(CENTRAL_LIMIT_POSITIVE.min(reach)));
419 switches.push(around(-CENTRAL_LIMIT.min(reach)));
420 } else {
421 switches.push([0.0, -f64::MIN_POSITIVE]);
422 }
423 let s = (0.5 * SINGULAR_REACH / (v + 0.5)).powi(2);
424 if s < 0.5 {
425 switches.push(around(2.0f64.mul_add(s, -1.0)));
426 }
427 switches
428}
429
430fn reflected<const DERIVATIVE: bool>(v: f64, m: i32, y: f64) -> Result<(f64, f64)> {
432 let order = m.abs();
433 let odd = order % 2 == 1;
434 let (sin, cos) = sin_cos_pi(v);
435 let frac = v - floor_small(v);
437 let frac_cot = if frac < 1e-4 {
438 pi_cot_scaled(frac)
439 } else {
440 frac * PI * cos / sin
441 };
442 if order > 0
443 && f64::from(order) <= floor_small(v)
444 && v <= SINGULAR_REACH / (2.0 * (0.5 * (1.0 - y)).sqrt()) - 0.5
445 {
446 return order_series::<DERIVATIVE>(v, m, y, sin, cos, frac_cot);
447 }
448 let slopes = match order {
449 _ if DERIVATIVE => Slopes::Both,
450 0 => Slopes::None,
451 1 => Slopes::Both,
452 _ => Slopes::Singular,
453 };
454 let zero = order_zero(v, y, frac_cot, slopes)?;
455 let ((regular, regular_slope), (singular, singular_slope)) = match order {
456 0 => (
457 (
458 Scaled::new(zero.regular, 0),
459 Scaled::new(zero.regular_slope, 0),
460 ),
461 (
462 Scaled::new(zero.singular, 0),
463 Scaled::new(zero.singular_slope, 0),
464 ),
465 ),
466 1 if v > 1.0 => {
469 let (w, w_low) = one_minus_square(y);
470 let sine = w.sqrt();
471 let sine = sine + 0.5 * w_low / sine;
472 let lowered = zero.regular_slope / (v * (v + 1.0) * sine);
473 (
474 (
475 Scaled::new(lowered, 0),
476 Scaled::new(y * lowered - sine * zero.regular, 0),
477 ),
478 raise_singular(v, order, y, &zero),
479 )
480 }
481 _ => (regular(v, order, y)?, raise_singular(v, order, y, &zero)),
482 };
483 let (first, second) = if m > 0 {
484 (
485 gamma_ratio(v, order, None).scale(cos),
486 Scaled::new(if odd { -sin } else { sin }, 0),
487 )
488 } else {
489 (
490 Scaled::new(if odd { -cos } else { cos }, 0),
491 sine_over_gamma_ratio(v, order, sin),
492 )
493 };
494 let value = regular.mul(first).add(singular.mul(second));
495 let slope = if DERIVATIVE {
496 regular_slope
497 .mul(first)
498 .add(singular_slope.mul(second))
499 .scale(-1.0)
500 } else {
501 value
502 };
503 finish::<DERIVATIVE>(value, slope, y)
504}
505
506fn order_series<const DERIVATIVE: bool>(
516 v: f64,
517 m: i32,
518 y: f64,
519 sin: f64,
520 cos: f64,
521 frac_cot: f64,
522) -> Result<(f64, f64)> {
523 let order = m.abs();
524 let mu = f64::from(order);
525 let s = 0.5 * (1.0 - y);
526 let (w, w_low) = one_minus_square(y);
527 let (mut finite, mut finite_slope, mut term) = (1.0, 0.0, 1.0);
529 for k in 1..order {
530 let k = f64::from(k);
531 term *= (v + k) * (k - 1.0 - v) * s / (k * (k - mu));
532 finite += term;
533 finite_slope += k * term;
534 }
535 let sums = order_log_sums::<DERIVATIVE>(v, order, s, frac_cot)?;
537 let (regular, regular_slope, log, log_slope) =
538 (sums.regular, sums.regular_slope, sums.log, sums.log_slope);
539 let inverse_factorial = usize::try_from(order)
541 .ok()
542 .and_then(|index| INV_FACTORIAL.get(index).zip(INV_FACTORIAL.get(index - 1)))
543 .ok_or_else(nonfinite)?;
544 let base = sine_power(w, w_low, order).mul(Scaled::new(*inverse_factorial.0, -order));
545 let odd = order % 2 == 1;
546 let sign = if odd { -1.0 } else { 1.0 };
547 let regular_part = base;
548 let log_part = base.scale(sign / PI);
549 let (s_mantissa, s_exponent) = frexp(s);
550 let finite_part = base
552 .mul(Scaled::new(-sign / (PI * *inverse_factorial.0), 0))
553 .mul(Scaled::new(1.0 / *inverse_factorial.1, 0))
554 .mul(Scaled::new(s_mantissa.powi(-order), -order * s_exponent));
555 let ratio = gamma_ratio(v, order, None);
556 let (regular_part, log_part, finite_part) = if m > 0 {
557 (
558 regular_part.mul(ratio).scale(cos),
559 log_part.mul(ratio).scale(sign * sin),
560 finite_part.scale(sign * sin),
561 )
562 } else {
563 let inverse = Scaled::new(1.0 / ratio.mantissa, -ratio.exponent);
564 (
565 regular_part.scale(sign * cos),
566 log_part.scale(sin),
567 finite_part.mul(inverse).scale(sin),
568 )
569 };
570 let value = regular_part
571 .scale(regular)
572 .add(log_part.scale(log))
573 .add(finite_part.scale(finite));
574 if !DERIVATIVE {
575 return finish::<false>(value, value, y);
576 }
577 let tilt = mu * (1.0 - 2.0 * s);
579 let slope = regular_part
580 .scale(tilt * regular + 2.0 * (1.0 - s) * regular_slope)
581 .add(log_part.scale(tilt * log + 2.0 * (1.0 - s) * log_slope))
582 .add(finite_part.scale(2.0 * (1.0 - s) * finite_slope - mu * finite));
583 finish::<DERIVATIVE>(value, slope, y)
584}
585
586#[derive(Clone, Copy, Debug, Default)]
588struct OrderSums {
589 regular: f64,
590 regular_slope: f64,
591 log: f64,
592 log_slope: f64,
593 values: Convergence,
594 slopes: Convergence,
595}
596
597impl OrderSums {
598 #[inline]
601 fn add<const SLOPE: bool>(
602 &mut self,
603 k: f64,
604 term: f64,
605 plain: f64,
606 ratio: impl Fn() -> f64,
607 ) -> bool {
608 if !self.values.done {
609 self.regular += plain;
610 self.log += term;
611 self.values.update(term.abs() + plain.abs(), &ratio);
612 }
613 if SLOPE {
614 let slope_term = k * term + plain;
615 self.regular_slope += k * plain;
616 self.log_slope += slope_term;
617 self.slopes
618 .update(slope_term.abs() + k * plain.abs(), &ratio);
619 return self.values.done && self.slopes.done;
620 }
621 self.values.done
622 }
623}
624
625fn order_log_sums<const SLOPE: bool>(
629 v: f64,
630 order: i32,
631 s: f64,
632 frac_cot: f64,
633) -> Result<OrderSums> {
634 let mu = f64::from(order);
635 let whole = floor_small(v);
636 let frac = v - whole;
637 let pivot = whole - mu;
638 let ratio_after = |k: f64| {
639 let bound = (v + mu + k + 1.0) * ((mu - v + k).abs() + 1.0) / ((mu + k + 1.0) * (k + 1.0));
640 s * bound.max(1.0) * (1.0 + 2.0 / (k + 1.0))
641 };
642 let mut sums = OrderSums::default();
643 let psi_low = digamma(1.0 + v - mu);
647 let (psi_high, psi_order) = if mu <= 4.0 {
648 (
649 psi_low + shifted_sum(v - mu, 2 * order),
650 shifted_sum(0.0, order) - EULER,
651 )
652 } else {
653 (digamma(v + mu + 1.0), digamma(mu + 1.0))
654 };
655 let mut bracket = s.ln() + EULER - psi_order + psi_high + psi_low;
656 let mut weight = 1.0; let mut k = 0.0;
658 let mut count = 0;
659 loop {
660 if sums.add::<SLOPE>(k, weight * bracket, weight, || ratio_after(k)) {
661 return Ok(sums);
662 }
663 if k >= pivot {
664 break;
665 }
666 let next = k + 1.0;
668 let (upper, shifted, gap) = (v + mu + next, mu + next, v - mu - k);
669 let (upper_next, shifted_gap) = (upper * next, shifted * gap);
670 let reciprocal = 1.0 / (upper_next * shifted_gap);
671 weight *= (mu - v + k) * upper * upper * gap * s * reciprocal;
672 bracket +=
673 (next * shifted_gap - upper * shifted_gap - upper_next * gap - upper_next * shifted)
674 * reciprocal;
675 k = next;
676 count += 1;
677 if count > MAX_TERMS {
678 return Err(nonfinite());
679 }
680 }
681 let next = k + 1.0;
683 let (upper, shifted) = (v + mu + next, mu + next);
684 weight *= upper * s / (shifted * next);
685 bracket += 1.0 / upper - 1.0 / next - 1.0 / shifted;
686 k = next;
687 if sums.add::<SLOPE>(k, weight * (1.0 - frac * bracket), -frac * weight, || {
689 ratio_after(k)
690 }) {
691 return Ok(sums);
692 }
693 let next = k + 1.0;
695 let (upper, shifted) = (v + mu + next, mu + next);
696 weight *= (mu - v + k) * upper * s / (shifted * next);
697 bracket += 1.0 / upper - 1.0 / next - 1.0 / shifted + digamma_reflection_step(frac, frac_cot);
698 k = next;
699 loop {
700 if sums.add::<SLOPE>(
701 k,
702 weight * (frac_cot - frac * bracket),
703 -frac * weight,
704 || ratio_after(k),
705 ) {
706 return Ok(sums);
707 }
708 let next = k + 1.0;
709 let (upper, shifted, gap) = (v + mu + next, mu + next, k + mu - v);
710 let (upper_next, shifted_gap) = (upper * next, shifted * gap);
711 let reciprocal = 1.0 / (upper_next * shifted_gap);
712 weight *= (mu - v + k) * upper * upper * gap * s * reciprocal;
713 bracket += (next * shifted_gap - upper * shifted_gap - upper_next * gap
714 + upper_next * shifted)
715 * reciprocal;
716 k = next;
717 count += 1;
718 if count > MAX_TERMS {
719 return Err(nonfinite());
720 }
721 }
722}
723
724fn finish<const DERIVATIVE: bool>(value: Scaled, slope: Scaled, y: f64) -> Result<(f64, f64)> {
726 let value = value.to_f64();
727 if !value.is_finite() {
728 return Err(nonfinite());
729 }
730 if !DERIVATIVE {
731 return Ok((value, 0.0));
732 }
733 let (w, _) = one_minus_square(y);
734 let derivative = Scaled::new(slope.mantissa / w, slope.exponent).to_f64();
735 if !derivative.is_finite() {
736 return Err(nonfinite());
737 }
738 Ok((value, derivative))
739}
740
741fn endpoint<const DERIVATIVE: bool>(v: f64, m: i32) -> Result<(f64, f64)> {
744 let value = if m == 0 { 1.0 } else { 0.0 };
745 if !DERIVATIVE {
746 return Ok((value, 0.0));
747 }
748 let derivative = match m {
749 0 => 0.5 * v * (v + 1.0),
750 1 | -1 => return Err(nonfinite()),
751 2 => -0.25 * gamma_ratio(v, 2, None).to_f64(),
752 -2 => -0.25,
753 _ => 0.0,
754 };
755 Ok((value, derivative))
756}
757
758fn regular(v: f64, order: i32, y: f64) -> Result<(Scaled, Scaled)> {
760 let mu = f64::from(order);
761 let whole = floor_small(v);
762 let frac = v - whole;
763 let t = 0.5 * (1.0 - y);
764 let (w, w_low) = one_minus_square(y);
765 let start = if mu >= whole {
766 v
767 } else {
768 let base = 2.0 * mu + 1.0;
770 let reach = 0.5 * ((base * base + 4.0 * REGULAR_REACH * (mu + 1.0) / t).sqrt() - base);
771 let lift = floor_small((reach - frac).clamp(0.0, whole - mu));
772 (mu + lift) + frac
773 };
774 let b_whole = mu + 1.0 + (start - frac);
778 let (a, b, c) = (mu - start, b_whole + frac, mu + 1.0);
779 let mut term = (b_whole * t + frac * t) / c;
780 let (mut sum, mut weighted, mut size) = (term, term, term.abs());
781 let mut k = 1.0;
782 let mut count = 0;
783 loop {
784 let rising = a + k;
785 term *= (rising * (b_whole + k) + rising * frac) * t / ((c + k) * (k + 1.0));
786 k += 1.0;
787 sum += term;
788 let current = k * term;
789 weighted += current;
790 size += current.abs();
791 if a + k > 0.0 && current.abs() <= TOLERANCE * size {
792 let rho = t * (b + k) * (k + 1.0) / ((c + k) * k);
794 if rho < 1.0 && current.abs() * rho <= TOLERANCE * size * (1.0 - rho) {
795 break;
796 }
797 }
798 count += 1;
799 if count > MAX_TERMS {
800 return Err(nonfinite());
801 }
802 }
803 let mut p = 1.0 + a * sum;
804 let mut d = 2.0 * (1.0 - t) * weighted;
805 let mut exponent = 0;
806 let mut degree = start;
807 for _ in 0..small_integer(v - start) {
808 let coupling = (degree - mu) / (degree + mu + 1.0);
809 let next = y * p - coupling * d;
810 d = w * p + (w_low * p + y * coupling * d);
811 p = next;
812 degree += 1.0;
813 if p.abs().max(d.abs()) > BIG {
814 p = scalbn(p, -BIG_EXPONENT);
815 d = scalbn(d, -BIG_EXPONENT);
816 exponent += BIG_EXPONENT;
817 }
818 }
819 let slope = (v - mu) * d - mu * y * p;
820 if order == 0 {
821 return Ok((Scaled::new(p, exponent), Scaled::new(slope, exponent)));
822 }
823 let inverse_factorial = usize::try_from(order)
824 .ok()
825 .and_then(|index| INV_FACTORIAL.get(index))
826 .ok_or_else(nonfinite)?;
827 let prefactor = sine_power(w, w_low, order).mul(Scaled::new(*inverse_factorial, -order));
828 Ok((
829 prefactor.mul(Scaled::new(p, exponent)),
830 prefactor.mul(Scaled::new(slope, exponent)),
831 ))
832}
833
834fn central<const SLOPE: bool>(v: f64, order: i32, x: f64) -> Result<(Scaled, Scaled)> {
841 let mu = f64::from(order);
842 let scale = scalbn(SQRT_PI, -order);
843 let mut even = scale * rgamma(f64::midpoint(v, mu) + 1.0) * rgamma(f64::midpoint(1.0 - v, mu));
844 let mut odd = -2.0 * scale * rgamma(f64::midpoint(v + mu, 1.0)) * rgamma(f64::midpoint(mu, -v));
845 let square = x * x;
846 let magnitude_x = x.abs();
847 let (mut sum_even, mut sum_odd) = (even, odd);
851 let (mut slope_even, mut slope_odd, mut slope_sum_odd) = (0.0, 0.0, odd);
853 let mut values = Convergence {
854 size: even.abs() + magnitude_x * odd.abs(),
855 ..Convergence::default()
856 };
857 let mut slopes = Convergence {
858 size: odd.abs(),
859 done: !SLOPE,
860 ..Convergence::default()
861 };
862 let (mut low, mut high, mut first) = (mu - v, mu + v + 1.0, 1.0);
864 let mut index = 1.0;
865 let mut count = 0;
866 while !(values.done && slopes.done) {
867 let reciprocal = 1.0 / (first * (first + 1.0) * (first + 2.0));
868 let ratio_even = low * high * (first + 2.0) * reciprocal;
869 let ratio_odd = (low + 1.0) * (high + 1.0) * first * reciprocal;
870 let partial_even = even * ratio_even;
871 let partial_odd = odd * ratio_odd;
872 even = partial_even * square;
873 odd = partial_odd * square;
874 let rho = || square * ratio_even.abs().max(ratio_odd.abs()).max(1.0) * (1.0 + 2.0 / index);
876 if !values.done {
877 sum_even += even;
878 sum_odd += odd;
879 values.update(even.abs() + magnitude_x * odd.abs(), rho);
880 }
881 if !slopes.done {
882 slope_even += index * partial_even;
883 slope_odd += index * partial_odd;
884 slope_sum_odd += odd;
885 slopes.update(
886 (index + 1.0) * (2.0 * magnitude_x * partial_even.abs() + partial_odd.abs()),
887 rho,
888 );
889 }
890 low += 2.0;
891 high += 2.0;
892 first += 2.0;
893 index += 1.0;
894 count += 1;
895 if count > MAX_TERMS {
896 return Err(nonfinite());
897 }
898 }
899 let (w, w_low) = one_minus_square(x);
900 let value = sum_even + x * sum_odd;
901 let slope = if SLOPE {
902 w * (2.0 * x * slope_even + slope_sum_odd + 2.0 * square * slope_odd) - mu * x * value
903 } else {
904 0.0
905 };
906 if order == 0 {
907 return Ok((Scaled::new(value, 0), Scaled::new(slope, 0)));
908 }
909 let prefactor = sine_power(w, w_low, order);
910 Ok((
911 prefactor.mul(Scaled::new(value, 0)),
912 prefactor.mul(Scaled::new(slope, 0)),
913 ))
914}
915
916fn rgamma(z: f64) -> f64 {
919 let truncated = small_integer(z);
920 let floor = if f64::from(truncated) > z {
921 truncated - 1
922 } else {
923 truncated
924 };
925 let (mut z, mut numerator, mut denominator) = (z, 1.0, 1.0);
926 for _ in floor..1 {
927 numerator *= z;
928 z += 1.0;
929 }
930 for _ in 2..=floor {
931 z -= 1.0;
932 denominator *= z;
933 }
934 numerator * rgamma_core(z - 1.5) / denominator
935}
936
937fn rgamma_core(h: f64) -> f64 {
939 let [
940 c0,
941 c1,
942 c2,
943 c3,
944 c4,
945 c5,
946 c6,
947 c7,
948 c8,
949 c9,
950 c10,
951 c11,
952 c12,
953 c13,
954 c14,
955 c15,
956 c16,
957 c17,
958 c18,
959 c19,
960 c20,
961 ] = RGAMMA_TAYLOR;
962 let h2 = h * h;
963 let h4 = h2 * h2;
964 let h8 = h4 * h4;
965 let quad = |a: f64, b: f64, c: f64, d: f64| (a + b * h) + (c + d * h) * h2;
966 let low = (quad(c0, c1, c2, c3) + quad(c4, c5, c6, c7) * h4)
967 + (quad(c8, c9, c10, c11) + quad(c12, c13, c14, c15) * h4) * h8;
968 let high = quad(c16, c17, c18, c19) + c20 * h4;
969 low + high * (h8 * h8)
970}
971
972fn sine_power(w: f64, w_low: f64, order: i32) -> Scaled {
974 let half = 0.5 * f64::from(order);
975 let correction = 1.0 + half * (w_low / w);
977 match order {
978 0 => return Scaled::new(1.0, 0),
979 1 => return Scaled::new(w.sqrt() * correction, 0),
980 2 => return Scaled::new(w + w_low, 0),
981 3 => return Scaled::new(w * w.sqrt() * correction, 0),
982 4 => return Scaled::new(w * w * correction, 0),
983 _ => {}
984 }
985 if w > 0.75 {
986 return Scaled::new((half * ((w - 1.0) + w_low).ln_1p()).exp(), 0);
988 }
989 let (mut mantissa, mut exponent) = frexp(w);
990 if exponent % 2 != 0 {
991 mantissa *= 2.0;
992 exponent -= 1;
993 }
994 Scaled::new(mantissa.powf(half) * correction, exponent / 2 * order)
995}
996
997#[derive(Clone, Copy, Debug)]
1000struct OrderZero {
1001 regular: f64,
1002 regular_slope: f64,
1003 singular: f64,
1004 singular_slope: f64,
1005}
1006
1007#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1009enum Slopes {
1010 None,
1011 Singular,
1012 Both,
1013}
1014
1015fn order_zero(v: f64, y: f64, frac_cot: f64, slopes: Slopes) -> Result<OrderZero> {
1019 let s = 0.5 * (1.0 - y);
1020 let frac = v - floor_small(v);
1021 let reach = SINGULAR_REACH / (2.0 * s.sqrt()) - 0.5;
1022 let start = if v <= reach {
1023 v
1024 } else {
1025 let steps = floor_small(v - reach);
1027 let steps = if steps < v - reach {
1028 steps + 1.0
1029 } else {
1030 steps
1031 };
1032 (v - steps).max((1.0 + frac).min(v))
1033 };
1034 if start >= v {
1035 return match slopes {
1036 Slopes::None => log_series::<false, false>(v, y, frac_cot),
1037 Slopes::Singular => log_series::<true, false>(v, y, frac_cot),
1038 Slopes::Both => log_series::<true, true>(v, y, frac_cot),
1039 };
1040 }
1041 let zero = log_series::<true, true>(start, y, frac_cot)?;
1042 let (w, w_low) = one_minus_square(y);
1043 let (mut p, mut q) = (zero.regular, zero.singular);
1044 let (mut dp, mut dq) = (zero.regular_slope / start, zero.singular_slope / start);
1045 let mut degree = start;
1046 for _ in 0..small_integer(v - start) {
1047 let coupling = degree / (degree + 1.0);
1048 let next_p = y * p - coupling * dp;
1049 let next_q = y * q - coupling * dq;
1050 dp = w * p + (w_low * p + y * coupling * dp);
1051 dq = w * q + (w_low * q + y * coupling * dq);
1052 p = next_p;
1053 q = next_q;
1054 degree += 1.0;
1055 }
1056 Ok(OrderZero {
1057 regular: p,
1058 regular_slope: v * dp,
1059 singular: q,
1060 singular_slope: v * dq,
1061 })
1062}
1063
1064fn raise_singular(v: f64, order: i32, y: f64, zero: &OrderZero) -> (Scaled, Scaled) {
1067 let (w, w_low) = one_minus_square(y);
1068 let sine = w.sqrt();
1069 let sine = sine + 0.5 * w_low / sine;
1070 let cotangent = y / sine;
1071 let (mut previous, mut current) = (zero.singular, -zero.singular_slope / sine);
1072 let mut exponent = 0;
1073 for k in 1..order {
1074 let k = f64::from(k);
1075 let next = -2.0 * k * cotangent * current - (v - k + 1.0) * (v + k) * previous;
1076 previous = current;
1077 current = next;
1078 if current.abs() > BIG {
1079 previous = scalbn(previous, -BIG_EXPONENT);
1080 current = scalbn(current, -BIG_EXPONENT);
1081 exponent += BIG_EXPONENT;
1082 }
1083 }
1084 let mu = f64::from(order);
1085 let slope = (v + mu) * (v - mu + 1.0) * sine * previous + mu * y * current;
1086 (Scaled::new(current, exponent), Scaled::new(slope, exponent))
1087}
1088
1089#[derive(Clone, Copy, Debug, Default)]
1092struct Convergence {
1093 size: f64,
1094 small_before: bool,
1095 done: bool,
1096}
1097
1098impl Convergence {
1099 #[inline]
1100 fn update(&mut self, magnitude: f64, rho: impl Fn() -> f64) {
1101 if self.done {
1102 return;
1103 }
1104 self.size += magnitude;
1105 let small = magnitude <= TOLERANCE * self.size;
1106 if small && self.small_before {
1107 let rho = rho();
1108 self.done = rho < 1.0 && magnitude * rho <= TOLERANCE * self.size * (1.0 - rho);
1109 }
1110 self.small_before = small;
1111 }
1112}
1113
1114#[derive(Clone, Copy, Debug, Default)]
1119struct Sums<const SINGULAR: bool, const REGULAR: bool> {
1120 singular: f64,
1121 regular: f64,
1122 singular_slope: f64,
1123 regular_slope: f64,
1124 values: Convergence,
1125 singular_slopes: Convergence,
1126 regular_slopes: Convergence,
1127}
1128
1129impl<const SINGULAR: bool, const REGULAR: bool> Sums<SINGULAR, REGULAR> {
1130 #[inline]
1133 fn add(&mut self, k: f64, term: f64, plain: f64, ratio: impl Fn() -> f64) -> bool {
1134 let rho = || ratio() * (1.0 + 2.0 / (k + 1.0));
1136 if !self.values.done {
1137 self.singular += term;
1138 self.regular += plain;
1139 self.values.update(term.abs() + plain.abs(), rho);
1140 }
1141 if SINGULAR {
1142 let slope_term = k * term - plain;
1143 self.singular_slope += slope_term;
1144 self.singular_slopes.update(slope_term.abs(), rho);
1145 }
1146 if REGULAR {
1147 self.regular_slope += k * plain;
1148 self.regular_slopes.update(k * plain.abs(), rho);
1149 }
1150 self.values.done
1151 && (!SINGULAR || self.singular_slopes.done)
1152 && (!REGULAR || self.regular_slopes.done)
1153 }
1154
1155 fn finish(&self, s: f64) -> OrderZero {
1156 OrderZero {
1157 regular: self.regular,
1158 regular_slope: -2.0 * (1.0 - s) * self.regular_slope,
1159 singular: -self.singular / PI,
1160 singular_slope: 2.0 / PI * (1.0 - s) * self.singular_slope,
1161 }
1162 }
1163}
1164
1165fn log_series<const SINGULAR: bool, const REGULAR: bool>(
1175 v: f64,
1176 y: f64,
1177 frac_cot: f64,
1178) -> Result<OrderZero> {
1179 let whole = floor_small(v);
1180 let frac = v - whole;
1181 let s = 0.5 * (1.0 - y);
1182 let ratio_after = |k: f64| {
1184 let bound = (v + k + 1.0) * ((v - k).abs() + 1.0) / ((k + 1.0) * (k + 1.0));
1185 s * bound.max(1.0)
1186 };
1187 let mut sums = Sums::<SINGULAR, REGULAR>::default();
1188 let psi = digamma(v + 1.0);
1189 let mut regular_part = -2.0 * EULER - psi - s.ln(); let mut psi_down = psi; let mut weight = 1.0; let mut k = 0.0;
1193 let mut count = 0;
1194 loop {
1196 if sums.add(k, weight * (regular_part - psi_down), weight, || {
1197 ratio_after(k)
1198 }) {
1199 return Ok(sums.finish(s));
1200 }
1201 if k >= whole {
1202 break;
1203 }
1204 let next = k + 1.0;
1205 let (up, gap) = (v + next, v - k);
1206 let reciprocal = 1.0 / (next * up * gap);
1207 let inv_next = up * gap * reciprocal;
1208 weight *= -(up * gap) * s * inv_next * inv_next;
1209 regular_part += (2.0 * up - next) * gap * reciprocal;
1210 psi_down -= next * up * reciprocal;
1211 k = next;
1212 count += 1;
1213 if count > MAX_TERMS {
1214 return Err(nonfinite());
1215 }
1216 }
1217 let next = k + 1.0;
1219 let up = v + next;
1220 let square = next * next;
1221 weight *= up * s / square;
1222 regular_part += (2.0 * up - next) / (next * up);
1223 k = next;
1224 let term = weight * (frac * (psi_down - regular_part) - 1.0);
1225 if sums.add(k, term, -frac * weight, || ratio_after(k)) {
1226 return Ok(sums.finish(s));
1227 }
1228 let next = k + 1.0;
1230 let up = v + next;
1231 weight *= up * (k - v) * s / (next * next);
1232 regular_part += (2.0 * up - next) / (next * up);
1233 k = next;
1234 let mut bracket = psi_down + digamma_reflection_step(frac, frac_cot) - regular_part;
1236 loop {
1237 let term = weight * (frac * bracket - frac_cot);
1238 if sums.add(k, term, -frac * weight, || ratio_after(k)) {
1239 return Ok(sums.finish(s));
1240 }
1241 let next = k + 1.0;
1242 let (up, gap) = (v + next, k - v);
1243 let reciprocal = 1.0 / (next * up * gap);
1244 let inv_next = up * gap * reciprocal;
1245 weight *= up * gap * s * inv_next * inv_next;
1246 bracket += (next * up - (2.0 * up - next) * gap) * reciprocal;
1247 k = next;
1248 count += 1;
1249 if count > MAX_TERMS {
1250 return Err(nonfinite());
1251 }
1252 }
1253}
1254
1255fn digamma_reflection_step(frac: f64, frac_cot: f64) -> f64 {
1259 if frac <= 0.5 {
1260 (frac_cot - 1.0) / frac + 1.0 / (1.0 - frac)
1261 } else {
1262 digamma(2.0 - frac) - digamma(1.0 + frac)
1263 }
1264}
1265
1266fn shifted_sum(start: f64, count: i32) -> f64 {
1268 let (mut numerator, mut denominator) = (0.0, 1.0);
1269 for i in 1..=count {
1270 let factor = start + f64::from(i);
1271 numerator = numerator * factor + denominator;
1272 denominator *= factor;
1273 }
1274 numerator / denominator
1275}
1276
1277fn pi_cot_scaled(r: f64) -> f64 {
1279 let z = PI * r;
1280 if z.abs() < 1e-4 {
1281 let z2 = z * z;
1282 1.0 - z2 / 3.0 - z2 * z2 / 45.0
1283 } else {
1284 z / z.tan()
1285 }
1286}
1287
1288fn digamma(mut x: f64) -> f64 {
1290 let (mut numerator, mut denominator) = (0.0, 1.0);
1292 let shifts = if x < 10.0 {
1293 small_integer(10.0 - x) + 1
1294 } else {
1295 0
1296 };
1297 for _ in 0..shifts {
1298 numerator = numerator * x + denominator;
1299 denominator *= x;
1300 x += 1.0;
1301 }
1302 let shift = numerator / denominator;
1303 let inv = 1.0 / x;
1304 let inv2 = inv * inv;
1305 let tail = inv2
1306 * (1.0 / 12.0
1307 - inv2
1308 * (1.0 / 120.0
1309 - inv2
1310 * (1.0 / 252.0
1311 - inv2
1312 * (1.0 / 240.0
1313 - inv2
1314 * (1.0 / 132.0
1315 - inv2 * (691.0 / 32760.0 - inv2 / 12.0))))));
1316 x.ln() - 0.5 * inv - tail - shift
1317}
1318
1319fn sin_cos_pi(v: f64) -> (f64, f64) {
1321 let nearest = nearest_integer(v);
1322 let (sin, cos) = (PI * (v - f64::from(nearest))).sin_cos();
1323 if nearest % 2 == 0 {
1324 (sin, cos)
1325 } else {
1326 (-sin, -cos)
1327 }
1328}
1329
1330fn two_sum(a: f64, b: f64) -> (f64, f64) {
1332 let sum = a + b;
1333 let b_virtual = sum - a;
1334 let a_virtual = sum - b_virtual;
1335 (sum, (a - a_virtual) + (b - b_virtual))
1336}
1337
1338fn exact_square(v: f64) -> (f64, f64) {
1340 let split = 134_217_729.0 * v; let head = split - (split - v);
1342 let tail = v - head;
1343 let square = v * v;
1344 (
1345 square,
1346 ((head * head - square) + 2.0 * head * tail) + tail * tail,
1347 )
1348}
1349
1350fn one_minus_square(y: f64) -> (f64, f64) {
1352 let (square, square_error) = exact_square(y);
1353 let (difference, error) = two_sum(1.0, -square);
1354 let low = error - square_error;
1355 let high = difference + low;
1356 (high, low - (high - difference))
1357}
1358
1359fn degree_product(v: f64) -> (f64, f64) {
1361 let (square, square_error) = exact_square(v);
1362 let (high, error) = two_sum(square, v);
1363 (high, error + square_error)
1364}
1365
1366fn gamma_ratio(v: f64, order: i32, skip: Option<i32>) -> Scaled {
1369 let (high, low) = degree_product(v);
1370 let mut product = 1.0;
1371 let mut exponent = 0;
1372 for i in 1..=order {
1373 let index = f64::from(i);
1374 if skip == Some(i) {
1375 product *= v + index;
1376 } else {
1377 let (difference, error) = two_sum(high, -(index * (index - 1.0)));
1380 product = product * difference + product * (error + low);
1381 }
1382 if i % 32 == 0 {
1383 let (mantissa, shift) = frexp(product);
1384 product = mantissa;
1385 exponent += shift;
1386 }
1387 }
1388 Scaled::new(product, exponent)
1389}
1390
1391fn sine_over_gamma_ratio(v: f64, order: i32, sin: f64) -> Scaled {
1394 let nearest = nearest_integer(v);
1395 if nearest + 1 > order {
1396 let ratio = gamma_ratio(v, order, None);
1397 return Scaled::new(sin / ratio.mantissa, -ratio.exponent);
1398 }
1399 let ratio = gamma_ratio(v, order, Some(nearest + 1));
1400 let z = PI * (v - f64::from(nearest));
1401 let sinc = if z.abs() < 1e-4 {
1402 PI * (1.0 - z * z / 6.0)
1403 } else {
1404 PI * z.sin() / z
1405 };
1406 let sign = if nearest % 2 == 0 { 1.0 } else { -1.0 };
1407 Scaled::new(sign * sinc / ratio.mantissa, -ratio.exponent)
1408}
1409
1410#[cfg(test)]
1411mod tests {
1412 #![allow(clippy::float_cmp)] use super::*;
1414
1415 const REFERENCES: [(f64, i32, f64, f64, f64); 13] = [
1417 (
1418 2.3,
1419 1,
1420 0.5,
1421 -1.158_877_916_558_258_6,
1422 -3.105_224_571_364_395,
1423 ),
1424 (
1425 2.3,
1426 -2,
1427 0.1,
1428 8.210_878_443_624_224e-2,
1429 5.272_898_021_258_5e-2,
1430 ),
1431 (
1432 0.2,
1433 0,
1434 -0.999_999,
1435 -1.797_687_281_513_591_6,
1436 1.870_981_884_756_791e5,
1437 ),
1438 (
1439 10.000_000_000_000_002,
1440 6,
1441 -0.9,
1442 7.664_247_046_048_816e4,
1443 1.738_989_837_866_624_4e6,
1444 ),
1445 (
1446 127.2,
1447 64,
1448 0.999_999,
1449 2.133_285_121_993_467_3e-24,
1450 -6.826_489_043_723_42e-17,
1451 ),
1452 (
1453 65.2,
1454 -64,
1455 -0.9999,
1456 1.336_260_716_230_590_1e6,
1457 -4.275_790_332_015_145e11,
1458 ),
1459 (
1461 127.9,
1462 121,
1463 -0.34,
1464 2.108_817_946_242_377_7e242,
1465 1.073_952_115_051_059e243,
1466 ),
1467 (
1468 127.000_000_000_000_1,
1469 -63,
1470 -0.999,
1471 -4.423_236_554_341_923e-87,
1472 1.388_205_387_672_740_3e-82,
1473 ),
1474 (
1475 120.6,
1476 0,
1477 1.0 - f64::EPSILON / 2.0,
1478 9.999_999_999_991_86e-1,
1479 7.332_479_999_997_015_5e3,
1480 ),
1481 (
1482 0.5,
1483 0,
1484 0.0,
1485 5.393_526_011_883_794e-1,
1486 5.901_702_995_080_481e-1,
1487 ),
1488 (
1489 127.2,
1490 127,
1491 0.0,
1492 -4.160_241_594_472_013e250,
1493 -1.949_011_429_617_312_4e251,
1494 ),
1495 (
1496 127.2,
1497 -127,
1498 -0.5,
1499 -3.491_950_613_042_738_7e-246,
1500 2.869_573_962_247_748_2e-244,
1501 ),
1502 (2.3, 0, 1.0, 1.0, 3.794_999_999_999_999_5),
1503 ];
1504
1505 #[test]
1506 fn matches_high_precision_references() {
1507 for (v, m, x, value, derivative) in REFERENCES {
1508 let (p, q) = ferrers_real_degree::<true>(v, m, x).unwrap();
1509 let (p_false, zero) = ferrers_real_degree::<false>(v, m, x).unwrap();
1510 assert!(
1511 (p - value).abs() <= 1e-13 * value.abs(),
1512 "{v} {m} {x}: {p} vs {value}"
1513 );
1514 assert!(
1515 (q - derivative).abs() <= 1e-13 * derivative.abs(),
1516 "{v} {m} {x}: {q} vs {derivative}"
1517 );
1518 assert_eq!(p_false.to_bits(), p.to_bits());
1519 assert_eq!(zero.to_bits(), 0.0_f64.to_bits());
1520 }
1521 }
1522
1523 #[test]
1524 fn endpoint_limits_are_exact() {
1525 assert_eq!(
1526 ferrers_real_degree::<true>(2.3, 0, 1.0).unwrap(),
1527 (1.0, 0.5 * 2.3 * 3.3)
1528 );
1529 assert_eq!(
1530 ferrers_real_degree::<true>(2.3, -2, 1.0).unwrap(),
1531 (0.0, -0.25)
1532 );
1533 assert_eq!(
1534 ferrers_real_degree::<true>(2.3, 7, 1.0).unwrap(),
1535 (0.0, 0.0)
1536 );
1537 let (value, derivative) = ferrers_real_degree::<true>(2.3, 2, 1.0).unwrap();
1538 assert_eq!(value, 0.0);
1539 assert!((derivative + 10.607_025).abs() < 1e-13 * 10.607_025);
1540 assert_eq!(
1541 ferrers_real_degree::<false>(2.3, 1, 1.0).unwrap(),
1542 (0.0, 0.0)
1543 );
1544 for m in [1, -1] {
1545 let error = ferrers_real_degree::<true>(2.3, m, 1.0).unwrap_err();
1546 assert!(
1547 matches!(&error, Error::SpecialFunction(message) if message.contains("non-finite"))
1548 );
1549 }
1550 }
1551
1552 #[test]
1553 fn rejects_inputs_outside_the_domain() {
1554 let nan = f64::NAN;
1555 let cases = [
1556 (0.0, 0, 0.5),
1557 (-0.0, 0, 0.5),
1558 (2.0, 0, 0.5),
1559 (128.0, 0, 0.5),
1560 (128.5, 0, 0.5),
1561 (-0.5, 0, 0.5),
1562 (nan, 0, 0.5),
1563 (f64::INFINITY, 0, 0.5),
1564 (2.5, 131, 0.5),
1565 (2.5, i32::MIN, 0.5),
1566 (2.5, 0, -1.0),
1567 (2.5, 0, 1.000_000_1),
1568 (2.5, 0, nan),
1569 ];
1570 for (v, m, x) in cases {
1571 assert!(
1572 matches!(
1573 ferrers_real_degree::<false>(v, m, x),
1574 Err(Error::InvalidInput(_))
1575 ),
1576 "{v} {m} {x}"
1577 );
1578 assert!(
1579 matches!(
1580 ferrers_real_degree::<true>(v, m, x),
1581 Err(Error::InvalidInput(_))
1582 ),
1583 "{v} {m} {x}"
1584 );
1585 }
1586 }
1587
1588 #[test]
1589 fn tiny_degrees_and_overflow() {
1590 let (value, derivative) = ferrers_real_degree::<true>(5e-324, 0, -0.8).unwrap();
1591 assert_eq!(value, 1.0);
1592 assert!(derivative.abs() < f64::MIN_POSITIVE);
1593 for result in [
1595 ferrers_real_degree::<false>(127.2, 126, -0.999_999),
1596 ferrers_real_degree::<true>(127.2, 126, -0.999_999),
1597 ] {
1598 assert!(
1599 matches!(result, Err(Error::SpecialFunction(message)) if message.contains("non-finite"))
1600 );
1601 }
1602 let (value, _) = ferrers_real_degree::<true>(127.2, -126, -0.999_999).unwrap();
1603 assert!((value / 2.286_871_2e105 - 1.0).abs() < 1e-6);
1604 }
1605
1606 #[test]
1609 fn recurrences_hold_across_routes() {
1610 let degrees: [f64; 8] = [
1611 1.3,
1612 2.000_000_001,
1613 2.999_999_999,
1614 7.5,
1615 20.25,
1616 41.000_000_000_01,
1617 63.7,
1618 125.9,
1619 ];
1620 let arguments = [
1621 -0.999_999_9,
1622 -0.99,
1623 -0.7,
1624 -0.3,
1625 -0.05,
1626 0.0,
1627 0.05,
1628 0.3,
1629 0.7,
1630 0.99,
1631 0.999_999_9,
1632 ];
1633 let mut checked = 0;
1634 for v in degrees {
1635 let top = small_integer(v.floor()) - 1;
1636 for m in [0, 1, -1, 2, -top / 2, top / 2, -top, top] {
1637 for x in arguments {
1638 let results = [
1639 ferrers_real_degree::<true>(v, m, x),
1640 ferrers_real_degree::<false>(v - 1.0, m, x),
1641 ferrers_real_degree::<false>(v + 1.0, m, x),
1642 ];
1643 let [Ok((p, derivative)), Ok((lower, _)), Ok((upper, _))] = results else {
1645 assert!(results.iter().all(|result| matches!(
1646 result,
1647 Ok(_) | Err(Error::SpecialFunction(_))
1648 )));
1649 continue;
1650 };
1651 let order = f64::from(m);
1652 let scale = (v - order + 1.0).abs() * upper.abs()
1654 + (2.0 * v + 1.0) * p.abs()
1655 + (v + order).abs() * lower.abs()
1656 + 1e-288;
1657 let recurrence =
1658 (v - order + 1.0) * upper - (2.0 * v + 1.0) * x * p + (v + order) * lower;
1659 assert!(
1660 recurrence.abs() <= 1e-12 * scale,
1661 "recurrence {v} {m} {x}: {recurrence} / {scale}"
1662 );
1663 checked += 1;
1664 let expected = (v + order) * lower - v * x * p;
1665 let deviation = (1.0 - x) * (1.0 + x) * derivative - expected;
1666 let slope_scale = (v + order).abs() * lower.abs()
1667 + v * p.abs()
1668 + (1.0 - x) * (1.0 + x) * derivative.abs()
1669 + 1e-288;
1670 assert!(
1671 deviation.abs() <= 1e-12 * slope_scale,
1672 "derivative {v} {m} {x}: {deviation} / {slope_scale}"
1673 );
1674 }
1675 }
1676 }
1677 assert!(
1678 checked > 600,
1679 "only {checked} combinations were representable"
1680 );
1681 }
1682}