1#![forbid(unsafe_code)]
2
3use core::hint::cold_path;
6
7use crate::norm::norm_near_overflow;
8use crate::rounding::{compare_product_with_rounded, two_sum_error};
9use crate::{ArithmeticOperation, LaError};
10
11#[must_use]
46#[non_exhaustive]
47#[derive(Clone, Copy, Debug, PartialEq)]
48pub struct ScalarWithErrorBound {
49 estimate: f64,
50 absolute_error_bound: f64,
51 lower_bound: f64,
52 upper_bound: f64,
53}
54
55impl ScalarWithErrorBound {
56 #[inline]
58 #[must_use]
59 pub const fn estimate(self) -> f64 {
60 self.estimate
61 }
62
63 #[inline]
65 #[must_use]
66 pub const fn absolute_error_bound(self) -> f64 {
67 self.absolute_error_bound
68 }
69
70 #[inline]
72 #[must_use]
73 pub const fn lower_bound(self) -> f64 {
74 self.lower_bound
75 }
76
77 #[inline]
79 #[must_use]
80 pub const fn upper_bound(self) -> f64 {
81 self.upper_bound
82 }
83
84 const fn try_new(estimate: f64, absolute_error_bound: f64) -> Option<Self> {
90 if !estimate.is_finite() || !absolute_error_bound.is_finite() || absolute_error_bound < 0.0
91 {
92 return None;
93 }
94
95 if absolute_error_bound == 0.0 {
96 return Some(Self {
97 estimate,
98 absolute_error_bound: 0.0,
99 lower_bound: estimate,
100 upper_bound: estimate,
101 });
102 }
103
104 let lower_rounded = estimate - absolute_error_bound;
105 let upper_rounded = estimate + absolute_error_bound;
106 if !lower_rounded.is_finite() || !upper_rounded.is_finite() {
107 return None;
108 }
109
110 let lower_error = two_sum_error(estimate, -absolute_error_bound, lower_rounded);
111 let upper_error = two_sum_error(estimate, absolute_error_bound, upper_rounded);
112 if !lower_error.is_finite() || !upper_error.is_finite() {
113 return None;
114 }
115
116 let lower_bound = if lower_error < 0.0 {
117 lower_rounded.next_down()
118 } else {
119 lower_rounded
120 };
121 let upper_bound = if upper_error > 0.0 {
122 upper_rounded.next_up()
123 } else {
124 upper_rounded
125 };
126 if !lower_bound.is_finite() || !upper_bound.is_finite() {
127 return None;
128 }
129
130 Some(Self {
131 estimate,
132 absolute_error_bound,
133 lower_bound,
134 upper_bound,
135 })
136 }
137}
138
139#[derive(Clone, Copy, Debug, PartialEq)]
147struct CertifiedReduction {
148 estimate: f64,
149 magnitude_upper: f64,
150 proof_available: bool,
151}
152
153impl CertifiedReduction {
154 const ZERO: Self = Self {
155 estimate: 0.0,
156 magnitude_upper: 0.0,
157 proof_available: true,
158 };
159
160 const fn add_product(
166 mut self,
167 left: f64,
168 right: f64,
169 operation: ArithmeticOperation,
170 index: usize,
171 ) -> Result<Self, LaError> {
172 let prior = self.estimate;
173 let estimate = left.mul_add(right, prior);
174 if !estimate.is_finite() {
175 cold_path();
176 return Err(LaError::non_finite_computation_step(operation, index));
177 }
178
179 if self.proof_available {
180 self.proof_available = estimate.is_normal()
181 || (estimate == 0.0 && Self::fma_result_is_exact_zero(left, right, prior));
182 }
183 if self.proof_available {
184 match Self::add_product_magnitude_upper(self.magnitude_upper, left, right) {
185 Some(magnitude_upper) => self.magnitude_upper = magnitude_upper,
186 None => self.proof_available = false,
187 }
188 }
189 self.estimate = estimate;
190 Ok(self)
191 }
192
193 const fn fma_result_is_exact_zero(left: f64, right: f64, addend: f64) -> bool {
199 if left == 0.0 || right == 0.0 {
200 return addend == 0.0;
201 }
202
203 let rounded_product = left * right;
204 let rounded_bits = rounded_product.to_bits();
205 let negated_addend_bits = (-addend).to_bits();
206 let same_rounded_value = rounded_bits == negated_addend_bits
207 || (rounded_bits << 1 == 0 && negated_addend_bits << 1 == 0);
208 rounded_product.is_finite()
209 && same_rounded_value
210 && compare_product_with_rounded(left, right, rounded_product) == 0
211 }
212
213 const fn add_product_magnitude_upper(
219 magnitude_upper: f64,
220 left: f64,
221 right: f64,
222 ) -> Option<f64> {
223 if left == 0.0 || right == 0.0 {
224 return Some(magnitude_upper);
225 }
226
227 let left_magnitude = left.abs();
228 let right_magnitude = right.abs();
229 let rounded_product = left_magnitude * right_magnitude;
230 if !rounded_product.is_normal() {
231 return None;
232 }
233
234 let product_upper =
235 if compare_product_with_rounded(left_magnitude, right_magnitude, rounded_product) > 0 {
236 rounded_product.next_up()
237 } else {
238 rounded_product
239 };
240 if !product_upper.is_finite() {
241 return None;
242 }
243
244 if magnitude_upper == 0.0 {
245 return Some(product_upper);
246 }
247 let rounded_sum = magnitude_upper + product_upper;
248 if !rounded_sum.is_finite() {
249 return None;
250 }
251 let sum_upper = rounded_sum.next_up();
252 if sum_upper.is_finite() {
253 Some(sum_upper)
254 } else {
255 None
256 }
257 }
258
259 #[expect(
266 clippy::cast_precision_loss,
267 reason = "a usable gamma requires a term count below 2^53, where the cast is exact"
268 )]
269 const fn finish(self, term_count: Option<usize>) -> Option<ScalarWithErrorBound> {
270 if !self.proof_available {
271 return None;
272 }
273 if self.magnitude_upper == 0.0 {
274 return ScalarWithErrorBound::try_new(self.estimate, 0.0);
275 }
276
277 let Some(term_count) = term_count else {
278 return None;
279 };
280 let scaled_roundoff = (term_count as f64) * (f64::EPSILON / 2.0);
281 if !scaled_roundoff.is_finite() || scaled_roundoff >= 1.0 {
282 return None;
283 }
284
285 let gamma = scaled_roundoff / (1.0 - scaled_roundoff);
289 let gamma_upper = gamma.next_up();
290 if !gamma_upper.is_finite() {
291 return None;
292 }
293 let rounded_bound = gamma_upper * self.magnitude_upper;
294 if !rounded_bound.is_finite() {
295 return None;
296 }
297 let absolute_error_bound = if rounded_bound == 0.0 {
298 0.0
299 } else {
300 rounded_bound.next_up()
301 };
302 ScalarWithErrorBound::try_new(self.estimate, absolute_error_bound)
303 }
304}
305
306#[must_use]
325#[derive(Clone, Copy, Debug, PartialEq)]
326pub struct Vector<const D: usize> {
327 data: [f64; D],
328}
329
330impl<const D: usize> Vector<D> {
331 #[cfg(test)]
333 #[inline]
334 pub(crate) const fn new(data: [f64; D]) -> Self {
335 match Self::try_new(data) {
336 Ok(vector) => vector,
337 Err(_) => panic!("Vector::new requires finite entries"),
338 }
339 }
340
341 #[inline]
361 pub const fn try_new(data: [f64; D]) -> Result<Self, LaError> {
362 if let Some(index) = Self::first_non_finite_entry(&data) {
363 Err(LaError::non_finite_input_vector(index))
364 } else {
365 Ok(Self { data })
366 }
367 }
368
369 #[inline]
375 pub(crate) const fn from_computation(
376 data: [f64; D],
377 operation: ArithmeticOperation,
378 ) -> Result<Self, LaError> {
379 if let Some(index) = Self::first_non_finite_entry(&data) {
380 Err(LaError::non_finite_computation_step(operation, index))
381 } else {
382 Ok(Self { data })
383 }
384 }
385
386 const fn first_non_finite_entry(data: &[f64; D]) -> Option<usize> {
391 let mut i = 0;
392 while i < D {
393 if !data[i].is_finite() {
394 return Some(i);
395 }
396 i += 1;
397 }
398 None
399 }
400
401 #[inline]
411 pub const fn zero() -> Self {
412 Self { data: [0.0; D] }
413 }
414
415 #[inline]
428 #[must_use]
429 pub const fn as_array(&self) -> &[f64; D] {
430 &self.data
431 }
432
433 #[inline]
447 #[must_use]
448 pub const fn into_array(self) -> [f64; D] {
449 self.data
450 }
451
452 #[inline]
476 pub const fn dot(&self, other: &Self) -> Result<f64, LaError> {
477 self.dot_with_operation(other, ArithmeticOperation::VectorDotProduct)
478 }
479
480 #[inline]
530 pub const fn dot_with_errbound(
531 &self,
532 other: &Self,
533 ) -> Result<Option<ScalarWithErrorBound>, LaError> {
534 let left = self.as_array();
535 let right = other.as_array();
536 let mut reduction = CertifiedReduction::ZERO;
537 let mut i = 0;
538 while i < D {
539 reduction = match reduction.add_product(
540 left[i],
541 right[i],
542 ArithmeticOperation::VectorDotProduct,
543 i,
544 ) {
545 Ok(reduction) => reduction,
546 Err(error) => return Err(error),
547 };
548 i += 1;
549 }
550 Ok(reduction.finish(Some(D)))
551 }
552
553 #[inline]
608 pub const fn dot_difference_with_errbound(
609 &self,
610 left: &Self,
611 right: &Self,
612 ) -> Result<Option<ScalarWithErrorBound>, LaError> {
613 let axis = self.as_array();
614 let left = left.as_array();
615 let right = right.as_array();
616 let mut reduction = CertifiedReduction::ZERO;
617 let mut i = 0;
618 while i < D {
619 reduction = match reduction.add_product(
620 axis[i],
621 left[i],
622 ArithmeticOperation::VectorDotDifference,
623 i,
624 ) {
625 Ok(reduction) => reduction,
626 Err(error) => return Err(error),
627 };
628 reduction = match reduction.add_product(
629 -axis[i],
630 right[i],
631 ArithmeticOperation::VectorDotDifference,
632 i,
633 ) {
634 Ok(reduction) => reduction,
635 Err(error) => return Err(error),
636 };
637 i += 1;
638 }
639 Ok(reduction.finish(D.checked_mul(2)))
640 }
641
642 const fn dot_with_operation(
644 &self,
645 other: &Self,
646 operation: ArithmeticOperation,
647 ) -> Result<f64, LaError> {
648 let lhs = self.as_array();
649 let rhs = other.as_array();
650 let mut acc = 0.0;
651 let mut i = 0;
652 while i < D {
653 acc = lhs[i].mul_add(rhs[i], acc);
654 i += 1;
655 }
656 if acc.is_finite() {
657 Ok(acc)
658 } else {
659 cold_path();
660 Err(Self::dot_non_finite_error(lhs, rhs, operation))
661 }
662 }
663
664 #[cold]
672 const fn dot_non_finite_error(
673 lhs: &[f64; D],
674 rhs: &[f64; D],
675 operation: ArithmeticOperation,
676 ) -> LaError {
677 let mut acc = 0.0;
678 let mut i = 0;
679 let last = D.saturating_sub(1);
680 while i < last {
681 acc = lhs[i].mul_add(rhs[i], acc);
682 if !acc.is_finite() {
683 return LaError::non_finite_computation_step(operation, i);
684 }
685 i += 1;
686 }
687
688 LaError::non_finite_computation_step(operation, last)
689 }
690
691 #[inline]
715 pub const fn norm_squared(&self) -> Result<f64, LaError> {
716 self.dot_with_operation(self, ArithmeticOperation::VectorSquaredNorm)
717 }
718
719 #[inline]
765 pub fn norm(&self) -> Result<f64, LaError> {
766 let mut entries = self.as_array().iter();
767 let mut scale = entries.next().copied().unwrap_or(0.0).abs();
770 let mut scaled_sum = 1.0;
771
772 for &entry in entries {
773 let magnitude = entry.abs();
774 if magnitude == 0.0 {
775 continue;
776 }
777
778 if scale < magnitude {
779 let ratio = scale / magnitude;
780 scaled_sum = (scaled_sum * ratio).mul_add(ratio, 1.0);
781 scale = magnitude;
782 } else {
783 let ratio = magnitude / scale;
784 scaled_sum = ratio.mul_add(ratio, scaled_sum);
785 }
786 }
787
788 let dimension_bits = usize::BITS - D.leading_zeros();
794 let safe_scale = f64::from_bits(u64::from(2046 - dimension_bits) << 52);
795 if scale > safe_scale {
796 return norm_near_overflow(self.as_array(), scale);
797 }
798 Ok(scale * scaled_sum.sqrt())
799 }
800}
801
802impl<const D: usize> Default for Vector<D> {
803 #[inline]
804 fn default() -> Self {
805 Self::zero()
806 }
807}
808
809#[cfg(test)]
810mod tests {
811 use core::hint::black_box;
812
813 use approx::assert_abs_diff_eq;
814 use pastey::paste;
815
816 use super::*;
817
818 fn assert_certified_proof_loss_survives_normal_terms<const D: usize>() {
819 let mut left_data = [0.0; D];
820 left_data[0] = f64::MIN_POSITIVE;
821 left_data[D - 1] = 1.0;
822 let mut right_data = [1.0; D];
823 right_data[0] = 0.5;
824 let left = Vector::new(left_data);
825 let right = Vector::new(right_data);
826
827 assert_abs_diff_eq!(left.dot(&right).unwrap(), 1.0, epsilon = 0.0);
830 assert_eq!(left.dot_with_errbound(&right), Ok(None));
831 assert_eq!(
832 left.dot_difference_with_errbound(&right, &Vector::zero()),
833 Ok(None)
834 );
835 }
836
837 fn assert_certified_proof_loss_preserves_later_overflow<const D: usize>() {
838 let mut axis_data = [0.0; D];
839 axis_data[0] = f64::MIN_POSITIVE;
840 axis_data[D - 1] = f64::MAX;
841 let axis = Vector::new(axis_data);
842 let mut left_data = [0.0; D];
843 left_data[0] = 0.5;
844 left_data[D - 1] = 2.0;
845
846 assert_eq!(
847 axis.dot_with_errbound(&Vector::new(left_data)),
848 Err(LaError::non_finite_computation_step(
849 ArithmeticOperation::VectorDotProduct,
850 D - 1,
851 ))
852 );
853 let difference_error = Err(LaError::non_finite_computation_step(
854 ArithmeticOperation::VectorDotDifference,
855 D - 1,
856 ));
857 assert_eq!(
858 axis.dot_difference_with_errbound(&Vector::new(left_data), &Vector::zero()),
859 difference_error
860 );
861
862 left_data[D - 1] = 0.0;
865 let mut right_data = [0.0; D];
866 right_data[D - 1] = -2.0;
867 assert_eq!(
868 axis.dot_difference_with_errbound(&Vector::new(left_data), &Vector::new(right_data)),
869 difference_error
870 );
871 }
872
873 macro_rules! gen_certified_reduction_sequence_tests {
874 ($d:literal) => {
875 paste! {
876 #[test]
877 fn [<certified_proof_loss_survives_normal_terms_ $d d>]() {
878 assert_certified_proof_loss_survives_normal_terms::<$d>();
879 }
880
881 #[test]
882 fn [<certified_proof_loss_preserves_later_overflow_ $d d>]() {
883 assert_certified_proof_loss_preserves_later_overflow::<$d>();
884 }
885 }
886 };
887 }
888
889 gen_certified_reduction_sequence_tests!(2);
890 gen_certified_reduction_sequence_tests!(3);
891 gen_certified_reduction_sequence_tests!(4);
892 gen_certified_reduction_sequence_tests!(5);
893
894 macro_rules! gen_vector_tests {
895 ($d:literal) => {
896 paste! {
897 #[test]
898 fn [<vector_new_as_array_into_array_ $d d>]() {
899 let arr = {
900 let mut arr = [0.0f64; $d];
901 let values = [1.0f64, 2.0, 3.0, 4.0, 5.0];
902 for (dst, src) in arr.iter_mut().zip(values.iter()) {
903 *dst = *src;
904 }
905 arr
906 };
907
908 let v = Vector::<$d>::new(arr);
909
910 for i in 0..$d {
911 assert_abs_diff_eq!(v.as_array()[i], arr[i], epsilon = 0.0);
912 }
913
914 let out = v.into_array();
915 for i in 0..$d {
916 assert_abs_diff_eq!(out[i], arr[i], epsilon = 0.0);
917 }
918 }
919
920 #[test]
921 fn [<vector_zero_as_array_into_array_default_ $d d>]() {
922 let z = Vector::<$d>::zero();
923 for &x in z.as_array() {
924 assert_abs_diff_eq!(x, 0.0, epsilon = 0.0);
925 }
926 for x in z.into_array() {
927 assert_abs_diff_eq!(x, 0.0, epsilon = 0.0);
928 }
929
930 let d = Vector::<$d>::default();
931 for x in d.into_array() {
932 assert_abs_diff_eq!(x, 0.0, epsilon = 0.0);
933 }
934 }
935
936 #[test]
937 fn [<vector_dot_and_norm_squared_ $d d>]() {
938 let a_arr = {
942 let mut arr = [0.0f64; $d];
943 let values = [1.0f64, 2.0, 3.0, 4.0, 5.0];
944 for (dst, src) in arr.iter_mut().zip(values.iter()) {
945 *dst = black_box(*src);
946 }
947 arr
948 };
949 let b_arr = {
950 let mut arr = [0.0f64; $d];
951 let values = [-2.0f64, 0.5, 4.0, -1.0, 2.0];
952 for (dst, src) in arr.iter_mut().zip(values.iter()) {
953 *dst = black_box(*src);
954 }
955 arr
956 };
957
958 let expected_dot = {
959 let mut acc = 0.0;
960 let mut i = 0;
961 while i < $d {
962 acc = a_arr[i].mul_add(b_arr[i], acc);
963 i += 1;
964 }
965 acc
966 };
967 let expected_norm_squared = {
968 let mut acc = 0.0;
969 let mut i = 0;
970 while i < $d {
971 acc = a_arr[i].mul_add(a_arr[i], acc);
972 i += 1;
973 }
974 acc
975 };
976
977 let a = Vector::<$d>::new(black_box(a_arr));
978 let b = Vector::<$d>::new(black_box(b_arr));
979
980 let dot_fn: fn(&Vector<$d>, &Vector<$d>) -> Result<f64, LaError> =
983 black_box(Vector::<$d>::dot);
984 let norm_squared_fn: fn(&Vector<$d>) -> Result<f64, LaError> =
985 black_box(Vector::<$d>::norm_squared);
986
987 assert_abs_diff_eq!(
988 dot_fn(black_box(&a), black_box(&b)).unwrap(),
989 expected_dot,
990 epsilon = 1e-14
991 );
992 assert_abs_diff_eq!(
993 norm_squared_fn(black_box(&a)).unwrap(),
994 expected_norm_squared,
995 epsilon = 1e-14
996 );
997 }
998
999 #[test]
1000 fn [<vector_certified_dot_and_difference_ $d d>]() {
1001 let mut left_data = [0.0; $d];
1002 let mut right_data = [0.0; $d];
1003 let left_values = [1.0, 2.0, 3.0, 4.0, 5.0];
1004 let right_values = [2.0, 3.0, 4.0, 5.0, 6.0];
1005 for (destination, source) in left_data.iter_mut().zip(left_values) {
1006 *destination = source;
1007 }
1008 for (destination, source) in right_data.iter_mut().zip(right_values) {
1009 *destination = source;
1010 }
1011 let left = Vector::<$d>::new(left_data);
1012 let right = Vector::<$d>::new(right_data);
1013 let zero = Vector::<$d>::zero();
1014
1015 let dot = left.dot(&right).unwrap();
1016 let dot_bound = left.dot_with_errbound(&right).unwrap().unwrap();
1017 assert_abs_diff_eq!(dot_bound.estimate(), dot, epsilon = 0.0);
1018 assert!(dot_bound.absolute_error_bound() >= 0.0);
1019 assert!(dot_bound.lower_bound() <= dot);
1020 assert!(dot <= dot_bound.upper_bound());
1021
1022 let difference_bound = left
1023 .dot_difference_with_errbound(&right, &zero)
1024 .unwrap()
1025 .unwrap();
1026 assert_abs_diff_eq!(difference_bound.estimate(), dot, epsilon = 0.0);
1027 assert!(difference_bound.lower_bound() <= dot);
1028 assert!(dot <= difference_bound.upper_bound());
1029 }
1030
1031 #[test]
1032 fn [<vector_try_new_rejects_non_finite_ $d d>]() {
1033 for value in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
1034 let mut data = [1.0f64; $d];
1035 data[$d - 1] = value;
1036 assert_eq!(
1037 Vector::<$d>::try_new(data),
1038 Err(LaError::non_finite_input_vector($d - 1))
1039 );
1040 }
1041
1042 let mut data = [1.0f64; $d];
1043 data[0] = f64::INFINITY;
1044 data[$d - 1] = f64::NAN;
1045 assert_eq!(
1046 Vector::<$d>::try_new(data),
1047 Err(LaError::non_finite_input_vector(0))
1048 );
1049 }
1050
1051 #[test]
1052 fn [<vector_from_computation_preserves_failure_provenance_ $d d>]() {
1053 let mut data = [1.0f64; $d];
1054 data[$d - 1] = f64::INFINITY;
1055
1056 assert_eq!(
1057 Vector::<$d>::from_computation(
1058 data,
1059 ArithmeticOperation::LuSolve,
1060 ),
1061 Err(LaError::non_finite_computation_step(
1062 ArithmeticOperation::LuSolve,
1063 $d - 1,
1064 ))
1065 );
1066 }
1067
1068 #[test]
1069 fn [<vector_dot_and_norm_squared_reject_overflow_ $d d>]() {
1070 let mut a_arr = [1.0f64; $d];
1071 a_arr[0] = f64::MAX;
1072 let a = Vector::<$d>::new(a_arr);
1073
1074 let mut b_arr = [1.0f64; $d];
1075 b_arr[0] = 2.0;
1076 let b = Vector::<$d>::new(b_arr);
1077
1078 assert_eq!(
1079 a.dot(&b),
1080 Err(LaError::non_finite_computation_step(
1081 ArithmeticOperation::VectorDotProduct,
1082 0,
1083 ))
1084 );
1085 assert_eq!(
1086 a.dot_with_errbound(&b),
1087 Err(LaError::non_finite_computation_step(
1088 ArithmeticOperation::VectorDotProduct,
1089 0,
1090 ))
1091 );
1092 assert_eq!(
1093 a.dot_difference_with_errbound(&b, &Vector::zero()),
1094 Err(LaError::non_finite_computation_step(
1095 ArithmeticOperation::VectorDotDifference,
1096 0,
1097 ))
1098 );
1099 assert_eq!(
1100 a.norm_squared(),
1101 Err(LaError::non_finite_computation_step(
1102 ArithmeticOperation::VectorSquaredNorm,
1103 0,
1104 ))
1105 );
1106 }
1107
1108 }
1109 };
1110 }
1111
1112 gen_vector_tests!(1);
1114 gen_vector_tests!(2);
1115 gen_vector_tests!(3);
1116 gen_vector_tests!(4);
1117 gen_vector_tests!(5);
1118 gen_vector_tests!(6);
1119 gen_vector_tests!(7);
1120 gen_vector_tests!(8);
1121
1122 fn known_norm_input<const D: usize>() -> ([f64; D], f64) {
1123 let mut data = [0.0; D];
1124 if D == 1 {
1125 data[0] = -5.0;
1126 } else if D >= 2 {
1127 data[0] = -3.0;
1128 data[1] = 4.0;
1129 }
1130 (data, if D == 0 { 0.0 } else { 5.0 })
1131 }
1132
1133 macro_rules! gen_vector_norm_known_answer_tests {
1134 ($d:literal) => {
1135 paste! {
1136 #[test]
1137 fn [<vector_norm_known_answer_ $d d>]() {
1138 let (data, expected) = known_norm_input::<$d>();
1139 let vector = Vector::<$d>::new(data);
1140
1141 assert_eq!(vector.norm(), Ok(expected));
1142 }
1143 }
1144 };
1145 }
1146
1147 gen_vector_norm_known_answer_tests!(0);
1148 gen_vector_norm_known_answer_tests!(1);
1149 gen_vector_norm_known_answer_tests!(2);
1150 gen_vector_norm_known_answer_tests!(3);
1151 gen_vector_norm_known_answer_tests!(4);
1152 gen_vector_norm_known_answer_tests!(5);
1153 gen_vector_norm_known_answer_tests!(6);
1154 gen_vector_norm_known_answer_tests!(7);
1155 gen_vector_norm_known_answer_tests!(8);
1156
1157 macro_rules! gen_vector_replay_tests {
1158 ($d:literal) => {
1159 paste! {
1160 #[test]
1161 fn [<vector_dot_and_norm_squared_report_last_overflowing_step_ $d d>]() {
1162 let mut dot_lhs = [1.0f64; $d];
1163 dot_lhs[$d - 1] = f64::MAX;
1164 let mut dot_rhs = [1.0f64; $d];
1165 dot_rhs[$d - 1] = 2.0;
1166 let dot_lhs = Vector::<$d>::new(dot_lhs);
1167 let dot_rhs = Vector::<$d>::new(dot_rhs);
1168
1169 assert_eq!(
1170 dot_lhs.dot(&dot_rhs),
1171 Err(LaError::non_finite_computation_step(
1172 ArithmeticOperation::VectorDotProduct,
1173 $d - 1,
1174 ))
1175 );
1176
1177 let mut norm_data = [1.0f64; $d];
1178 norm_data[$d - 1] = f64::MAX;
1179 let vector = Vector::<$d>::new(norm_data);
1180
1181 assert_eq!(
1182 vector.norm_squared(),
1183 Err(LaError::non_finite_computation_step(
1184 ArithmeticOperation::VectorSquaredNorm,
1185 $d - 1,
1186 ))
1187 );
1188 }
1189 }
1190 };
1191 }
1192
1193 gen_vector_replay_tests!(2);
1194 gen_vector_replay_tests!(3);
1195 gen_vector_replay_tests!(4);
1196 gen_vector_replay_tests!(5);
1197
1198 macro_rules! gen_vector_const_eval_tests {
1199 ($d:literal, $dot:literal, $norm_squared:literal) => {
1200 paste! {
1201 #[test]
1202 fn [<vector_dot_and_norm_squared_const_eval_ $d d>]() {
1203 const DOT: Result<f64, LaError> = Vector::<$d>::new([1.0; $d])
1204 .dot(&Vector::<$d>::new([2.0; $d]));
1205 const NORM_SQUARED: Result<f64, LaError> =
1206 Vector::<$d>::new([1.0; $d]).norm_squared();
1207
1208 assert_eq!(DOT, Ok($dot));
1209 assert_eq!(NORM_SQUARED, Ok($norm_squared));
1210 }
1211 }
1212 };
1213 }
1214
1215 gen_vector_const_eval_tests!(2, 4.0, 2.0);
1216 gen_vector_const_eval_tests!(3, 6.0, 3.0);
1217 gen_vector_const_eval_tests!(4, 8.0, 4.0);
1218 gen_vector_const_eval_tests!(5, 10.0, 5.0);
1219
1220 #[test]
1221 fn vector_dot_and_norm_squared_overflow_const_eval() {
1222 const DOT: Result<f64, LaError> =
1223 Vector::<2>::new([f64::MAX; 2]).dot(&Vector::<2>::new([1.0; 2]));
1224 const NORM_SQUARED: Result<f64, LaError> = Vector::<2>::new([f64::MAX; 2]).norm_squared();
1225
1226 assert_eq!(
1227 DOT,
1228 Err(LaError::non_finite_computation_step(
1229 ArithmeticOperation::VectorDotProduct,
1230 1,
1231 ))
1232 );
1233 assert_eq!(
1234 NORM_SQUARED,
1235 Err(LaError::non_finite_computation_step(
1236 ArithmeticOperation::VectorSquaredNorm,
1237 0,
1238 ))
1239 );
1240 }
1241
1242 #[test]
1243 fn vector_dot_and_norm_squared_preserve_fma_and_left_to_right_order() {
1244 let dot_large = 9_007_199_254_740_992.0;
1245 let dot_lhs = Vector::<4>::new([dot_large, 1.0, 1.0, 1.0]);
1246 let dot_rhs = Vector::<4>::new([1.0; 4]);
1247 assert_eq!(dot_lhs.dot(&dot_rhs), Ok(dot_large));
1248
1249 let fused_lhs = Vector::<2>::new([f64::MAX, f64::MAX]);
1250 let fused_rhs = Vector::<2>::new([-1.0, 2.0]);
1251 assert_eq!(fused_lhs.dot(&fused_rhs), Ok(f64::MAX));
1252
1253 let norm_large = 134_217_728.0;
1254 let vector = Vector::<4>::new([norm_large, 1.0, 1.0, 1.0]);
1255 assert_eq!(vector.norm_squared(), Ok(norm_large * norm_large));
1256 }
1257
1258 #[test]
1259 fn vector_norm_preserves_zero_sign_and_subnormal_magnitudes() {
1260 let signed_zero = Vector::<4>::new([-0.0, 0.0, -0.0, 0.0]);
1261 assert_eq!(signed_zero.norm().unwrap().to_bits(), 0.0f64.to_bits());
1262
1263 let least_subnormal = f64::from_bits(1);
1264 let subnormal = Vector::<2>::new([3.0 * least_subnormal, -4.0 * least_subnormal]);
1265 assert_eq!(
1266 subnormal.norm().unwrap().to_bits(),
1267 (5.0 * least_subnormal).to_bits()
1268 );
1269 }
1270
1271 #[test]
1272 fn vector_norm_handles_mixed_and_overflowing_magnitudes() {
1273 let large = Vector::<2>::new([1.0e200, -1.0e200]);
1274 let expected = 2.0f64.sqrt() * 1.0e200;
1275 assert_abs_diff_eq!(large.norm().unwrap(), expected, epsilon = 2.0e184);
1276 assert_eq!(
1277 large.norm_squared(),
1278 Err(LaError::non_finite_computation_step(
1279 ArithmeticOperation::VectorSquaredNorm,
1280 0,
1281 )),
1282 );
1283
1284 let mixed = Vector::<4>::new([1.0e200, 1.0e-200, -f64::from_bits(1), 0.0]);
1285 assert_eq!(mixed.norm(), Ok(1.0e200));
1286
1287 let unrepresentable = Vector::<2>::new([f64::MAX, f64::MAX]);
1288 assert_eq!(
1289 unrepresentable.norm(),
1290 Err(LaError::non_finite_computation_scalar(
1291 ArithmeticOperation::VectorNorm,
1292 ))
1293 );
1294 }
1295
1296 #[test]
1297 fn vector_norm_accepts_largest_finite_norm() {
1298 let maximum = Vector::<2>::new([f64::MAX, 0.0]);
1299
1300 assert_eq!(maximum.norm(), Ok(f64::MAX));
1301 }
1302
1303 #[test]
1304 fn certified_dot_preserves_fma_estimate_and_withholds_range_exhausted_bound() {
1305 let left = Vector::<2>::new([f64::MAX, f64::MAX]);
1306 let right = Vector::<2>::new([-1.0, 2.0]);
1307
1308 assert_eq!(left.dot(&right), Ok(f64::MAX));
1309 assert_eq!(left.dot_with_errbound(&right), Ok(None));
1310 }
1311
1312 #[test]
1313 fn certified_dot_withholds_bound_when_finite_endpoints_cannot_be_published() {
1314 let maximum = Vector::<1>::new([f64::MAX]);
1315 let one = Vector::<1>::new([1.0]);
1316
1317 assert_eq!(maximum.dot(&one), Ok(f64::MAX));
1318 assert_eq!(maximum.dot_with_errbound(&one), Ok(None));
1319 }
1320
1321 #[test]
1322 fn certified_dot_withholds_bound_when_magnitude_sum_exhausts_range() {
1323 let maximum = Vector::<2>::new([f64::MAX, f64::MAX]);
1324
1325 for factor in [0.5, 0.75] {
1326 let cancelling = Vector::<2>::new([factor, -factor]);
1327 let estimate = maximum
1328 .dot(&cancelling)
1329 .expect("the cancelling FMA estimate must remain finite");
1330 assert!(estimate.is_finite());
1331 assert_eq!(maximum.dot_with_errbound(&cancelling), Ok(None));
1332 }
1333 }
1334
1335 #[test]
1336 fn certified_bounds_distinguish_conclusive_and_inconclusive_results() {
1337 let conclusive = Vector::<2>::new([1.0, 2.0])
1338 .dot_with_errbound(&Vector::new([3.0, 4.0]))
1339 .unwrap()
1340 .unwrap();
1341 assert_abs_diff_eq!(conclusive.estimate(), 11.0, epsilon = 0.0);
1342 assert!(conclusive.lower_bound() > 1.0);
1343
1344 let inconclusive = Vector::<2>::new([1.0, 1.0])
1345 .dot_with_errbound(&Vector::new([1.0, -1.0]))
1346 .unwrap()
1347 .unwrap();
1348 assert_abs_diff_eq!(inconclusive.estimate(), 0.0, epsilon = 0.0);
1349 assert!(inconclusive.absolute_error_bound() > 0.0);
1350 assert!(inconclusive.lower_bound() < 0.0);
1351 assert!(inconclusive.upper_bound() > 0.0);
1352 }
1353
1354 #[test]
1355 fn certified_zero_and_signed_zero_have_an_exact_zero_bound() {
1356 let left = Vector::<3>::new([-0.0, 0.0, -0.0]);
1357 let right = Vector::<3>::new([f64::MAX, -1.0, f64::MIN_POSITIVE]);
1358 let bounded = left.dot_with_errbound(&right).unwrap().unwrap();
1359
1360 assert_abs_diff_eq!(bounded.estimate(), 0.0, epsilon = 0.0);
1361 assert_abs_diff_eq!(bounded.absolute_error_bound(), 0.0, epsilon = 0.0);
1362 assert_abs_diff_eq!(bounded.lower_bound(), 0.0, epsilon = 0.0);
1363 assert_abs_diff_eq!(bounded.upper_bound(), 0.0, epsilon = 0.0);
1364 }
1365
1366 #[test]
1367 fn certified_reductions_withhold_bounds_for_subnormal_products() {
1368 let tiny = Vector::<1>::new([f64::MIN_POSITIVE]);
1369 let half = Vector::<1>::new([0.5]);
1370 assert_eq!(tiny.dot_with_errbound(&half), Ok(None));
1371
1372 let min_subnormal = Vector::<1>::new([f64::from_bits(1)]);
1373 assert_eq!(
1374 min_subnormal.dot_with_errbound(&Vector::new([1.0])),
1375 Ok(None)
1376 );
1377 assert_eq!(
1378 min_subnormal.dot_with_errbound(&Vector::new([0.5])),
1379 Ok(None)
1380 );
1381 assert_eq!(
1382 tiny.dot_difference_with_errbound(&half, &Vector::zero()),
1383 Ok(None)
1384 );
1385 }
1386
1387 #[test]
1388 fn certified_reduction_detects_fma_cancellation_below_subnormal_range() {
1389 let near_sqrt_min = f64::from_bits((512_u64 << 52) | 1);
1390 let rounded_product = near_sqrt_min * near_sqrt_min;
1391 assert!(rounded_product.is_normal());
1392 assert_abs_diff_eq!(
1393 near_sqrt_min.mul_add(near_sqrt_min, -rounded_product),
1394 0.0,
1395 epsilon = 0.0
1396 );
1397
1398 let left = Vector::<2>::new([-rounded_product, near_sqrt_min]);
1399 let right = Vector::<2>::new([1.0, near_sqrt_min]);
1400 assert_eq!(left.dot(&right), Ok(0.0));
1401 assert_eq!(left.dot_with_errbound(&right), Ok(None));
1402 }
1403
1404 #[test]
1405 fn certified_dot_handles_mixed_normal_magnitudes() {
1406 let left = Vector::<2>::new([1.0e100, 1.0e-100]);
1407 let right = Vector::<2>::new([1.0e-100, 1.0e100]);
1408 let bounded = left.dot_with_errbound(&right).unwrap().unwrap();
1409
1410 assert_abs_diff_eq!(bounded.estimate(), 2.0, epsilon = 0.0);
1411 assert!(bounded.lower_bound() <= 2.0);
1412 assert!(bounded.upper_bound() >= 2.0);
1413 }
1414
1415 #[test]
1416 fn certified_dot_difference_does_not_round_coordinates_first() {
1417 let scale = 18_014_398_509_481_984.0;
1418 let axis = Vector::<1>::new([scale]);
1419 let left = Vector::<1>::new([1.0]);
1420 let right = Vector::<1>::new([1.0 / scale]);
1421 assert_abs_diff_eq!(left.as_array()[0] - right.as_array()[0], 1.0, epsilon = 0.0);
1422
1423 let bounded = axis
1424 .dot_difference_with_errbound(&left, &right)
1425 .unwrap()
1426 .unwrap();
1427 assert_abs_diff_eq!(bounded.estimate(), scale, epsilon = 0.0);
1428 assert!(bounded.lower_bound() < scale);
1429 assert!(bounded.upper_bound() >= scale);
1430 }
1431
1432 #[test]
1433 fn certified_reductions_are_const_evaluable() {
1434 const DOT: Result<Option<ScalarWithErrorBound>, LaError> =
1435 Vector::<2>::new([1.0, 2.0]).dot_with_errbound(&Vector::<2>::new([3.0, 4.0]));
1436 const DIFFERENCE: Result<Option<ScalarWithErrorBound>, LaError> =
1437 Vector::<2>::new([2.0, -1.0]).dot_difference_with_errbound(
1438 &Vector::<2>::new([4.0, 1.0]),
1439 &Vector::<2>::new([1.0, 3.0]),
1440 );
1441
1442 assert_abs_diff_eq!(DOT.unwrap().unwrap().estimate(), 11.0, epsilon = 0.0);
1443 assert_abs_diff_eq!(DIFFERENCE.unwrap().unwrap().estimate(), 8.0, epsilon = 0.0);
1444 }
1445
1446 #[test]
1447 fn certified_dot_difference_reports_second_fma_overflow() {
1448 let axis = Vector::<2>::new([1.0, f64::MAX]);
1449 let left = Vector::<2>::new([0.0, 1.0]);
1450 let right = Vector::<2>::new([0.0, -1.0]);
1451
1452 assert_eq!(
1453 axis.dot_difference_with_errbound(&left, &right),
1454 Err(LaError::non_finite_computation_step(
1455 ArithmeticOperation::VectorDotDifference,
1456 1,
1457 ))
1458 );
1459 }
1460
1461 #[test]
1462 fn vector_dot_and_norm_squared_report_first_middle_overflowing_step() {
1463 let dot_lhs = Vector::<3>::new([f64::MAX, f64::MAX, 1.0]);
1464 let dot_rhs = Vector::<3>::new([1.0; 3]);
1465 assert_eq!(
1466 dot_lhs.dot(&dot_rhs),
1467 Err(LaError::non_finite_computation_step(
1468 ArithmeticOperation::VectorDotProduct,
1469 1,
1470 ))
1471 );
1472
1473 let norm_large = 1.0e154;
1474 let vector = Vector::<3>::new([norm_large, norm_large, 1.0]);
1475 assert_eq!(
1476 vector.norm_squared(),
1477 Err(LaError::non_finite_computation_step(
1478 ArithmeticOperation::VectorSquaredNorm,
1479 1,
1480 ))
1481 );
1482 }
1483
1484 #[test]
1485 fn zero_dimension_vector_has_zero_dot_and_norm() {
1486 let vector = Vector::<0>::try_new([]).unwrap();
1487
1488 assert!(vector.as_array().is_empty());
1489 assert!(vector.into_array().is_empty());
1490 assert_eq!(vector.dot(&Vector::zero()), Ok(0.0));
1491 let dot_bound = vector.dot_with_errbound(&Vector::zero()).unwrap().unwrap();
1492 assert_abs_diff_eq!(dot_bound.absolute_error_bound(), 0.0, epsilon = 0.0);
1493 let difference_bound = vector
1494 .dot_difference_with_errbound(&Vector::zero(), &Vector::zero())
1495 .unwrap()
1496 .unwrap();
1497 assert_abs_diff_eq!(difference_bound.absolute_error_bound(), 0.0, epsilon = 0.0);
1498 assert_eq!(vector.norm_squared(), Ok(0.0));
1499 }
1500
1501 #[test]
1502 fn certified_dot_withholds_bound_when_exact_product_exceeds_finite_range() {
1503 let left_value = f64::from_bits(0x7fe3_0319_b612_3729);
1504 let right_value = f64::from_bits(0x3ffa_ee21_bf46_bc00);
1505 let left = Vector::<1>::new([left_value]);
1506 let right = Vector::<1>::new([right_value]);
1507
1508 assert!(left_value.mul_add(right_value, -f64::MAX) > 0.0);
1509 assert_eq!(left.dot(&right), Ok(f64::MAX));
1510 assert_eq!(left.dot_with_errbound(&right), Ok(None));
1511 }
1512}