statrs/distribution/
triangular.rs

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