@@ -191,14 +191,42 @@ impl<const D: usize> Vector<D> {
191191 let mut acc = 0.0 ;
192192 let mut i = 0 ;
193193 while i < D {
194+ acc = lhs[ i] . mul_add ( rhs[ i] , acc) ;
195+ i += 1 ;
196+ }
197+ if acc. is_finite ( ) {
198+ Ok ( acc)
199+ } else {
200+ cold_path ( ) ;
201+ Err ( Self :: dot_non_finite_error ( lhs, rhs, operation) )
202+ }
203+ }
204+
205+ /// Replay a non-finite dot product to locate the first failing step.
206+ ///
207+ /// This runs only after the success-path traversal has produced a non-finite
208+ /// final accumulator. Stored entries are finite, so once a fused multiply-add
209+ /// produces a non-finite accumulator, later steps cannot make it finite again.
210+ /// Replaying the same left-to-right operations must therefore find the first
211+ /// failing index.
212+ #[ cold]
213+ const fn dot_non_finite_error (
214+ lhs : & [ f64 ; D ] ,
215+ rhs : & [ f64 ; D ] ,
216+ operation : ArithmeticOperation ,
217+ ) -> LaError {
218+ let mut acc = 0.0 ;
219+ let mut i = 0 ;
220+ let last = D . saturating_sub ( 1 ) ;
221+ while i < last {
194222 acc = lhs[ i] . mul_add ( rhs[ i] , acc) ;
195223 if !acc. is_finite ( ) {
196- cold_path ( ) ;
197- return Err ( LaError :: non_finite_computation_step ( operation, i) ) ;
224+ return LaError :: non_finite_computation_step ( operation, i) ;
198225 }
199226 i += 1 ;
200227 }
201- Ok ( acc)
228+
229+ LaError :: non_finite_computation_step ( operation, last)
202230 }
203231
204232 /// Squared Euclidean norm.
@@ -425,6 +453,130 @@ mod tests {
425453 gen_vector_tests ! ( 4 ) ;
426454 gen_vector_tests ! ( 5 ) ;
427455
456+ macro_rules! gen_vector_replay_tests {
457+ ( $d: literal) => {
458+ paste! {
459+ #[ test]
460+ fn [ <vector_dot_and_norm2_sq_report_last_overflowing_step_ $d d>] ( ) {
461+ let mut dot_lhs = [ 1.0f64 ; $d] ;
462+ dot_lhs[ $d - 1 ] = f64 :: MAX ;
463+ let mut dot_rhs = [ 1.0f64 ; $d] ;
464+ dot_rhs[ $d - 1 ] = 2.0 ;
465+ let dot_lhs = Vector :: <$d>:: new( dot_lhs) ;
466+ let dot_rhs = Vector :: <$d>:: new( dot_rhs) ;
467+
468+ assert_eq!(
469+ dot_lhs. dot( & dot_rhs) ,
470+ Err ( LaError :: non_finite_computation_step(
471+ ArithmeticOperation :: VectorDotProduct ,
472+ $d - 1 ,
473+ ) )
474+ ) ;
475+
476+ let mut norm_data = [ 1.0f64 ; $d] ;
477+ norm_data[ $d - 1 ] = f64 :: MAX ;
478+ let vector = Vector :: <$d>:: new( norm_data) ;
479+
480+ assert_eq!(
481+ vector. norm2_sq( ) ,
482+ Err ( LaError :: non_finite_computation_step(
483+ ArithmeticOperation :: VectorSquaredNorm ,
484+ $d - 1 ,
485+ ) )
486+ ) ;
487+ }
488+ }
489+ } ;
490+ }
491+
492+ gen_vector_replay_tests ! ( 2 ) ;
493+ gen_vector_replay_tests ! ( 3 ) ;
494+ gen_vector_replay_tests ! ( 4 ) ;
495+ gen_vector_replay_tests ! ( 5 ) ;
496+
497+ macro_rules! gen_vector_const_eval_tests {
498+ ( $d: literal, $dot: literal, $norm2_sq: literal) => {
499+ paste! {
500+ #[ test]
501+ fn [ <vector_dot_and_norm2_sq_const_eval_ $d d>] ( ) {
502+ const DOT : Result <f64 , LaError > = Vector :: <$d>:: new( [ 1.0 ; $d] )
503+ . dot( & Vector :: <$d>:: new( [ 2.0 ; $d] ) ) ;
504+ const NORM2_SQ : Result <f64 , LaError > =
505+ Vector :: <$d>:: new( [ 1.0 ; $d] ) . norm2_sq( ) ;
506+
507+ assert_eq!( DOT , Ok ( $dot) ) ;
508+ assert_eq!( NORM2_SQ , Ok ( $norm2_sq) ) ;
509+ }
510+ }
511+ } ;
512+ }
513+
514+ gen_vector_const_eval_tests ! ( 2 , 4.0 , 2.0 ) ;
515+ gen_vector_const_eval_tests ! ( 3 , 6.0 , 3.0 ) ;
516+ gen_vector_const_eval_tests ! ( 4 , 8.0 , 4.0 ) ;
517+ gen_vector_const_eval_tests ! ( 5 , 10.0 , 5.0 ) ;
518+
519+ #[ test]
520+ fn vector_dot_and_norm2_sq_overflow_const_eval ( ) {
521+ const DOT : Result < f64 , LaError > =
522+ Vector :: < 2 > :: new ( [ f64:: MAX ; 2 ] ) . dot ( & Vector :: < 2 > :: new ( [ 1.0 ; 2 ] ) ) ;
523+ const NORM2_SQ : Result < f64 , LaError > = Vector :: < 2 > :: new ( [ f64:: MAX ; 2 ] ) . norm2_sq ( ) ;
524+
525+ assert_eq ! (
526+ DOT ,
527+ Err ( LaError :: non_finite_computation_step(
528+ ArithmeticOperation :: VectorDotProduct ,
529+ 1 ,
530+ ) )
531+ ) ;
532+ assert_eq ! (
533+ NORM2_SQ ,
534+ Err ( LaError :: non_finite_computation_step(
535+ ArithmeticOperation :: VectorSquaredNorm ,
536+ 0 ,
537+ ) )
538+ ) ;
539+ }
540+
541+ #[ test]
542+ fn vector_dot_and_norm2_sq_preserve_fma_and_left_to_right_order ( ) {
543+ let dot_large = 9_007_199_254_740_992.0 ;
544+ let dot_lhs = Vector :: < 4 > :: new ( [ dot_large, 1.0 , 1.0 , 1.0 ] ) ;
545+ let dot_rhs = Vector :: < 4 > :: new ( [ 1.0 ; 4 ] ) ;
546+ assert_eq ! ( dot_lhs. dot( & dot_rhs) , Ok ( dot_large) ) ;
547+
548+ let fused_lhs = Vector :: < 2 > :: new ( [ f64:: MAX , f64:: MAX ] ) ;
549+ let fused_rhs = Vector :: < 2 > :: new ( [ -1.0 , 2.0 ] ) ;
550+ assert_eq ! ( fused_lhs. dot( & fused_rhs) , Ok ( f64 :: MAX ) ) ;
551+
552+ let norm_large = 134_217_728.0 ;
553+ let vector = Vector :: < 4 > :: new ( [ norm_large, 1.0 , 1.0 , 1.0 ] ) ;
554+ assert_eq ! ( vector. norm2_sq( ) , Ok ( norm_large * norm_large) ) ;
555+ }
556+
557+ #[ test]
558+ fn vector_dot_and_norm2_sq_report_first_middle_overflowing_step ( ) {
559+ let dot_lhs = Vector :: < 3 > :: new ( [ f64:: MAX , f64:: MAX , 1.0 ] ) ;
560+ let dot_rhs = Vector :: < 3 > :: new ( [ 1.0 ; 3 ] ) ;
561+ assert_eq ! (
562+ dot_lhs. dot( & dot_rhs) ,
563+ Err ( LaError :: non_finite_computation_step(
564+ ArithmeticOperation :: VectorDotProduct ,
565+ 1 ,
566+ ) )
567+ ) ;
568+
569+ let norm_large = 1.0e154 ;
570+ let vector = Vector :: < 3 > :: new ( [ norm_large, norm_large, 1.0 ] ) ;
571+ assert_eq ! (
572+ vector. norm2_sq( ) ,
573+ Err ( LaError :: non_finite_computation_step(
574+ ArithmeticOperation :: VectorSquaredNorm ,
575+ 1 ,
576+ ) )
577+ ) ;
578+ }
579+
428580 #[ test]
429581 fn zero_dimension_vector_has_zero_dot_and_norm ( ) {
430582 let vector = Vector :: < 0 > :: try_new ( [ ] ) . unwrap ( ) ;
0 commit comments