1#![allow(clippy::float_cmp, clippy::suboptimal_flops)]
62
63use crate::vector2d;
64use crate::{Degrees, Radians, Validate, two_sum};
65use core::{cmp::Ordering, ops::Neg};
66use num_traits::{Float, float::FloatConst};
67
68pub const SQ_EPSILON: f64 = f64::EPSILON * f64::EPSILON;
70
71#[allow(clippy::excessive_precision, clippy::unreadable_literal)]
74pub const SQRT_3: f64 = 1.732050807568877293527446341505872367_f64;
75
76pub const COS_30_DEGREES: f64 = SQRT_3 / 2.0;
78pub const MAX_LINEAR_SIN_ANGLE: f64 = 9.67e7 * f64::EPSILON;
80pub const MAX_COS_ANGLE_IS_ONE: f64 = 3.35e7 * f64::EPSILON;
82
83pub const THIRTY: f64 = 30.0;
84pub const FORTY_FIVE: f64 = 45.0;
85
86#[must_use]
90fn to_radians<T: Float + FloatConst>(angle: Degrees<T>) -> Radians<T> {
91 let thirty = T::from(THIRTY).expect("Could not convert constant to Float");
92 if angle.0.abs() == thirty {
93 Radians(T::FRAC_PI_6().copysign(angle.0))
94 } else {
95 Radians(angle.0.to_radians())
96 }
97}
98
99#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd)]
102#[repr(transparent)]
103pub struct UnitNegRange<T: Float>(pub T);
104
105impl<T: Float> Default for UnitNegRange<T> {
106 fn default() -> Self {
107 Self(T::zero())
108 }
109}
110
111impl<T: Float> UnitNegRange<T> {
112 #[must_use]
125 pub fn clamp(value: T) -> Self {
126 Self(value.clamp(-T::one(), T::one()))
127 }
128
129 #[must_use]
131 pub fn abs(self) -> Self {
132 Self(self.0.abs())
133 }
134}
135
136impl<T: Float> Validate for UnitNegRange<T> {
137 fn is_valid(&self) -> bool {
151 (-T::one()..=T::one()).contains(&self.0)
152 }
153}
154
155impl<T: Float> Neg for UnitNegRange<T> {
156 type Output = Self;
157
158 fn neg(self) -> Self {
162 Self(T::zero() - self.0)
163 }
164}
165
166#[must_use]
173pub fn sq_a_minus_sq_b<T: Float>(a: UnitNegRange<T>, b: UnitNegRange<T>) -> UnitNegRange<T> {
174 UnitNegRange::<T>((a.0 - b.0) * (a.0 + b.0))
175}
176
177#[must_use]
184pub fn one_minus_sq_value<T: Float>(a: UnitNegRange<T>) -> UnitNegRange<T> {
185 sq_a_minus_sq_b(UnitNegRange(T::one()), a)
186}
187
188#[must_use]
203pub fn swap_sin_cos<T: Float>(a: UnitNegRange<T>) -> UnitNegRange<T> {
204 UnitNegRange(one_minus_sq_value(a).0.sqrt())
205}
206
207#[allow(clippy::missing_panics_doc)]
221#[must_use]
222pub fn cosine_from_sine<T: Float>(a: UnitNegRange<T>, sign: T) -> UnitNegRange<T> {
223 let max_cos_angle_is_one =
224 T::from(MAX_COS_ANGLE_IS_ONE).expect("Could not convert constant to Float");
225 if a.0.abs() > max_cos_angle_is_one {
226 let b = swap_sin_cos(a);
227 if b.0 > T::zero() {
228 UnitNegRange(b.0.copysign(sign))
229 } else {
230 b
231 }
232 } else {
233 UnitNegRange(T::one().copysign(sign))
234 }
235}
236
237#[allow(clippy::missing_panics_doc)]
241#[must_use]
242pub fn sine<T: Float + FloatConst>(angle: Radians<T>) -> UnitNegRange<T> {
243 let max_linear_sin_angle =
244 T::from(MAX_LINEAR_SIN_ANGLE).expect("Could not convert constant to Float");
245 let angle_abs = angle.0.abs();
246 if angle_abs == T::FRAC_PI_4() {
247 UnitNegRange(T::FRAC_1_SQRT_2().copysign(angle.0))
248 } else if angle_abs > max_linear_sin_angle {
249 UnitNegRange(angle.0.sin())
250 } else {
251 UnitNegRange(angle.0)
252 }
253}
254
255#[must_use]
259pub fn cosine<T: Float + FloatConst>(angle: Radians<T>, sin: UnitNegRange<T>) -> UnitNegRange<T> {
260 let angle_abs = angle.0.abs();
261 if angle_abs == T::FRAC_PI_4() {
262 UnitNegRange(T::FRAC_1_SQRT_2().copysign(T::FRAC_PI_2() - angle_abs))
263 } else {
264 cosine_from_sine(sin, T::FRAC_PI_2() - angle_abs)
265 }
266}
267
268#[must_use]
275fn assign_sin_cos_to_quadrant<T: Float>(
276 sin: UnitNegRange<T>,
277 cos: UnitNegRange<T>,
278 q: i32,
279) -> (UnitNegRange<T>, UnitNegRange<T>) {
280 match q & 3 {
281 1 => (cos, -sin), 2 => (-sin, -cos), 3 => (-cos, sin), _ => (sin, cos),
285 }
286}
287
288#[must_use]
301pub fn sincos<T>(radians: Radians<T>) -> (UnitNegRange<T>, UnitNegRange<T>)
302where
303 T: Float + FloatConst,
304 f64: From<T>,
305{
306 let radians = f64::from(radians.0);
307 let rq = libm::remquo(radians, core::f64::consts::FRAC_PI_2);
308
309 let radians_q = T::from(rq.0).expect("Could not convert value to Float");
311 let radians_q = Radians(radians_q);
312 let sin = sine(radians_q);
313 assign_sin_cos_to_quadrant(sin, cosine(radians_q, sin), rq.1)
314}
315
316#[must_use]
330pub fn sincos_diff<T>(a: Radians<T>, b: Radians<T>) -> (UnitNegRange<T>, UnitNegRange<T>)
331where
332 T: Float + FloatConst,
333 f64: From<T>,
334{
335 let delta = two_sum(a.0, -b.0);
336 let radians = f64::from(delta.0);
337 let rq = libm::remquo(radians, core::f64::consts::FRAC_PI_2);
338
339 let radians_q = T::from(rq.0).expect("Could not convert value to Float");
341 let radians_q = Radians(radians_q + delta.1);
342 let sin = sine(radians_q);
343 assign_sin_cos_to_quadrant(sin, cosine(radians_q, sin), rq.1)
344}
345
346#[must_use]
360pub fn arctan2<T: Float + FloatConst>(sin: UnitNegRange<T>, cos: UnitNegRange<T>) -> Radians<T> {
361 let sin_abs = sin.0.abs();
362 let cos_abs = cos.0.abs();
363
364 let radians_pi_2 = match sin_abs.partial_cmp(&cos_abs).expect("sin or cos is NaN") {
366 Ordering::Equal => T::FRAC_PI_4(),
367 Ordering::Less => sin_abs.atan2(cos_abs),
368 Ordering::Greater => T::FRAC_PI_2() - cos_abs.atan2(sin_abs),
369 };
370
371 let radians_pi = if cos.0 < T::zero() {
373 T::PI() - radians_pi_2
374 } else {
375 radians_pi_2
376 };
377
378 Radians(radians_pi.copysign(sin.0))
380}
381
382#[must_use]
395pub fn sincosd<T>(degrees: Degrees<T>) -> (UnitNegRange<T>, UnitNegRange<T>)
396where
397 T: Float + FloatConst,
398 f64: From<T>,
399{
400 let rq: (f64, i32) = libm::remquo(f64::from(degrees.0), 90.0);
401
402 let radians_q = T::from(rq.0).expect("Could not convert value to Float");
404 let radians_q = to_radians(Degrees(radians_q));
405 let sin = sine(radians_q);
406 assign_sin_cos_to_quadrant(sin, cosine(radians_q, sin), rq.1)
407}
408
409#[must_use]
423pub fn sincosd_diff<T>(a: Degrees<T>, b: Degrees<T>) -> (UnitNegRange<T>, UnitNegRange<T>)
424where
425 T: Float + FloatConst,
426 f64: From<T>,
427{
428 let delta = two_sum(a.0, -b.0);
429 let rq: (f64, i32) = libm::remquo(f64::from(delta.0), 90.0);
430
431 let radians_q = T::from(rq.0).expect("Could not convert value to Float");
433 let radians_q = to_radians(Degrees(radians_q + delta.1));
434 let sin = sine(radians_q);
435 assign_sin_cos_to_quadrant(sin, cosine(radians_q, sin), rq.1)
436}
437
438#[must_use]
442fn arctan2_degrees<T: Float + FloatConst>(sin_abs: T, cos_abs: T) -> T {
443 let half = T::one() / (T::one() + T::one());
444 let thirty = T::from(THIRTY).expect("Could not convert constant to Float");
445 if sin_abs == half {
446 thirty
447 } else {
448 sin_abs.atan2(cos_abs).to_degrees()
449 }
450}
451
452#[must_use]
461pub fn arctan2d<T>(sin: UnitNegRange<T>, cos: UnitNegRange<T>) -> Degrees<T>
462where
463 T: Float + FloatConst,
464 f64: From<T>,
465{
466 let forty_five = T::from(FORTY_FIVE).expect("Could not convert constant to Float");
467 let ninety = forty_five + forty_five;
468 let one_eighty = ninety + ninety;
469
470 let sin_abs = sin.0.abs();
471 let cos_abs = cos.0.abs();
472
473 let degrees_90 = match sin_abs.partial_cmp(&cos_abs).expect("sin or cos is NaN") {
475 Ordering::Equal => forty_five,
476 Ordering::Less => arctan2_degrees(sin_abs, cos_abs),
477 Ordering::Greater => ninety - arctan2_degrees(cos_abs, sin_abs),
478 };
479
480 let degrees_180 = if cos.0 < T::zero() {
482 one_eighty - degrees_90
483 } else {
484 degrees_90
485 };
486
487 Degrees(degrees_180.copysign(sin.0))
489}
490
491#[must_use]
497pub fn csc<T: Float>(sin: UnitNegRange<T>) -> Option<T> {
498 let sq_epsilon = T::epsilon() * T::epsilon();
499 if sin.0.abs() >= sq_epsilon {
500 Some(T::one() / sin.0)
501 } else {
502 None
503 }
504}
505
506#[must_use]
512pub fn sec<T: Float>(cos: UnitNegRange<T>) -> Option<T> {
513 let sq_epsilon = T::epsilon() * T::epsilon();
514 if cos.0.abs() >= sq_epsilon {
515 Some(T::one() / cos.0)
516 } else {
517 None
518 }
519}
520
521#[must_use]
527pub fn tan<T: Float>(sin: UnitNegRange<T>, cos: UnitNegRange<T>) -> Option<T> {
528 sec(cos).map(|secant| sin.0 * secant)
529}
530
531#[must_use]
537pub fn cot<T: Float>(sin: UnitNegRange<T>, cos: UnitNegRange<T>) -> Option<T> {
538 csc(sin).map(|cosecant| cos.0 * cosecant)
539}
540
541#[must_use]
550pub fn sine_diff<T: Float>(
551 sin_a: UnitNegRange<T>,
552 cos_a: UnitNegRange<T>,
553 sin_b: UnitNegRange<T>,
554 cos_b: UnitNegRange<T>,
555) -> UnitNegRange<T> {
556 UnitNegRange::clamp(vector2d::perp_product(sin_a.0, cos_a.0, sin_b.0, cos_b.0))
557}
558
559#[must_use]
568pub fn sine_sum<T: Float>(
569 sin_a: UnitNegRange<T>,
570 cos_a: UnitNegRange<T>,
571 sin_b: UnitNegRange<T>,
572 cos_b: UnitNegRange<T>,
573) -> UnitNegRange<T> {
574 sine_diff(sin_a, cos_a, -sin_b, cos_b)
575}
576
577#[must_use]
586pub fn cosine_diff<T: Float>(
587 sin_a: UnitNegRange<T>,
588 cos_a: UnitNegRange<T>,
589 sin_b: UnitNegRange<T>,
590 cos_b: UnitNegRange<T>,
591) -> UnitNegRange<T> {
592 UnitNegRange::clamp(vector2d::dot_product(sin_a.0, cos_a.0, sin_b.0, cos_b.0))
593}
594
595#[must_use]
604pub fn cosine_sum<T: Float>(
605 sin_a: UnitNegRange<T>,
606 cos_a: UnitNegRange<T>,
607 sin_b: UnitNegRange<T>,
608 cos_b: UnitNegRange<T>,
609) -> UnitNegRange<T> {
610 cosine_diff(sin_a, cos_a, -sin_b, cos_b)
611}
612
613#[must_use]
617pub fn sq_sine_half<T: Float>(cos: UnitNegRange<T>) -> T {
618 let half = T::one() / (T::one() + T::one());
619 (T::one() - cos.0) * half
620}
621
622#[must_use]
626pub fn sq_cosine_half<T: Float>(cos: UnitNegRange<T>) -> T {
627 let half = T::one() / (T::one() + T::one());
628 (T::one() + cos.0) * half
629}
630
631#[must_use]
641pub fn calculate_adjacent_length<T: Float>(length: T, hypotenuse: T) -> T {
642 if length <= T::zero() {
643 hypotenuse
644 } else if length >= hypotenuse {
645 T::zero()
646 } else {
647 ((hypotenuse - length) * (hypotenuse + length)).sqrt()
648 }
649}
650
651#[must_use]
661pub fn spherical_adjacent_length<T: Float + FloatConst>(
662 a: Radians<T>,
663 c: Radians<T>,
664) -> Radians<T> {
665 if a <= Radians(T::zero()) {
666 c
667 } else if a >= c {
668 Radians(T::zero())
669 } else {
670 Radians((c.0.cos() / a.0.cos()).acos())
671 }
672}
673
674#[must_use]
682pub fn spherical_hypotenuse_length<T: Float + FloatConst>(
683 a: Radians<T>,
684 b: Radians<T>,
685) -> Radians<T> {
686 if a <= Radians(T::zero()) {
687 b
688 } else if b <= Radians(T::zero()) {
689 a
690 } else {
691 Radians((a.0.cos() * b.0.cos()).acos())
692 }
693}
694
695#[must_use]
704pub fn spherical_cosine_rule<T: Float + FloatConst>(
705 cos_angle: UnitNegRange<T>,
706 length: Radians<T>,
707) -> Radians<T> {
708 Radians((cos_angle.0 * length.0.tan()).atan())
709}
710
711#[cfg(test)]
712mod tests {
713 use super::*;
714 use crate::is_within_tolerance;
715
716 #[test]
717 fn unit_neg_range_traits() {
718 let zero = UnitNegRange::default();
719 assert_eq!(UnitNegRange(0.0), zero);
720 let one = UnitNegRange(1.0);
721
722 let one_clone = one.clone();
723 assert_eq!(one_clone, one);
724
725 let minus_one = -one;
726 assert_eq!(minus_one, UnitNegRange(-1.0));
727 assert!(minus_one < one);
728 assert_eq!(one, minus_one.abs());
729
730 print!("UnitNegRange: {:?}", one);
731 }
732
733 #[test]
734 fn unit_neg_range_clamp() {
735 assert_eq!(-1.0, UnitNegRange::clamp(-1.0 - f64::EPSILON).0);
737 assert_eq!(-1.0, UnitNegRange::clamp(-1.0).0);
739 assert_eq!(1.0, UnitNegRange::clamp(1.0).0);
741 assert_eq!(1.0, UnitNegRange::clamp(1.0 + f64::EPSILON).0);
743 }
744
745 #[test]
746 fn unit_neg_range_is_valid() {
747 assert!(!UnitNegRange(-1.0 - f64::EPSILON).is_valid());
748 assert!(UnitNegRange(-1.0).is_valid());
749 assert!(UnitNegRange(1.0).is_valid());
750 assert!(!UnitNegRange(1.0 + f64::EPSILON).is_valid());
751 }
752
753 #[test]
754 fn test_trig_functions() {
755 let cos_60 = UnitNegRange(0.5);
756 let sin_60 = swap_sin_cos(cos_60);
757 assert_eq!(COS_30_DEGREES, sin_60.0);
758
759 let sin_120 = sin_60;
760 let cos_120 = cosine_from_sine(sin_120, -1.0);
761
762 let zero = cosine_from_sine(UnitNegRange(1.0), -1.0);
763 assert_eq!(0.0, zero.0);
764 assert!(zero.0.is_sign_positive());
765
766 let recip_sq_epsilon = 1.0 / SQ_EPSILON;
767
768 let sin_msq_epsilon = UnitNegRange(-SQ_EPSILON);
769 assert_eq!(-recip_sq_epsilon, csc(sin_msq_epsilon).unwrap());
770 assert_eq!(-recip_sq_epsilon, sec(sin_msq_epsilon).unwrap());
771
772 let cos_msq_epsilon = swap_sin_cos(sin_msq_epsilon);
773 assert_eq!(1.0, sec(cos_msq_epsilon).unwrap());
774 assert_eq!(1.0, csc(cos_msq_epsilon).unwrap());
775
776 assert_eq!(-SQ_EPSILON, tan(sin_msq_epsilon, cos_msq_epsilon).unwrap());
777 assert_eq!(
778 -recip_sq_epsilon,
779 cot(sin_msq_epsilon, cos_msq_epsilon).unwrap()
780 );
781
782 assert!(is_within_tolerance(
783 sin_120.0,
784 sine_sum(sin_60, cos_60, sin_60, cos_60).0,
785 f64::EPSILON
786 ));
787 assert!(is_within_tolerance(
788 cos_120.0,
789 cosine_sum(sin_60, cos_60, sin_60, cos_60).0,
790 f64::EPSILON
791 ));
792
793 let result = sq_sine_half(cos_120);
794 assert_eq!(sin_60.0, result.sqrt());
795
796 let result = sq_cosine_half(cos_120);
797 assert!(is_within_tolerance(cos_60.0, result.sqrt(), f64::EPSILON));
798 }
799
800 #[test]
801 fn test_small_angle_conversion() {
802 assert_eq!(MAX_LINEAR_SIN_ANGLE, sine(Radians(MAX_LINEAR_SIN_ANGLE)).0);
804
805 let s = sine(Radians(MAX_COS_ANGLE_IS_ONE));
807 assert_eq!(
808 MAX_COS_ANGLE_IS_ONE.cos(),
809 cosine(Radians(MAX_COS_ANGLE_IS_ONE), s).0
810 );
811 assert_eq!(1.0, MAX_COS_ANGLE_IS_ONE.cos());
812
813 let angle = Radians(4.74e7 * f64::EPSILON);
815 assert_eq!(1.0, angle.0.cos());
816
817 let s = sine(angle);
820 let result = cosine(angle, s);
821 assert_eq!(1.0 - f64::EPSILON / 2.0, result.0);
822 assert!(result.0 < angle.0.cos());
823 }
824
825 #[test]
826 fn test_radians_conversion() {
827 assert!(
831 core::f64::consts::FRAC_PI_2
832 != core::f64::consts::FRAC_PI_3 + core::f64::consts::FRAC_PI_6
833 );
834
835 assert_eq!(
837 core::f64::consts::FRAC_PI_2 + f64::EPSILON,
838 core::f64::consts::FRAC_PI_3 + core::f64::consts::FRAC_PI_6
839 );
840
841 assert_eq!(
843 core::f64::consts::FRAC_PI_2,
844 2.0 * core::f64::consts::FRAC_PI_4
845 );
846 assert_eq!(core::f64::consts::PI, 2.0 * core::f64::consts::FRAC_PI_2);
848
849 assert_eq!(core::f64::consts::FRAC_PI_4, 45.0_f64.to_radians());
851
852 assert_eq!(
854 core::f64::consts::FRAC_1_SQRT_2 - 0.5 * f64::EPSILON,
855 core::f64::consts::FRAC_PI_4.sin()
856 );
857
858 let result = sincos(Radians(-core::f64::consts::FRAC_PI_6));
860 assert_eq!(-0.5, result.0.0);
861 assert_eq!(COS_30_DEGREES, result.1.0);
862 assert_eq!(-core::f64::consts::FRAC_PI_6, arctan2(result.0, result.1).0);
863
864 let result = sincos(Radians(core::f64::consts::FRAC_PI_3));
866 assert!(is_within_tolerance(
869 COS_30_DEGREES,
870 result.0.0,
871 f64::EPSILON
872 ));
873 assert!(is_within_tolerance(0.5, result.1.0, f64::EPSILON));
875 assert_eq!(core::f64::consts::FRAC_PI_3, arctan2(result.0, result.1).0);
876
877 let result = sincos(Radians(-core::f64::consts::PI));
879 assert_eq!(0.0, result.0.0);
880 assert_eq!(-1.0, result.1.0);
881 assert_eq!(core::f64::consts::PI, arctan2(result.0, result.1).0);
882
883 let result = sincos_diff(
885 Radians(core::f64::consts::PI),
886 Radians(core::f64::consts::FRAC_PI_4),
887 );
888 assert_eq!(core::f64::consts::FRAC_1_SQRT_2, result.0.0);
889 assert_eq!(-core::f64::consts::FRAC_1_SQRT_2, result.1.0);
890 assert_eq!(
891 core::f64::consts::PI - core::f64::consts::FRAC_PI_4,
892 arctan2(result.0, result.1).0
893 );
894
895 let result = sincos_diff(
897 Radians(3.0 * core::f64::consts::TAU),
898 Radians(core::f64::consts::FRAC_PI_3),
899 );
900 assert!(is_within_tolerance(
903 -COS_30_DEGREES,
904 result.0.0,
905 f64::EPSILON
906 ));
907 assert!(is_within_tolerance(0.5, result.1.0, f64::EPSILON));
909 assert_eq!(-core::f64::consts::FRAC_PI_3, arctan2(result.0, result.1).0);
910 }
911
912 #[test]
913 fn test_degrees_conversion() {
914 assert_eq!(90.0, 60.0 + 30.0);
916 assert_eq!(90.0, 2.0 * 45.0);
917 assert_eq!(180.0, 2.0 * 90.0);
918
919 let result = sincosd(Degrees(-30.0));
921 assert_eq!(-0.5, result.0.0);
922 assert_eq!(COS_30_DEGREES, result.1.0);
923 assert_eq!(-30.0, arctan2d(result.0, result.1).0);
924
925 let result = sincosd(Degrees(60.0));
927 assert_eq!(COS_30_DEGREES, result.0.0);
928 assert_eq!(0.5, result.1.0);
929 assert_eq!(60.0, arctan2d(result.0, result.1).0);
930
931 let result = sincosd(Degrees(-180.0));
933 assert_eq!(0.0, result.0.0);
934 assert_eq!(-1.0, result.1.0);
935 assert_eq!(180.0, arctan2d(result.0, result.1).0);
936
937 let result = sincosd_diff(Degrees(180.0), Degrees(45.0));
939 assert_eq!(core::f64::consts::FRAC_1_SQRT_2, result.0.0);
940 assert_eq!(-core::f64::consts::FRAC_1_SQRT_2, result.1.0);
941 assert_eq!(180.0 - 45.0, arctan2d(result.0, result.1).0);
942
943 let result = sincosd_diff(Degrees(1080.0), Degrees(60.0));
945 assert_eq!(-COS_30_DEGREES, result.0.0);
946 assert_eq!(0.5, result.1.0);
947 assert_eq!(-60.0, arctan2d(result.0, result.1).0);
948 }
949
950 #[test]
951 fn test_calculate_adjacent_length() {
952 assert_eq!(0.0, calculate_adjacent_length(5.0, 5.0));
954
955 assert_eq!(5.0, calculate_adjacent_length(0.0, 5.0));
957
958 assert_eq!(0.0, calculate_adjacent_length(6.0, 5.0));
960
961 assert_eq!(3.0, calculate_adjacent_length(4.0, 5.0));
963 }
964
965 #[test]
966 fn test_spherical_adjacent_length() {
967 assert_eq!(
969 Radians(0.0),
970 spherical_adjacent_length(Radians(5.0_f64.to_radians()), Radians(5.0_f64.to_radians()))
971 );
972
973 assert_eq!(
975 Radians(5.0_f64.to_radians()),
976 spherical_adjacent_length(Radians(0.0), Radians(5.0_f64.to_radians()))
977 );
978
979 assert_eq!(
981 Radians(0.0),
982 spherical_adjacent_length(Radians(6.0_f64.to_radians()), Radians(5.0_f64.to_radians()))
983 );
984
985 let result =
987 spherical_adjacent_length(Radians(4.0_f64.to_radians()), Radians(5.0_f64.to_radians()));
988 assert!(is_within_tolerance(3.0_f64.to_radians(), result.0, 1.0e-4));
989 }
990
991 #[test]
992 fn test_spherical_hypotenuse_length() {
993 let zero = Radians(0.0);
994 let three = Radians(3.0_f64.to_radians());
995 let four = Radians(4.0_f64.to_radians());
996
997 assert_eq!(three, spherical_hypotenuse_length(-four, three));
999 assert_eq!(four, spherical_hypotenuse_length(four, -three));
1001
1002 assert_eq!(three, spherical_hypotenuse_length(zero, three));
1004 assert_eq!(four, spherical_hypotenuse_length(four, zero));
1006 assert_eq!(zero, spherical_hypotenuse_length(zero, zero));
1008
1009 let result = Radians(0.087240926337265545);
1011 assert_eq!(result, spherical_hypotenuse_length(four, three));
1012 assert_eq!(result, spherical_hypotenuse_length(three, four));
1013 }
1014
1015 #[test]
1016 fn test_spherical_cosine_rule() {
1017 let result = spherical_cosine_rule(UnitNegRange(0.0), Radians(1.0));
1018 assert_eq!(0.0, result.0);
1019
1020 let result = spherical_cosine_rule(UnitNegRange(0.8660254037844386), Radians(0.5));
1021 assert_eq!(0.44190663576327144, result.0);
1022
1023 let result = spherical_cosine_rule(UnitNegRange(0.5), Radians(1.0));
1024 assert_eq!(0.66161993185017653, result.0);
1025
1026 let result = spherical_cosine_rule(UnitNegRange(1.0), Radians(1.0));
1027 assert_eq!(1.0, result.0);
1028 }
1029}