statrs/distribution/
fisher_snedecor.rs

1use crate::distribution::{Continuous, ContinuousCDF};
2use crate::function::beta;
3use crate::statistics::*;
4use std::f64;
5
6/// Implements the
7/// [Fisher-Snedecor](https://en.wikipedia.org/wiki/F-distribution) distribution
8/// also commonly known as the F-distribution
9///
10/// # Examples
11///
12/// ```
13/// use statrs::distribution::{FisherSnedecor, Continuous};
14/// use statrs::statistics::Distribution;
15/// use statrs::prec;
16///
17/// let n = FisherSnedecor::new(3.0, 3.0).unwrap();
18/// assert_eq!(n.mean().unwrap(), 3.0);
19/// assert!(prec::almost_eq(n.pdf(1.0), 0.318309886183790671538, 1e-15));
20/// ```
21#[derive(Debug, Copy, Clone, PartialEq)]
22pub struct FisherSnedecor {
23    freedom_1: f64,
24    freedom_2: f64,
25}
26
27/// Represents the errors that can occur when creating a [`FisherSnedecor`].
28#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
29#[non_exhaustive]
30pub enum FisherSnedecorError {
31    /// `freedom_1` is NaN, infinite, zero or less than zero.
32    Freedom1Invalid,
33
34    /// `freedom_2` is NaN, infinite, zero or less than zero.
35    Freedom2Invalid,
36}
37
38impl std::fmt::Display for FisherSnedecorError {
39    #[cfg_attr(coverage_nightly, coverage(off))]
40    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
41        match self {
42            FisherSnedecorError::Freedom1Invalid => {
43                write!(f, "freedom_1 is NaN, infinite, zero or less than zero.")
44            }
45            FisherSnedecorError::Freedom2Invalid => {
46                write!(f, "freedom_2 is NaN, infinite, zero or less than zero.")
47            }
48        }
49    }
50}
51
52impl std::error::Error for FisherSnedecorError {}
53
54impl FisherSnedecor {
55    /// Constructs a new fisher-snedecor distribution with
56    /// degrees of freedom `freedom_1` and `freedom_2`
57    ///
58    /// # Errors
59    ///
60    /// Returns an error if `freedom_1` or `freedom_2` are `NaN`.
61    /// Also returns an error if `freedom_1 <= 0.0` or `freedom_2 <= 0.0`
62    ///
63    /// # Examples
64    ///
65    /// ```
66    /// use statrs::distribution::FisherSnedecor;
67    ///
68    /// let mut result = FisherSnedecor::new(1.0, 1.0);
69    /// assert!(result.is_ok());
70    ///
71    /// result = FisherSnedecor::new(0.0, 0.0);
72    /// assert!(result.is_err());
73    /// ```
74    pub fn new(freedom_1: f64, freedom_2: f64) -> Result<FisherSnedecor, FisherSnedecorError> {
75        if !freedom_1.is_finite() || freedom_1 <= 0.0 {
76            return Err(FisherSnedecorError::Freedom1Invalid);
77        }
78
79        if !freedom_2.is_finite() || freedom_2 <= 0.0 {
80            return Err(FisherSnedecorError::Freedom2Invalid);
81        }
82
83        Ok(FisherSnedecor {
84            freedom_1,
85            freedom_2,
86        })
87    }
88
89    /// Returns the first degree of freedom for the
90    /// fisher-snedecor distribution
91    ///
92    /// # Examples
93    ///
94    /// ```
95    /// use statrs::distribution::FisherSnedecor;
96    ///
97    /// let n = FisherSnedecor::new(2.0, 3.0).unwrap();
98    /// assert_eq!(n.freedom_1(), 2.0);
99    /// ```
100    pub fn freedom_1(&self) -> f64 {
101        self.freedom_1
102    }
103
104    /// Returns the second degree of freedom for the
105    /// fisher-snedecor distribution
106    ///
107    /// # Examples
108    ///
109    /// ```
110    /// use statrs::distribution::FisherSnedecor;
111    ///
112    /// let n = FisherSnedecor::new(2.0, 3.0).unwrap();
113    /// assert_eq!(n.freedom_2(), 3.0);
114    /// ```
115    pub fn freedom_2(&self) -> f64 {
116        self.freedom_2
117    }
118}
119
120impl std::fmt::Display for FisherSnedecor {
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        write!(f, "F({},{})", self.freedom_1, self.freedom_2)
123    }
124}
125
126#[cfg(feature = "rand")]
127#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
128impl ::rand::distributions::Distribution<f64> for FisherSnedecor {
129    fn sample<R: ::rand::Rng + ?Sized>(&self, rng: &mut R) -> f64 {
130        (super::gamma::sample_unchecked(rng, self.freedom_1 / 2.0, 0.5) * self.freedom_2)
131            / (super::gamma::sample_unchecked(rng, self.freedom_2 / 2.0, 0.5) * self.freedom_1)
132    }
133}
134
135impl ContinuousCDF<f64, f64> for FisherSnedecor {
136    /// Calculates the cumulative distribution function for the fisher-snedecor
137    /// distribution
138    /// at `x`
139    ///
140    /// # Formula
141    ///
142    /// ```text
143    /// I_((d1 * x) / (d1 * x + d2))(d1 / 2, d2 / 2)
144    /// ```
145    ///
146    /// where `d1` is the first degree of freedom, `d2` is
147    /// the second degree of freedom, and `I` is the regularized incomplete
148    /// beta function
149    fn cdf(&self, x: f64) -> f64 {
150        if x < 0.0 {
151            0.0
152        } else if x.is_infinite() {
153            1.0
154        } else {
155            beta::beta_reg(
156                self.freedom_1 / 2.0,
157                self.freedom_2 / 2.0,
158                self.freedom_1 * x / (self.freedom_1 * x + self.freedom_2),
159            )
160        }
161    }
162
163    /// Calculates the survival function for the fisher-snedecor
164    /// distribution at `x`
165    ///
166    /// # Formula
167    ///
168    /// ```text
169    /// I_(1 - ((d1 * x) / (d1 * x + d2))(d2 / 2, d1 / 2)
170    /// ```
171    ///
172    /// where `d1` is the first degree of freedom, `d2` is
173    /// the second degree of freedom, and `I` is the regularized incomplete
174    /// beta function
175    fn sf(&self, x: f64) -> f64 {
176        if x < 0.0 {
177            1.0
178        } else if x.is_infinite() {
179            0.0
180        } else {
181            beta::beta_reg(
182                self.freedom_2 / 2.0,
183                self.freedom_1 / 2.0,
184                1. - ((self.freedom_1 * x) / (self.freedom_1 * x + self.freedom_2)),
185            )
186        }
187    }
188
189    /// Calculates the inverse cumulative distribution function for the
190    /// fisher-snedecor distribution at `x`
191    ///
192    /// # Formula
193    ///
194    /// ```text
195    /// z = I^{-1}_(x)(d1 / 2, d2 / 2)
196    /// d2 / (d1 (1 / z - 1))
197    /// ```
198    ///
199    /// where `d1` is the first degree of freedom, `d2` is
200    /// the second degree of freedom, and `I` is the regularized incomplete
201    /// beta function
202    fn inverse_cdf(&self, x: f64) -> f64 {
203        if !(0.0..=1.0).contains(&x) {
204            panic!("x must be in [0, 1]");
205        } else {
206            let z = beta::inv_beta_reg(self.freedom_1 / 2.0, self.freedom_2 / 2.0, x);
207            self.freedom_2 / (self.freedom_1 * (1.0 / z - 1.0))
208        }
209    }
210}
211
212impl Min<f64> for FisherSnedecor {
213    /// Returns the minimum value in the domain of the
214    /// fisher-snedecor distribution representable by a double precision
215    /// float
216    ///
217    /// # Formula
218    ///
219    /// ```text
220    /// 0
221    /// ```
222    fn min(&self) -> f64 {
223        0.0
224    }
225}
226
227impl Max<f64> for FisherSnedecor {
228    /// Returns the maximum value in the domain of the
229    /// fisher-snedecor distribution representable by a double precision
230    /// float
231    ///
232    /// # Formula
233    ///
234    /// ```text
235    /// f64::INFINITY
236    /// ```
237    fn max(&self) -> f64 {
238        f64::INFINITY
239    }
240}
241
242impl Distribution<f64> for FisherSnedecor {
243    /// Returns the mean of the fisher-snedecor distribution
244    ///
245    /// # Panics
246    ///
247    /// If `freedom_2 <= 2.0`
248    ///
249    /// # Remarks
250    ///
251    /// Returns `NaN` if `freedom_2` is `INF`
252    ///
253    /// # Formula
254    ///
255    /// ```text
256    /// d2 / (d2 - 2)
257    /// ```
258    ///
259    /// where `d2` is the second degree of freedom
260    fn mean(&self) -> Option<f64> {
261        if self.freedom_2 <= 2.0 {
262            None
263        } else {
264            Some(self.freedom_2 / (self.freedom_2 - 2.0))
265        }
266    }
267
268    /// Returns the variance of the fisher-snedecor distribution
269    ///
270    /// # Panics
271    ///
272    /// If `freedom_2 <= 4.0`
273    ///
274    /// # Remarks
275    ///
276    /// Returns `NaN` if `freedom_1` or `freedom_2` is `INF`
277    ///
278    /// # Formula
279    ///
280    /// ```text
281    /// (2 * d2^2 * (d1 + d2 - 2)) / (d1 * (d2 - 2)^2 * (d2 - 4))
282    /// ```
283    ///
284    /// where `d1` is the first degree of freedom and `d2` is
285    /// the second degree of freedom
286    fn variance(&self) -> Option<f64> {
287        if self.freedom_2 <= 4.0 {
288            None
289        } else {
290            let val =
291                (2.0 * self.freedom_2 * self.freedom_2 * (self.freedom_1 + self.freedom_2 - 2.0))
292                    / (self.freedom_1
293                        * (self.freedom_2 - 2.0)
294                        * (self.freedom_2 - 2.0)
295                        * (self.freedom_2 - 4.0));
296            Some(val)
297        }
298    }
299
300    /// Returns the skewness of the fisher-snedecor distribution
301    ///
302    /// # Panics
303    ///
304    /// If `freedom_2 <= 6.0`
305    ///
306    /// # Remarks
307    ///
308    /// Returns `NaN` if `freedom_1` or `freedom_2` is `INF`
309    ///
310    /// # Formula
311    ///
312    /// ```text
313    /// ((2d1 + d2 - 2) * sqrt(8 * (d2 - 4))) / ((d2 - 6) * sqrt(d1 * (d1 + d2
314    /// - 2)))
315    /// ```
316    ///
317    /// where `d1` is the first degree of freedom and `d2` is
318    /// the second degree of freedom
319    fn skewness(&self) -> Option<f64> {
320        if self.freedom_2 <= 6.0 {
321            None
322        } else {
323            let val = ((2.0 * self.freedom_1 + self.freedom_2 - 2.0)
324                * (8.0 * (self.freedom_2 - 4.0)).sqrt())
325                / ((self.freedom_2 - 6.0)
326                    * (self.freedom_1 * (self.freedom_1 + self.freedom_2 - 2.0)).sqrt());
327            Some(val)
328        }
329    }
330}
331
332impl Mode<Option<f64>> for FisherSnedecor {
333    /// Returns the mode for the fisher-snedecor distribution
334    ///
335    /// # Panics
336    ///
337    /// If `freedom_1 <= 2.0`
338    ///
339    /// # Remarks
340    ///
341    /// Returns `NaN` if `freedom_1` or `freedom_2` is `INF`
342    ///
343    /// # Formula
344    ///
345    /// ```text
346    /// ((d1 - 2) / d1) * (d2 / (d2 + 2))
347    /// ```
348    ///
349    /// where `d1` is the first degree of freedom and `d2` is
350    /// the second degree of freedom
351    fn mode(&self) -> Option<f64> {
352        if self.freedom_1 <= 2.0 {
353            None
354        } else {
355            let val = (self.freedom_2 * (self.freedom_1 - 2.0))
356                / (self.freedom_1 * (self.freedom_2 + 2.0));
357            Some(val)
358        }
359    }
360}
361
362impl Continuous<f64, f64> for FisherSnedecor {
363    /// Calculates the probability density function for the fisher-snedecor
364    /// distribution
365    /// at `x`
366    ///
367    /// # Remarks
368    ///
369    /// Returns `NaN` if `freedom_1`, `freedom_2` is `INF`, or `x` is `+INF` or
370    /// `-INF`
371    ///
372    /// # Formula
373    ///
374    /// ```text
375    /// sqrt(((d1 * x) ^ d1 * d2 ^ d2) / (d1 * x + d2) ^ (d1 + d2)) / (x * β(d1
376    /// / 2, d2 / 2))
377    /// ```
378    ///
379    /// where `d1` is the first degree of freedom, `d2` is
380    /// the second degree of freedom, and `β` is the beta function
381    fn pdf(&self, x: f64) -> f64 {
382        if x.is_infinite() || x <= 0.0 {
383            0.0
384        } else {
385            ((self.freedom_1 * x).powf(self.freedom_1) * self.freedom_2.powf(self.freedom_2)
386                / (self.freedom_1 * x + self.freedom_2).powf(self.freedom_1 + self.freedom_2))
387            .sqrt()
388                / (x * beta::beta(self.freedom_1 / 2.0, self.freedom_2 / 2.0))
389        }
390    }
391
392    /// Calculates the log probability density function for the fisher-snedecor
393    /// distribution
394    /// at `x`
395    ///
396    /// # Remarks
397    ///
398    /// Returns `NaN` if `freedom_1`, `freedom_2` is `INF`, or `x` is `+INF` or
399    /// `-INF`
400    ///
401    /// # Formula
402    ///
403    /// ```text
404    /// ln(sqrt(((d1 * x) ^ d1 * d2 ^ d2) / (d1 * x + d2) ^ (d1 + d2)) / (x *
405    /// β(d1 / 2, d2 / 2)))
406    /// ```
407    ///
408    /// where `d1` is the first degree of freedom, `d2` is
409    /// the second degree of freedom, and `β` is the beta function
410    fn ln_pdf(&self, x: f64) -> f64 {
411        self.pdf(x).ln()
412    }
413}
414
415#[rustfmt::skip]
416#[cfg(test)]
417mod tests {
418    use super::*;
419    use crate::distribution::internal::*;
420    use crate::testing_boiler;
421
422    testing_boiler!(freedom_1: f64, freedom_2: f64; FisherSnedecor; FisherSnedecorError);
423
424    #[test]
425    fn test_create() {
426        create_ok(0.1, 0.1);
427        create_ok(1.0, 0.1);
428        create_ok(10.0, 0.1);
429        create_ok(0.1, 1.0);
430        create_ok(1.0, 1.0);
431        create_ok(10.0, 1.0);
432    }
433
434    #[test]
435    fn test_bad_create() {
436        test_create_err(f64::INFINITY, 0.1, FisherSnedecorError::Freedom1Invalid);
437        test_create_err(0.1, f64::INFINITY, FisherSnedecorError::Freedom2Invalid);
438
439        create_err(f64::NAN, f64::NAN);
440        create_err(0.0, f64::NAN);
441        create_err(-1.0, f64::NAN);
442        create_err(-10.0, f64::NAN);
443        create_err(f64::NAN, 0.0);
444        create_err(0.0, 0.0);
445        create_err(-1.0, 0.0);
446        create_err(-10.0, 0.0);
447        create_err(f64::NAN, -1.0);
448        create_err(0.0, -1.0);
449        create_err(-1.0, -1.0);
450        create_err(-10.0, -1.0);
451        create_err(f64::NAN, -10.0);
452        create_err(0.0, -10.0);
453        create_err(-1.0, -10.0);
454        create_err(-10.0, -10.0);
455        create_err(f64::INFINITY, f64::INFINITY);
456    }
457
458    #[test]
459    fn test_mean() {
460        let mean = |x: FisherSnedecor| x.mean().unwrap();
461        test_exact(0.1, 10.0, 1.25, mean);
462        test_exact(1.0, 10.0, 1.25, mean);
463        test_exact(10.0, 10.0, 1.25, mean);
464    }
465
466    #[test]
467    fn test_mean_with_low_d2() {
468        test_none(0.1, 0.1, |dist| dist.mean());
469    }
470
471    #[test]
472    fn test_variance() {
473        let variance = |x: FisherSnedecor| x.variance().unwrap();
474        test_absolute(0.1, 10.0, 42.1875, 1e-14, variance);
475        test_exact(1.0, 10.0, 4.6875, variance);
476        test_exact(10.0, 10.0, 0.9375, variance);
477    }
478
479    #[test]
480    fn test_variance_with_low_d2() {
481        test_none(0.1, 0.1, |dist| dist.variance());
482    }
483
484    #[test]
485    fn test_skewness() {
486        let skewness = |x: FisherSnedecor| x.skewness().unwrap();
487        test_absolute(0.1, 10.0, 15.78090735784977089658, 1e-14, skewness);
488        test_exact(1.0, 10.0, 5.773502691896257645091, skewness);
489        test_exact(10.0, 10.0, 3.614784456460255759501, skewness);
490    }
491
492    #[test]
493    fn test_skewness_with_low_d2() {
494        test_none(0.1, 0.1, |dist| dist.skewness());
495    }
496
497    #[test]
498    fn test_mode() {
499        let mode = |x: FisherSnedecor| x.mode().unwrap();
500        test_exact(10.0, 0.1, 0.0380952380952380952381, mode);
501        test_exact(10.0, 1.0, 4.0 / 15.0, mode);
502        test_exact(10.0, 10.0, 2.0 / 3.0, mode);
503    }
504
505    #[test]
506    fn test_mode_with_low_d1() {
507        test_none(0.1, 0.1, |dist| dist.mode());
508    }
509
510    #[test]
511    fn test_min_max() {
512        let min = |x: FisherSnedecor| x.min();
513        let max = |x: FisherSnedecor| x.max();
514        test_exact(1.0, 1.0, 0.0, min);
515        test_exact(1.0, 1.0, f64::INFINITY, max);
516    }
517
518    #[test]
519    fn test_pdf() {
520        let pdf = |arg: f64| move |x: FisherSnedecor| x.pdf(arg);
521        test_absolute(0.1, 0.1, 0.0234154207226588982471, 1e-16, pdf(1.0));
522        test_absolute(1.0, 0.1, 0.0396064560910663979961, 1e-16, pdf(1.0));
523        test_absolute(10.0, 0.1, 0.0418440630400545297349, 1e-14, pdf(1.0));
524        test_absolute(0.1, 1.0, 0.0396064560910663979961, 1e-16, pdf(1.0));
525        test_absolute(1.0, 1.0, 0.1591549430918953357689, 1e-16, pdf(1.0));
526        test_absolute(10.0, 1.0, 0.230361989229138647108, 1e-16, pdf(1.0));
527        test_absolute(0.1, 0.1, 0.00221546909694001013517, 1e-18, pdf(10.0));
528        test_absolute(1.0, 0.1, 0.00369960370387922619592, 1e-17, pdf(10.0));
529        test_absolute(10.0, 0.1, 0.00390179721174142927402, 1e-15, pdf(10.0));
530        test_absolute(0.1, 1.0, 0.00319864073359931548273, 1e-17, pdf(10.0));
531        test_absolute(1.0, 1.0, 0.009150765837179460915678, 1e-17, pdf(10.0));
532        test_absolute(10.0, 1.0, 0.0116493859171442148446, 1e-17, pdf(10.0));
533        test_absolute(0.1, 10.0, 0.00305087016058573989694, 1e-15, pdf(10.0));
534        test_absolute(1.0, 10.0, 0.00271897749113479577864, 1e-17, pdf(10.0));
535        test_absolute(10.0, 10.0, 2.4289227234060500084E-4, 1e-18, pdf(10.0));
536    }
537
538    #[test]
539    fn test_ln_pdf() {
540        let ln_pdf = |arg: f64| move |x: FisherSnedecor| x.ln_pdf(arg);
541        test_absolute(0.1, 0.1, 0.0234154207226588982471f64.ln(), 1e-15, ln_pdf(1.0));
542        test_absolute(1.0, 0.1, 0.0396064560910663979961f64.ln(), 1e-15, ln_pdf(1.0));
543        test_absolute(10.0, 0.1, 0.0418440630400545297349f64.ln(), 1e-13, ln_pdf(1.0));
544        test_absolute(0.1, 1.0, 0.0396064560910663979961f64.ln(), 1e-15, ln_pdf(1.0));
545        test_absolute(1.0, 1.0, 0.1591549430918953357689f64.ln(), 1e-15, ln_pdf(1.0));
546        test_absolute(10.0, 1.0, 0.230361989229138647108f64.ln(), 1e-15, ln_pdf(1.0));
547        test_exact(0.1, 0.1, 0.00221546909694001013517f64.ln(), ln_pdf(10.0));
548        test_absolute(1.0, 0.1, 0.00369960370387922619592f64.ln(), 1e-15, ln_pdf(10.0));
549        test_absolute(10.0, 0.1, 0.00390179721174142927402f64.ln(), 1e-13, ln_pdf(10.0));
550        test_absolute(0.1, 1.0, 0.00319864073359931548273f64.ln(), 1e-15, ln_pdf(10.0));
551        test_absolute(1.0, 1.0, 0.009150765837179460915678f64.ln(), 1e-15, ln_pdf(10.0));
552        test_exact(10.0, 1.0, 0.0116493859171442148446f64.ln(), ln_pdf(10.0));
553        test_absolute(0.1, 10.0, 0.00305087016058573989694f64.ln(), 1e-13, ln_pdf(10.0));
554        test_exact(1.0, 10.0, 0.00271897749113479577864f64.ln(), ln_pdf(10.0));
555        test_absolute(10.0, 10.0, 2.4289227234060500084E-4f64.ln(), 1e-14, ln_pdf(10.0));
556    }
557
558    #[test]
559    fn test_cdf() {
560        let cdf = |arg: f64| move |x: FisherSnedecor| x.cdf(arg);
561        test_absolute(0.1, 0.1, 0.44712986033425140335, 1e-15, cdf(0.1));
562        test_absolute(1.0, 0.1, 0.08156522095104674015, 1e-15, cdf(0.1));
563        test_absolute(10.0, 0.1, 0.033184005716276536322, 1e-13, cdf(0.1));
564        test_absolute(0.1, 1.0, 0.74378710917986379989, 1e-15, cdf(0.1));
565        test_absolute(1.0, 1.0, 0.1949822290421366451595, 1e-16, cdf(0.1));
566        test_absolute(10.0, 1.0, 0.0101195597354337146205, 1e-17, cdf(0.1));
567        test_absolute(0.1, 0.1, 0.5, 1e-15, cdf(1.0));
568        test_absolute(1.0, 0.1, 0.16734351500944271141, 1e-14, cdf(1.0));
569        test_absolute(10.0, 0.1, 0.12207560664741704938, 1e-13, cdf(1.0));
570        test_absolute(0.1, 1.0, 0.83265648499055728859, 1e-15, cdf(1.0));
571        test_absolute(1.0, 1.0, 0.5, 1e-15, cdf(1.0));
572        test_absolute(10.0, 1.0, 0.340893132302059872675, 1e-15, cdf(1.0));
573    }
574
575    #[test]
576    fn test_cdf_lower_bound() {
577        let cdf = |arg: f64| move |x: FisherSnedecor| x.cdf(arg);
578        test_exact(0.1, 0.1, 0.0, cdf(-1.0));
579    }
580
581    #[test]
582    fn test_sf() {
583        let sf = |arg: f64| move |x: FisherSnedecor| x.sf(arg);
584        test_absolute(0.1, 0.1, 0.5528701396657489, 1e-12, sf(0.1));
585        test_absolute(1.0, 0.1, 0.9184347790489533, 1e-12, sf(0.1));
586        test_absolute(10.0, 0.1, 0.9668159942836896, 1e-12, sf(0.1));
587        test_absolute(0.1, 1.0, 0.25621289082013654, 1e-12, sf(0.1));
588        test_absolute(1.0, 1.0, 0.8050177709578634, 1e-12, sf(0.1));
589        test_absolute(10.0, 1.0, 0.9898804402645662, 1e-12, sf(0.1));
590        test_absolute(0.1, 0.1, 0.5, 1e-15, sf(1.0));
591        test_absolute(1.0, 0.1, 0.8326564849905562, 1e-12, sf(1.0));
592        test_absolute(10.0, 0.1, 0.8779243933525519, 1e-12, sf(1.0));
593        test_absolute(0.1, 1.0, 0.16734351500944344, 1e-12, sf(1.0));
594        test_absolute(1.0, 1.0, 0.5, 1e-12, sf(1.0));
595        test_absolute(10.0, 1.0, 0.65910686769794, 1e-12, sf(1.0));
596    }
597
598    #[test]
599    fn test_inverse_cdf() {
600        let func = |arg: f64| move |x: FisherSnedecor| x.inverse_cdf(x.cdf(arg));
601        test_absolute(0.1, 0.1, 0.1, 1e-12, func(0.1));
602        test_absolute(1.0, 0.1, 0.1, 1e-12, func(0.1));
603        test_absolute(10.0, 0.1, 0.1, 1e-12, func(0.1));
604        test_absolute(0.1, 1.0, 0.1, 1e-12, func(0.1));
605        test_absolute(1.0, 1.0, 0.1, 1e-12, func(0.1));
606        test_absolute(10.0, 1.0, 0.1, 1e-12, func(0.1));
607        test_absolute(0.1, 0.1, 1.0, 1e-13, func(1.0));
608        test_absolute(1.0, 0.1, 1.0, 1e-12, func(1.0));
609        test_absolute(10.0, 0.1, 1.0, 1e-12, func(1.0));
610        test_absolute(0.1, 1.0, 1.0, 1e-12, func(1.0));
611        test_absolute(1.0, 1.0, 1.0, 1e-12, func(1.0));
612        test_absolute(10.0, 1.0, 1.0, 1e-12, func(1.0));
613    }
614
615    #[test]
616    fn test_sf_lower_bound() {
617        let sf = |arg: f64| move |x: FisherSnedecor| x.sf(arg);
618        test_exact(0.1, 0.1, 1.0, sf(-1.0));
619    }
620
621    #[test]
622    fn test_continuous() {
623        test::check_continuous_distribution(&create_ok(10.0, 10.0), 0.0, 10.0);
624    }
625}