uom/si/
angle.rs

1//! Angle (dimensionless quantity).
2
3#[cfg(feature = "std")]
4use super::ratio::Ratio;
5
6quantity! {
7    /// Angle (dimensionless quantity).
8    quantity: Angle; "angle";
9    /// Dimension of angle, 1 (dimensionless).
10    dimension: ISQ<
11        Z0,     // length
12        Z0,     // mass
13        Z0,     // time
14        Z0,     // electric current
15        Z0,     // thermodynamic temperature
16        Z0,     // amount of substance
17        Z0>;    // luminous intensity
18    kind: dyn (crate::si::marker::AngleKind);
19    units {
20        /// SI derived unit of angle. It is the angle subtended at the center of a circle by an
21        /// arc that is equal in length to the radius of the circle.
22        @radian: 1.0_E0; "rad", "radian", "radians";
23        @revolution: 6.283_185_307_179_586_E0; "r", "revolution", "revolutions";
24        @degree: 1.745_329_251_994_329_5_E-2; "°", "degree", "degrees";
25        @gon: 1.570_796_326_794_896_7_E-2; "gon", "gon", "gons";
26        @mil: 9.817_477_E-4; "mil", "mil", "mils";
27        @minute: 2.908_882_086_657_216_E-4; "′", "minute", "minutes";
28        @second: 4.848_136_811_095_36_E-6; "″", "second", "seconds";
29    }
30}
31
32#[cfg(feature = "f32")]
33impl Angle<crate::si::SI<f32>, f32> {
34    /// A half turn, i.e. an angle with a value of π as measured in radians
35    pub const HALF_TURN: Self = Self {
36        dimension: crate::lib::marker::PhantomData,
37        units: crate::lib::marker::PhantomData,
38        value: crate::lib::f32::consts::PI,
39    };
40
41    /// A full turn, i.e. an angle with a value of 2π as measured in radians
42    pub const FULL_TURN: Self = Self {
43        dimension: crate::lib::marker::PhantomData,
44        units: crate::lib::marker::PhantomData,
45        value: 2. * crate::lib::f32::consts::PI,
46    };
47}
48
49#[cfg(feature = "f64")]
50impl Angle<crate::si::SI<f64>, f64> {
51    /// A half turn, i.e. an angle with a value of π as measured in radians
52    pub const HALF_TURN: Self = Self {
53        dimension: crate::lib::marker::PhantomData,
54        units: crate::lib::marker::PhantomData,
55        value: crate::lib::f64::consts::PI,
56    };
57
58    /// A full turn, i.e. an angle with a value of 2π as measured in radians
59    pub const FULL_TURN: Self = Self {
60        dimension: crate::lib::marker::PhantomData,
61        units: crate::lib::marker::PhantomData,
62        value: 2. * crate::lib::f64::consts::PI,
63    };
64}
65
66/// Implementation of various stdlib trigonometric functions
67#[cfg(feature = "std")]
68impl<U, V> Angle<U, V>
69where
70    U: crate::si::Units<V> + ?Sized,
71    V: crate::num::Float + crate::Conversion<V>,
72{
73    /// Computes the value of the cosine of the angle.
74    #[must_use = "method returns a new number and does not mutate the original value"]
75    #[inline(always)]
76    pub fn cos(self) -> Ratio<U, V> {
77        self.value.cos().into()
78    }
79
80    /// Computes the value of the hyperbolic cosine of the angle.
81    #[must_use = "method returns a new number and does not mutate the original value"]
82    #[inline(always)]
83    pub fn cosh(self) -> Ratio<U, V> {
84        self.value.cosh().into()
85    }
86
87    /// Computes the value of the sine of the angle.
88    #[must_use = "method returns a new number and does not mutate the original value"]
89    #[inline(always)]
90    pub fn sin(self) -> Ratio<U, V> {
91        self.value.sin().into()
92    }
93
94    /// Computes the value of the hyperbolic sine of the angle.
95    #[must_use = "method returns a new number and does not mutate the original value"]
96    #[inline(always)]
97    pub fn sinh(self) -> Ratio<U, V> {
98        self.value.sinh().into()
99    }
100
101    /// Computes the value of both the sine and cosine of the angle.
102    #[must_use = "method returns a new number and does not mutate the original value"]
103    #[inline(always)]
104    pub fn sin_cos(self) -> (Ratio<U, V>, Ratio<U, V>) {
105        let (sin, cos) = self.value.sin_cos();
106        (sin.into(), cos.into())
107    }
108
109    /// Computes the value of the tangent of the angle.
110    #[must_use = "method returns a new number and does not mutate the original value"]
111    #[inline(always)]
112    pub fn tan(self) -> Ratio<U, V> {
113        self.value.tan().into()
114    }
115
116    /// Computes the value of the hyperbolic tangent of the angle.
117    #[must_use = "method returns a new number and does not mutate the original value"]
118    #[inline(always)]
119    pub fn tanh(self) -> Ratio<U, V> {
120        self.value.tanh().into()
121    }
122}
123
124#[cfg(feature = "std")]
125impl<D, U, V> crate::si::Quantity<D, U, V>
126where
127    D: crate::si::Dimension + ?Sized,
128    U: crate::si::Units<V> + ?Sized,
129    V: crate::num::Float + crate::Conversion<V>,
130    radian: crate::Conversion<V, T = V::T>,
131{
132    /// Computes the four quadrant arctangent of self (y) and other (x).
133    #[must_use = "method returns a new number and does not mutate the original value"]
134    #[inline(always)]
135    pub fn atan2(self, other: Self) -> Angle<U, V> {
136        Angle::new::<radian>(self.value.atan2(other.value))
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    storage_types! {
143        use crate::lib::f64::consts::PI;
144        use crate::num::{FromPrimitive, One};
145        use crate::si::angle as a;
146        use crate::si::quantities::*;
147        use crate::tests::Test;
148
149        #[test]
150        fn check_units() {
151            Test::assert_eq(&Angle::new::<a::radian>(V::from_f64(2.0 * PI).unwrap()),
152                &Angle::new::<a::revolution>(V::one()));
153            Test::assert_eq(&Angle::new::<a::degree>(V::from_f64(360.0).unwrap()),
154                &Angle::new::<a::revolution>(V::one()));
155            Test::assert_approx_eq(&Angle::new::<a::gon>(V::from_f64(400.0).unwrap()),
156                &Angle::new::<a::revolution>(V::one()));
157            Test::assert_eq(&Angle::new::<a::minute>(V::from_f64(60.0).unwrap()),
158                &Angle::new::<a::degree>(V::one()));
159            Test::assert_eq(&Angle::new::<a::second>(V::from_f64(60.0 * 60.0).unwrap()),
160                &Angle::new::<a::degree>(V::one()));
161        }
162    }
163
164    #[cfg(feature = "std")]
165    mod trig {
166        storage_types! {
167            types: Float;
168
169            use crate::lib::f64::consts::PI;
170            use crate::num::{FromPrimitive, Zero};
171            use crate::si::angle as a;
172            use crate::si::length as l;
173            use crate::si::quantities::*;
174            use crate::tests::Test;
175
176            #[test]
177            fn sanity() {
178                let zero: Angle<V> = Angle::zero();
179                let nzero: Angle<V> = -Angle::zero();
180                let pi: Angle<V> = Angle::new::<a::radian>(V::from_f64(PI).unwrap());
181                let half: Angle<V> = Angle::new::<a::radian>(V::from_f64(PI / 2.0).unwrap());
182
183                Test::assert_approx_eq(&zero.cos().into(), &1.0);
184                Test::assert_approx_eq(&nzero.cos().into(), &1.0);
185
186                Test::assert_approx_eq(&pi.cos().into(), &-1.0);
187                Test::assert_approx_eq(&half.cos().into(), &0.0);
188
189                Test::assert_approx_eq(&zero.sin().into(), &0.0);
190                Test::assert_approx_eq(&nzero.sin().into(), &0.0);
191
192                // Float inaccuracy does not guarantee approximate values
193                // In these tests, it diverges slightly over the epsilon value
194                //Test::assert_approx_eq(&pi.sin().into(), &0.0);
195                //Test::assert_approx_eq(&half.sin().into(), &1.0);
196
197                Test::assert_approx_eq(&zero.tan().into(), &0.0);
198                Test::assert_approx_eq(&nzero.tan().into(), &0.0);
199
200                //Test::assert_approx_eq(&pi.tan(), &0.0);
201                // Cannot test for PI / 2 equality as it diverges to infinity
202                // Float inaccuracy does not guarantee a NAN or INFINITY result
203                //let result = half.tan().into();
204                //assert!(result == V::nan() || result == V::infinity());
205
206                Test::assert_approx_eq(&zero.cosh().into(), &1.0);
207                Test::assert_approx_eq(&nzero.cosh().into(), &1.0);
208
209                Test::assert_approx_eq(&zero.sinh().into(), &0.0);
210                Test::assert_approx_eq(&nzero.sinh().into(), &0.0);
211
212                Test::assert_approx_eq(&zero.tanh().into(), &0.0);
213                Test::assert_approx_eq(&nzero.tanh().into(), &0.0);
214            }
215
216            quickcheck! {
217                #[allow(trivial_casts)]
218                fn atan2(y: V, x: V) -> bool {
219                    Test::eq(&y.atan2(x),
220                        &Length::new::<l::meter>(y).atan2(Length::new::<l::meter>(x)).get::<a::radian>())
221                }
222            }
223
224            #[test]
225            fn turns() {
226                Test::assert_approx_eq(&Angle::<V>::HALF_TURN.cos().into(), &-1.0);
227                Test::assert_approx_eq(&Angle::<V>::FULL_TURN.cos().into(), &1.0);
228            }
229        }
230    }
231}