Skip to main content

gauss_quad/
data_api.rs

1// Copyright 2019-2024 Dominique Dresen
2// Copyright 2023-2026 Johanna Sörngård
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5//! The macros in this module define the common API for accessing the data that underlies the quadrature rules.
6//! The [`__impl_node_weight_rule!`] macro implements the API for a struct with both nodes and weights.
7//! It should be called in the module that defines the quadrature rule struct.
8//! [`__impl_node_rule!`] does the same thing as the previous macro
9//! but for a struct with only nodes and no weights.
10
11// The code in the macros uses fully qualified paths for every type, so it is quite verbose.
12// That is, instead of `usize` it uses `::core::primitive::usize` and so on. This makes it so that
13// the caller of the macro doesn't have to import anything into the module in order for the macro to compile,
14// and makes it compile even if the user has made custom types whose names shadow types used by the macro.
15
16use crate::math::pow;
17
18use core::{fmt, num::ParseFloatError, str::FromStr};
19
20/// A node in a quadrature rule.
21pub type Node = f64;
22/// A weight in a quadrature rule.
23pub type Weight = f64;
24
25#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27#[cfg_attr(
28    feature = "rkyv",
29    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
30)]
31#[cfg_attr(
32    feature = "zerocopy",
33    derive(
34        zerocopy::KnownLayout,
35        zerocopy::Immutable,
36        zerocopy::FromZeros,
37        zerocopy::IntoBytes
38    )
39)]
40/// A wrapper around an [`f64`] that ensures the value is greater than -1.0, finite, and not [`NAN`](f64::NAN).
41pub struct FiniteAboveNegOneF64(f64);
42
43impl fmt::Display for FiniteAboveNegOneF64 {
44    #[inline]
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        write!(f, "{}", self.0)
47    }
48}
49
50impl fmt::LowerExp for FiniteAboveNegOneF64 {
51    #[inline]
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        write!(f, "{:e}", self.0)
54    }
55}
56
57impl fmt::UpperExp for FiniteAboveNegOneF64 {
58    #[inline]
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        write!(f, "{:E}", self.0)
61    }
62}
63
64impl FiniteAboveNegOneF64 {
65    /// Creates a new `FiniteAboveNegOneF64` if the value is greater than -1.0, finite and not [`NAN`](f64::NAN).
66    #[inline]
67    pub const fn new(value: f64) -> Option<Self> {
68        if value > -1.0 && value.is_finite() {
69            Some(Self(value))
70        } else {
71            None
72        }
73    }
74
75    /// Returns the inner `f64` value of the `FiniteAboveNegOneF64`.
76    #[inline]
77    pub const fn get(&self) -> f64 {
78        self.0
79    }
80
81    #[inline]
82    pub const fn checked_add(self, other: f64) -> Option<Self> {
83        Self::new(self.get() + other)
84    }
85
86    #[inline]
87    pub const fn checked_sub(self, other: f64) -> Option<Self> {
88        Self::new(self.get() - other)
89    }
90
91    #[inline]
92    pub const fn checked_mul(self, other: f64) -> Option<Self> {
93        Self::new(self.get() * other)
94    }
95
96    #[inline]
97    pub const fn checked_div(self, denominator: f64) -> Option<Self> {
98        Self::new(self.get() / denominator)
99    }
100
101    #[inline]
102    pub fn checked_pow(self, exp: f64) -> Option<Self> {
103        Self::new(pow(self.get(), exp))
104    }
105}
106
107impl Default for FiniteAboveNegOneF64 {
108    /// Gives a `FiniteAboveNegOneF64` that represents zero.
109    #[inline]
110    fn default() -> Self {
111        Self(0.0)
112    }
113}
114
115#[derive(Debug, Copy, Clone, PartialEq, Eq)]
116/// The error that that is returned when trying to convert a [`f64`] value that is less than or equal to -1.0
117/// into a [`FiniteAboveNegOneF64`] with the [`TryFrom`] trait.
118pub struct InfNanNegOneOrLessError;
119
120impl fmt::Display for InfNanNegOneOrLessError {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        write!(
123            f,
124            "attempted to convert a value that is infinite, NAN, or less than or equal to -1.0 to a `FiniteAboveNegOneF64`"
125        )
126    }
127}
128
129impl core::error::Error for InfNanNegOneOrLessError {}
130
131impl TryFrom<f64> for FiniteAboveNegOneF64 {
132    type Error = InfNanNegOneOrLessError;
133
134    /// Tries to convert a `f64` into a `FiniteAboveNegOneF64`.
135    ///
136    /// Returns an error if the value is less than or equal to -1.0.
137    #[inline]
138    fn try_from(value: f64) -> Result<Self, Self::Error> {
139        FiniteAboveNegOneF64::new(value).ok_or(InfNanNegOneOrLessError)
140    }
141}
142
143impl From<FiniteAboveNegOneF64> for f64 {
144    #[inline]
145    fn from(value: FiniteAboveNegOneF64) -> Self {
146        value.0
147    }
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
151/// An error that can occur when parsing a `&str` into a [`FiniteAboveNegOneF64`].
152pub enum ParseFiniteAboveNegOneF64Error {
153    ParseError(ParseFloatError),
154    InvalidValue(InfNanNegOneOrLessError),
155}
156
157impl fmt::Display for ParseFiniteAboveNegOneF64Error {
158    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
159        match self {
160            ParseFiniteAboveNegOneF64Error::ParseError(e) => write!(f, "{e}"),
161            ParseFiniteAboveNegOneF64Error::InvalidValue(e) => write!(f, "{e}"),
162        }
163    }
164}
165
166impl core::error::Error for ParseFiniteAboveNegOneF64Error {
167    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
168        match self {
169            ParseFiniteAboveNegOneF64Error::ParseError(e) => Some(e),
170            ParseFiniteAboveNegOneF64Error::InvalidValue(e) => Some(e),
171        }
172    }
173}
174
175impl FromStr for FiniteAboveNegOneF64 {
176    type Err = ParseFiniteAboveNegOneF64Error;
177
178    fn from_str(s: &str) -> Result<Self, Self::Err> {
179        match s.parse::<f64>() {
180            Ok(value) => value
181                .try_into()
182                .map_err(ParseFiniteAboveNegOneF64Error::InvalidValue),
183            Err(e) => Err(ParseFiniteAboveNegOneF64Error::ParseError(e)),
184        }
185    }
186}
187
188/// This macro implements the data access API for the given quadrature rule struct that contains
189/// a field named `node_weight_pairs` of the type `Box<[(Node, Weight)]>`.
190/// It takes in the name of the quadrature rule struct as well as the names it should give the iterators
191/// over its nodes, weights, and both, as well as the iterator returned by the [`IntoIterator`] trait.
192#[doc(hidden)]
193#[macro_export]
194macro_rules! __impl_node_weight_rule {
195    (
196        // The name of the quadrature rule struct, e.g. GaussLegendre.
197        $quadrature_rule:ident,
198        // The name that the iterator over the nodes should have, e.g. GaussLegendreNodes.
199        $quadrature_rule_nodes:ident,
200        // The name that the iterator over the weights should have, e.g. GaussLegendreWeights.
201        $quadrature_rule_weights:ident,
202        // The name that the iterator returned when calling the `iter` function should have,
203        // e.g. GaussLegendreIter.
204        $quadrature_rule_iter:ident,
205        // The name of the iterator returned by the by the IntoIterator trait.
206        $quadrature_rule_into_iter:ident
207    ) => {
208        // Implements functions for accessing the underlying data of the quadrature rule struct
209        // in a way the adheres to the API guidelines: <https://rust-lang.github.io/api-guidelines/naming.html>.
210        // The functions in these impl blocks all have an #[inline] directive because they are trivial.
211
212        // Lets the user do
213        // for (node, weight) in QuadratuleRule::new(...) {
214        //     ...
215        // }
216        impl ::core::iter::IntoIterator for $quadrature_rule {
217            type IntoIter = $quadrature_rule_into_iter;
218            type Item = ($crate::Node, $crate::Weight);
219            #[inline]
220            fn into_iter(self) -> Self::IntoIter {
221                $quadrature_rule_into_iter::new(self.node_weight_pairs.into_iter())
222            }
223        }
224
225        // Lets the user do
226        // let rule = QuadratureRule::new(...);
227        // for &(node, weight) in &rule {
228        //     ...
229        // }
230        // rule.integrate(...) // <-- still available
231        impl<'a> ::core::iter::IntoIterator for &'a $quadrature_rule {
232            type IntoIter = $quadrature_rule_iter<'a>;
233            type Item = &'a ($crate::Node, $crate::Weight);
234            #[inline]
235            fn into_iter(self) -> Self::IntoIter {
236                $quadrature_rule_iter::new(self.node_weight_pairs.iter())
237            }
238        }
239
240        impl $quadrature_rule {
241            /// Returns an iterator over the nodes of the quadrature rule.
242            #[inline]
243            pub fn nodes(&self) -> $quadrature_rule_nodes<'_> {
244                $quadrature_rule_nodes::new(self.node_weight_pairs.iter().map(|p| &p.0))
245            }
246
247            /// Returns an iterator over the weights of the quadrature rule.
248            #[inline]
249            pub fn weights(&self) -> $quadrature_rule_weights<'_> {
250                $quadrature_rule_weights::new(self.node_weight_pairs.iter().map(|p| &p.1))
251            }
252
253            /// Returns an iterator over the node-weight pairs of the quadrature rule.
254            #[inline]
255            pub fn iter(&self) -> $quadrature_rule_iter<'_> {
256                $quadrature_rule_iter::new(self.node_weight_pairs.iter())
257            }
258
259            /// Returns a slice of all the node-weight pairs of the quadrature rule.
260            #[inline]
261            pub fn as_node_weight_pairs(&self) -> &[($crate::Node, $crate::Weight)] {
262                &self.node_weight_pairs
263            }
264
265            /// Converts the quadrature rule into a boxed slice of node-weight pairs.
266            ///
267            /// This function just returns the underlying data without any computation or cloning.
268            #[inline]
269            #[must_use = "`self` will be dropped if the result is not used"]
270            pub fn into_node_weight_pairs(self) -> ::alloc::boxed::Box<[($crate::Node, $crate::Weight)]> {
271                self.node_weight_pairs
272            }
273
274            /// Returns the degree of the quadrature rule.
275            #[inline]
276            pub fn degree(&self) -> ::core::primitive::usize {
277                self.node_weight_pairs.len()
278            }
279        }
280
281        // region: QuadratureRuleNodes
282
283        /// An iterator over the nodes of the quadrature rule.
284        #[derive(::core::fmt::Debug, ::core::clone::Clone)]
285        #[must_use = "iterators are lazy and do nothing unless consumed"]
286        pub struct $quadrature_rule_nodes<'a>(
287            // This horrible type is just the fully qualified path of the type returned
288            // by `slice.iter().map(|(x, _)| x)`.
289            ::core::iter::Map<
290                ::core::slice::Iter<'a, ($crate::Node, $crate::Weight)>,
291                fn(&'a ($crate::Node, $crate::Weight)) -> &'a $crate::Node,
292            >,
293        );
294
295        impl<'a> $quadrature_rule_nodes<'a> {
296            #[inline]
297            const fn new(
298                iter_map: ::core::iter::Map<
299                    ::core::slice::Iter<'a, ($crate::Node, $crate::Weight)>,
300                    fn(&'a ($crate::Node, $crate::Weight)) -> &'a $crate::Node,
301                >,
302            ) -> Self {
303                Self(iter_map)
304            }
305        }
306
307        $crate::__impl_slice_iterator_newtype_traits!{$quadrature_rule_nodes<'a>, &'a $crate::Node}
308
309        // endregion: QuadratureRuleNodes
310
311        // region: QuadratureRuleWeights
312
313        /// An iterator over the weights of the quadrature rule.
314        #[derive(::core::fmt::Debug, ::core::clone::Clone)]
315        #[must_use = "iterators are lazy and do nothing unless consumed"]
316        pub struct $quadrature_rule_weights<'a>(
317            // Same as the previous horrible type, but maps out the weight instead of the node.
318            ::core::iter::Map<
319                ::core::slice::Iter<'a, ($crate::Node, $crate::Weight)>,
320                fn(&'a ($crate::Node, $crate::Weight)) -> &'a $crate::Weight,
321            >,
322        );
323
324        impl<'a> $quadrature_rule_weights<'a> {
325            #[inline]
326            const fn new(
327                iter_map: ::core::iter::Map<
328                    ::core::slice::Iter<'a, ($crate::Node, $crate::Weight)>,
329                    fn(&'a ($crate::Node, $crate::Weight)) -> &'a $crate::Weight,
330                >,
331            ) -> Self {
332                Self(iter_map)
333            }
334        }
335
336        $crate::__impl_slice_iterator_newtype_traits!{$quadrature_rule_weights<'a>, &'a $crate::Weight}
337
338        // endregion: QuadratureRuleWeights
339
340        // region: QuadratureRuleIter
341
342        /// An iterator over the node-weight pairs of the quadrature rule.
343        ///
344        /// Created by the `iter` function on the quadrature rule struct.
345        #[derive(::core::fmt::Debug, ::core::clone::Clone)]
346        #[must_use = "iterators are lazy and do nothing unless consumed"]
347        pub struct $quadrature_rule_iter<'a>(
348            ::core::slice::Iter<'a, ($crate::Node, $crate::Weight)>,
349        );
350
351        impl<'a> $quadrature_rule_iter<'a> {
352            #[inline]
353            const fn new(
354                node_weight_pairs: ::core::slice::Iter<'a, ($crate::Node, $crate::Weight)>,
355            ) -> Self {
356                Self(node_weight_pairs)
357            }
358
359            /// Views the underlying data as a subslice of the original data.
360            ///
361            /// See [`core::slice::Iter::as_slice`] for more information.
362            #[inline]
363            pub fn as_slice(&self) -> &'a [($crate::Node, $crate::Weight)] {
364                self.0.as_slice()
365            }
366        }
367
368        impl<'a> ::core::convert::AsRef<[($crate::Node, $crate::Weight)]>
369            for $quadrature_rule_iter<'a>
370        {
371            #[inline]
372            fn as_ref(&self) -> &[($crate::Node, $crate::Weight)] {
373                self.0.as_ref()
374            }
375        }
376
377        $crate::__impl_slice_iterator_newtype_traits!{$quadrature_rule_iter<'a>, &'a ($crate::Node, $crate::Weight)}
378
379        // endregion: QuadratureRuleIter
380
381        // region: QuadratureRuleIntoIter
382
383        /// An owning iterator over the node-weight pairs of the quadrature rule.
384        ///
385        /// Created by the [`IntoIterator`] trait implementation of the quadrature rule struct.
386        #[derive(::core::fmt::Debug, ::core::clone::Clone)]
387        #[must_use = "iterators are lazy and do nothing unless consumed"]
388        pub struct $quadrature_rule_into_iter(::alloc::vec::IntoIter<($crate::Node, $crate::Weight)>);
389
390        impl $quadrature_rule_into_iter {
391            #[inline]
392            const fn new(
393                node_weight_pairs: ::alloc::vec::IntoIter<($crate::Node, $crate::Weight)>,
394            ) -> Self {
395                Self(node_weight_pairs)
396            }
397
398            /// Views the underlying data as a subslice of the original data.
399            ///
400            /// See [`alloc::vec::IntoIter::as_slice`] for more information.
401            #[inline]
402            pub fn as_slice(&self) -> &[($crate::Node, $crate::Weight)] {
403                self.0.as_slice()
404            }
405        }
406
407        impl ::core::convert::AsRef<[($crate::Node, $crate::Weight)]>
408            for $quadrature_rule_into_iter
409        {
410            #[inline]
411            fn as_ref(&self) -> &[($crate::Node, $crate::Weight)] {
412                self.0.as_ref()
413            }
414        }
415
416        $crate::__impl_slice_iterator_newtype_traits!{$quadrature_rule_into_iter, ($crate::Node, $crate::Weight)}
417
418        // endregion: QuadratureRuleIntoIter
419    };
420}
421
422/// Implements the [`Iterator`], [`DoubleEndedIterator`], [`ExactSizeIterator`] and [`FusedIterator`](core::iter::FusedIterator) traits for a struct
423/// that wraps an iterator that has those traits. Takes in the name of the struct and optionally its lifetime
424/// as well as the type returned by the iterator.
425#[macro_export]
426#[doc(hidden)]
427macro_rules! __impl_slice_iterator_newtype_traits {
428    ($iterator:ident$(<$a:lifetime>)?, $item:ty) => {
429        impl$(<$a>)? ::core::iter::Iterator for $iterator$(<$a>)? {
430            type Item = $item;
431            #[inline]
432            fn next(&mut self) -> ::core::option::Option<Self::Item> {
433                self.0.next()
434            }
435
436            #[inline]
437            fn size_hint(&self) -> (::core::primitive::usize, ::core::option::Option<::core::primitive::usize>) {
438                self.0.size_hint()
439            }
440
441            // These methods by default call the `next` method a lot to access data.
442            // This isn't needed in our case, since the data underlying the iterator is
443            // a slice, which has O(1) access to any element.
444            // As a result we reimplement them and just delegate to the builtin slice iterator methods.
445
446            #[inline]
447            fn nth(&mut self, index: ::core::primitive::usize) -> ::core::option::Option<Self::Item> {
448                self.0.nth(index)
449            }
450
451            #[inline]
452            fn count(self) -> ::core::primitive::usize {
453                self.0.count()
454            }
455
456            #[inline]
457            fn last(mut self) -> ::core::option::Option<Self::Item> {
458                self.0.next_back()
459            }
460        }
461
462        impl$(<$a>)? ::core::iter::DoubleEndedIterator for $iterator$(<$a>)? {
463            #[inline]
464            fn next_back(&mut self) -> ::core::option::Option<Self::Item> {
465                self.0.next_back()
466            }
467
468            #[inline]
469            fn nth_back(&mut self, n: ::core::primitive::usize) -> ::core::option::Option<Self::Item> {
470                self.0.nth_back(n)
471            }
472        }
473
474        impl$(<$a>)? ::core::iter::ExactSizeIterator for $iterator$(<$a>)? {
475            #[inline]
476            fn len(&self) -> ::core::primitive::usize {
477                self.0.len()
478            }
479        }
480        impl$(<$a>)? ::core::iter::FusedIterator for $iterator$(<$a>)? {}
481    };
482}
483
484/// This macro implements the data access API for rules that have only nodes and no weights.
485/// It takes in the name of the a rule struct that contans a field with the name `degree`
486/// of the type `NonZeroU32`. As well as the names it should give the iterator over its
487/// nodes and the iterator returned by the [`IntoIterator`] trait.
488#[macro_export]
489#[doc(hidden)]
490macro_rules! __impl_node_rule {
491    ($quadrature_rule:ident, $quadrature_rule_iter:ident) => {
492        // Lets the user do
493        // for node in QuadratureRule::new(...) {
494        //    ...
495        // }
496        impl ::core::iter::IntoIterator for $quadrature_rule {
497            type Item = $crate::Node;
498            type IntoIter = $quadrature_rule_iter;
499            #[inline]
500            fn into_iter(self) -> Self::IntoIter {
501                self.iter()
502            }
503        }
504
505        // Lets the user do
506        // let rule = QuadratureRule::new(...);
507        // for &node in &rule {
508        //     ...
509        // }
510        // rule.integrate(...) // <--- still available
511        impl<'a> ::core::iter::IntoIterator for &'a $quadrature_rule {
512            type IntoIter = $quadrature_rule_iter;
513            type Item = $crate::Node;
514            #[inline]
515            fn into_iter(self) -> Self::IntoIter {
516                self.iter()
517            }
518        }
519
520        impl $quadrature_rule {
521            /// Returns an iterator over the nodes of the rule.
522            #[inline]
523            pub fn iter(&self) -> $quadrature_rule_iter {
524                $quadrature_rule_iter::new((0..self.degree.get()).map(|d| d as $crate::Node))
525            }
526
527            /// Returns the degree of the rule.
528            #[inline]
529            pub fn degree(&self) -> ::core::num::NonZeroU32 {
530                self.degree
531            }
532        }
533
534        // region: QuadratureRuleIter
535
536        /// An iterator of the nodes of the rule.
537        #[derive(Debug, Clone)]
538        #[must_use = "iterators are lazy and do nothing unless consumed"]
539        pub struct $quadrature_rule_iter(
540            ::core::iter::Map<core::ops::Range<u32>, fn(u32) -> $crate::Node>,
541        );
542
543        impl $quadrature_rule_iter {
544            #[inline]
545            const fn new(
546                iter: ::core::iter::Map<core::ops::Range<u32>, fn(u32) -> $crate::Node>,
547            ) -> Self {
548                Self(iter)
549            }
550        }
551
552        $crate::__impl_slice_iterator_newtype_traits! {$quadrature_rule_iter, $crate::Node}
553
554        // endregion: QuadratureRuleIter
555    };
556}
557
558#[cfg(test)]
559mod tests {
560    use super::*;
561    use alloc::{boxed::Box, vec::Vec};
562    use core::f64;
563    use core::num::NonZeroUsize;
564
565    #[derive(Debug, Clone, PartialEq)]
566    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
567    pub struct MockQuadrature {
568        node_weight_pairs: Box<[(Node, Weight)]>,
569    }
570
571    impl MockQuadrature {
572        pub fn new(deg: NonZeroUsize) -> Self {
573            Self {
574                node_weight_pairs: (0..deg.get()).map(|d| (d as f64, 1.0)).collect(),
575            }
576        }
577
578        pub fn integrate<F>(&self, a: f64, b: f64, integrand: F) -> f64
579        where
580            F: Fn(f64) -> f64,
581        {
582            let rect_width = (b - a) / self.node_weight_pairs.len() as f64;
583            let result: f64 = self
584                .node_weight_pairs
585                .iter()
586                .map(|(x_val, w_val)| integrand(a + rect_width * (0.5 + x_val)) * w_val)
587                .sum();
588            result * rect_width
589        }
590    }
591
592    __impl_node_weight_rule! {MockQuadrature, MockQuadratureNodes, MockQuadratureWeights, MockQuadratureIter, MockQuadratureIntoIter}
593
594    #[test]
595    fn test_macro_implementation() {
596        let quad = MockQuadrature::new(5.try_into().unwrap());
597        assert_eq!(quad.integrate(0.0, 1.0, |x| x), 0.5);
598
599        // Test iterator implementations
600        assert_eq!(quad.nodes().count(), 5);
601        assert_eq!(quad.weights().count(), 5);
602        assert_eq!(quad.iter().count(), 5);
603        assert_eq!(quad.as_node_weight_pairs().len(), 5);
604
605        // Test IntoIterator implementation
606        let vec: Vec<(Node, Weight)> = quad.clone().into_iter().collect();
607        assert_eq!(vec.len(), 5);
608
609        // Test into_node_weight_pairs
610        let pairs = quad.clone().into_node_weight_pairs();
611        assert_eq!(pairs.len(), 5);
612
613        // Test degree
614        assert_eq!(quad.degree(), 5);
615
616        // test iter functions
617        let mut quad_iter = (&quad).into_iter();
618        assert_eq!(quad_iter.next().unwrap().0, 0.0);
619        assert_eq!(quad_iter.next().unwrap().0, 1.0);
620
621        let quad_iter = (&quad).into_iter();
622        assert_eq!(quad_iter.size_hint(), (5, Some(5)));
623
624        let mut quad_iter = (&quad).into_iter();
625        assert_eq!(quad_iter.nth(2).unwrap().0, 2.0);
626
627        let quad_iter = (&quad).into_iter();
628        assert_eq!(quad_iter.count(), 5);
629
630        let quad_iter = (&quad).into_iter();
631        assert_eq!(quad_iter.last().unwrap().0, 4.0);
632
633        let mut quad_iter = (&quad).into_iter();
634        assert_eq!(quad_iter.next_back().unwrap().0, 4.0);
635
636        let mut quad_iter = (&quad).into_iter();
637        assert_eq!(quad_iter.nth_back(1).unwrap().0, 3.0);
638
639        let quad_iter = (&quad).into_iter();
640        assert_eq!(quad_iter.len(), 5);
641
642        // test slice
643        let quad_slice = (&quad).into_iter().as_slice();
644        assert_eq!(quad_slice.len(), 5);
645        assert_eq!(quad_slice[2].0, 2.0);
646
647        // test as_ref
648        let quad_iter = (&quad).into_iter();
649        let quad_ref = quad_iter.as_ref();
650        assert_eq!(quad_ref.len(), 5);
651        assert_eq!(quad_ref[2].0, 2.0);
652    }
653
654    #[test]
655    fn test_new_finite_above_neg_one_f64() {
656        assert!(FiniteAboveNegOneF64::new(0.0).is_some());
657        assert!(FiniteAboveNegOneF64::new(-0.5).is_some());
658        assert!(FiniteAboveNegOneF64::new(-1.0).is_none());
659        assert!(FiniteAboveNegOneF64::new(f64::NAN).is_none());
660        assert!(FiniteAboveNegOneF64::new(f64::INFINITY).is_none());
661        assert!(FiniteAboveNegOneF64::new(f64::NEG_INFINITY).is_none());
662    }
663
664    #[test]
665    fn test_try_from_f64() {
666        assert!(FiniteAboveNegOneF64::try_from(0.0).is_ok());
667        assert!(FiniteAboveNegOneF64::try_from(-0.5).is_ok());
668        assert!(FiniteAboveNegOneF64::try_from(-1.0).is_err());
669        assert!(FiniteAboveNegOneF64::try_from(f64::NAN).is_err());
670        assert!(FiniteAboveNegOneF64::try_from(f64::INFINITY).is_err());
671        assert!(FiniteAboveNegOneF64::try_from(f64::NEG_INFINITY).is_err());
672    }
673
674    #[test]
675    fn test_from_str() {
676        assert_eq!(FiniteAboveNegOneF64::from_str("0.0").unwrap().get(), 0.0);
677        assert_eq!(FiniteAboveNegOneF64::from_str("-0.5").unwrap().get(), -0.5);
678        assert_eq!(
679            FiniteAboveNegOneF64::from_str("-1.0"),
680            Err(ParseFiniteAboveNegOneF64Error::InvalidValue(
681                InfNanNegOneOrLessError
682            ))
683        );
684        assert_eq!(
685            FiniteAboveNegOneF64::from_str("NAN"),
686            Err(ParseFiniteAboveNegOneF64Error::InvalidValue(
687                InfNanNegOneOrLessError
688            ))
689        );
690        assert_eq!(
691            FiniteAboveNegOneF64::from_str("INF"),
692            Err(ParseFiniteAboveNegOneF64Error::InvalidValue(
693                InfNanNegOneOrLessError
694            ))
695        );
696        assert_eq!(
697            FiniteAboveNegOneF64::from_str("-INF"),
698            Err(ParseFiniteAboveNegOneF64Error::InvalidValue(
699                InfNanNegOneOrLessError
700            ))
701        );
702    }
703
704    #[test]
705    fn test_finite_above_neg_one_f64_arithmetic() {
706        let value = FiniteAboveNegOneF64::new(0.5).unwrap();
707        assert_eq!(value.checked_add(0.5).unwrap().get(), 1.0);
708        assert!(value.checked_add(f64::INFINITY).is_none());
709        assert!(value.checked_sub(2.0).is_none());
710        assert!(value.checked_add(f64::NAN).is_none());
711        assert_eq!(value.checked_mul(2.0).unwrap().get(), 1.0);
712        assert!(value.checked_div(0.0).is_none());
713        assert!(value.checked_div(0.0).is_none());
714        assert!(value.checked_div(f64::NAN).is_none());
715        assert_eq!(value.checked_pow(2.0).unwrap().get(), 0.25);
716        assert!(
717            FiniteAboveNegOneF64::new(2.0)
718                .unwrap()
719                .checked_pow(f64::INFINITY)
720                .is_none()
721        );
722    }
723
724    #[test]
725    fn test_from_impl() {
726        let value = FiniteAboveNegOneF64::new(0.5).unwrap();
727        let f64_value: f64 = value.into();
728        assert_eq!(f64_value, 0.5);
729
730        let value_from_f64: Result<FiniteAboveNegOneF64, _> = 0.5f64.try_into();
731        assert!(value_from_f64.is_ok());
732        assert_eq!(value_from_f64.unwrap().get(), 0.5);
733
734        let value_from_invalid: Result<FiniteAboveNegOneF64, _> = (-1.0f64).try_into();
735        assert!(value_from_invalid.is_err());
736    }
737
738    #[test]
739    fn test_default() {
740        let value = FiniteAboveNegOneF64::default();
741        assert_eq!(value.get(), 0.0);
742    }
743}