@@ -1031,6 +1031,21 @@ impl Computable {
10311031 }
10321032 }
10331033
1034+ fn prescaled_asin ( value : Computable ) -> Computable {
1035+ // Tiny non-rational asin inputs use the direct odd-power series. This
1036+ // mirrors prescaled atan/asinh dispatch and avoids building the generic
1037+ // atan/sqrt transform once the argument is structurally small.
1038+ crate :: trace_dispatch!( "computable" , "constructor" , "prescaled-asin" ) ;
1039+ let sign = value. exact_sign ( ) ;
1040+ Self {
1041+ internal : Box :: new ( Approximation :: PrescaledAsin ( value) ) ,
1042+ cache : RefCell :: new ( Cache :: Invalid ) ,
1043+ bound : RefCell :: new ( BoundCache :: Invalid ) ,
1044+ exact_sign : RefCell :: new ( sign. map_or ( ExactSignCache :: Invalid , ExactSignCache :: Valid ) ) ,
1045+ signal : None ,
1046+ }
1047+ }
1048+
10341049 fn asinh_rational_deferred ( rational : Rational ) -> Computable {
10351050 // Same series as `prescaled_asinh`, but exact rationals can skip the
10361051 // child Computable wrapper and feed the kernel directly.
@@ -1045,6 +1060,21 @@ impl Computable {
10451060 }
10461061 }
10471062
1063+ fn prescaled_atanh ( value : Computable ) -> Computable {
1064+ // Tiny non-rational atanh inputs use the direct odd-power series. This
1065+ // keeps parity with exact-rational AtanhRational and avoids the heavier
1066+ // log-ratio graph for already-small symbolic arguments.
1067+ crate :: trace_dispatch!( "computable" , "constructor" , "prescaled-atanh" ) ;
1068+ let sign = value. exact_sign ( ) ;
1069+ Self {
1070+ internal : Box :: new ( Approximation :: PrescaledAtanh ( value) ) ,
1071+ cache : RefCell :: new ( Cache :: Invalid ) ,
1072+ bound : RefCell :: new ( BoundCache :: Invalid ) ,
1073+ exact_sign : RefCell :: new ( sign. map_or ( ExactSignCache :: Invalid , ExactSignCache :: Valid ) ) ,
1074+ signal : None ,
1075+ }
1076+ }
1077+
10481078 fn atanh_rational_deferred ( rational : Rational ) -> Computable {
10491079 // Tiny exact-rational atanh uses the odd series directly. Keeping the
10501080 // Rational payload avoids rebuilding a Ratio node in cold approximation
@@ -3343,6 +3373,11 @@ impl Computable {
33433373 crate :: trace_dispatch!( "computable" , "asin" , "known-negative-symmetry" ) ;
33443374 return self . negate ( ) . asin ( ) . negate ( ) ;
33453375 }
3376+ let ( _, planned_msd) = self . planning_sign_and_msd ( ) ;
3377+ if planned_msd. flatten ( ) . is_some_and ( |msd| msd <= -4 ) {
3378+ crate :: trace_dispatch!( "computable" , "asin" , "structural-tiny-prescaled" ) ;
3379+ return Self :: prescaled_asin ( self ) ;
3380+ }
33463381
33473382 crate :: trace_dispatch!( "computable" , "asin" , "generic-atan-sqrt-transform" ) ;
33483383 Self :: asin_deferred ( self )
@@ -3595,6 +3630,11 @@ impl Computable {
35953630 crate :: trace_dispatch!( "computable" , "atanh" , "known-negative-symmetry" ) ;
35963631 return self . negate ( ) . atanh ( ) . negate ( ) ;
35973632 }
3633+ let ( _, planned_msd) = self . planning_sign_and_msd ( ) ;
3634+ if planned_msd. flatten ( ) . is_some_and ( |msd| msd <= -4 ) {
3635+ crate :: trace_dispatch!( "computable" , "atanh" , "structural-tiny-prescaled" ) ;
3636+ return Self :: prescaled_atanh ( self ) ;
3637+ }
35983638
35993639 // General formula 1/2 * ln((1+x)/(1-x)). Tiny exact rationals avoid
36003640 // this path because the odd atanh series has much less setup.
@@ -5436,6 +5476,34 @@ mod tests {
54365476 assert_approx ( near_one. atanh ( ) , -40 , "7976218668587" , 2 ) ;
54375477 }
54385478
5479+ #[ test]
5480+ fn tiny_non_rational_asin_uses_prescaled_series ( ) {
5481+ let tiny = Computable :: rational ( Rational :: new ( 2 ) )
5482+ . sqrt ( )
5483+ . shift_left ( -20 ) ;
5484+ let result = tiny. clone ( ) . asin ( ) ;
5485+
5486+ assert ! ( matches!(
5487+ result. internal. as_ref( ) ,
5488+ Approximation :: PrescaledAsin ( _)
5489+ ) ) ;
5490+ assert_close ( result, Computable :: asin_deferred ( tiny) , -80 , 4 ) ;
5491+ }
5492+
5493+ #[ test]
5494+ fn tiny_non_rational_atanh_uses_prescaled_series ( ) {
5495+ let tiny = Computable :: rational ( Rational :: new ( 2 ) )
5496+ . sqrt ( )
5497+ . shift_left ( -20 ) ;
5498+ let result = tiny. clone ( ) . atanh ( ) ;
5499+
5500+ assert ! ( matches!(
5501+ result. internal. as_ref( ) ,
5502+ Approximation :: PrescaledAtanh ( _)
5503+ ) ) ;
5504+ assert_close ( result, Computable :: atanh_direct_deferred ( tiny) , -80 , 4 ) ;
5505+ }
5506+
54395507 #[ test]
54405508 fn inverse_hyperbolic_computable_kernels_approximate_expected_values ( ) {
54415509 let half = Computable :: rational ( Rational :: fraction ( 1 , 2 ) . unwrap ( ) ) ;
0 commit comments