@@ -101,6 +101,7 @@ pub(super) enum Approximation {
101101 // rational in the node avoids a child Computable::approx call before
102102 // entering that series.
103103 AsinRational ( Rational ) ,
104+ PrescaledAsin ( Computable ) ,
104105 // Generic non-rational asin uses the stable half-angle atan transform. A
105106 // deferred node keeps construction thin for symbolic radicals and endpoint
106107 // inputs that may never be approximated.
@@ -123,6 +124,7 @@ pub(super) enum Approximation {
123124 // approximation and keeps the exact value symbolic until the kernel rounds.
124125 AsinhRational ( Rational ) ,
125126 AtanhDirect ( Computable ) ,
127+ PrescaledAtanh ( Computable ) ,
126128 AtanhRational ( Rational ) ,
127129 PrescaledCos ( Computable ) ,
128130 // Small exact-rational Real::cos construction uses this leaf to avoid
@@ -239,6 +241,7 @@ impl Approximation {
239241 PrescaledAtan ( c) => atan_computable ( signal, c, p) ,
240242 AtanRational ( r) => atan_rational ( signal, r, p) ,
241243 AsinRational ( r) => asin_rational ( signal, r, p) ,
244+ PrescaledAsin ( c) => asin_computable ( signal, c, p) ,
242245 AsinDeferred ( c) => asin_deferred ( signal, c, p) ,
243246 AcosPositive ( c) => acos_positive ( signal, c, p) ,
244247 AcosPositiveRational ( r) => acos_positive_rational ( signal, r, p) ,
@@ -250,6 +253,7 @@ impl Approximation {
250253 PrescaledAsinh ( c) => asinh_computable ( signal, c, p) ,
251254 AsinhRational ( r) => asinh_rational ( signal, r, p) ,
252255 AtanhDirect ( c) => atanh_direct ( signal, c, p) ,
256+ PrescaledAtanh ( c) => atanh_computable ( signal, c, p) ,
253257 AtanhRational ( r) => atanh_rational ( signal, r, p) ,
254258 PrescaledCos ( c) => cos ( signal, c, p) ,
255259 PrescaledCosRational ( r) => cos_rational ( signal, r, p) ,
@@ -2107,6 +2111,44 @@ fn asin_rational(signal: &Option<Signal>, r: &Rational, p: Precision) -> BigInt
21072111 scale ( sum, calc_precision - p)
21082112}
21092113
2114+ // Approximate asin(c) for small |c|.
2115+ fn asin_computable ( signal : & Option < Signal > , c : & Computable , p : Precision ) -> BigInt {
2116+ // Dedicated tiny-argument asin series. It avoids the generic atan/sqrt
2117+ // transform, which is overkill and slower when |x| is already very small.
2118+ if p >= 1 {
2119+ return Zero :: zero ( ) ;
2120+ }
2121+
2122+ let iterations_needed: i32 = -p / 2 + 4 ;
2123+ let calc_precision = p - bound_log2 ( 2 * iterations_needed) - 5 ;
2124+ let op_prec = calc_precision - 3 ;
2125+ let op_appr = c. approx_signal ( signal, op_prec) ;
2126+ let op_squared = scale ( & op_appr * & op_appr, op_prec) ;
2127+
2128+ // Borrowed magnitude checks matter here because tiny inverse-trig benches
2129+ // run many short series from cold caches.
2130+ let max_trunc_error = BigUint :: one ( )
2131+ << usize:: try_from ( p - 4 - calc_precision) . expect ( "truncation shift is nonnegative" ) ;
2132+ let mut current_term = scale ( op_appr, op_prec - calc_precision) ;
2133+ let mut sum = current_term. clone ( ) ;
2134+ let mut n = 0_i32 ;
2135+
2136+ while current_term. magnitude ( ) > & max_trunc_error {
2137+ if should_stop ( signal) {
2138+ break ;
2139+ }
2140+ n += 1 ;
2141+ current_term = scale ( current_term * & op_squared, op_prec) ;
2142+ let numerator = ( 2 * n - 1 ) * ( 2 * n - 1 ) ;
2143+ let denominator = ( 2 * n) * ( 2 * n + 1 ) ;
2144+ current_term *= numerator;
2145+ current_term /= denominator;
2146+ sum += & current_term;
2147+ }
2148+
2149+ scale ( sum, calc_precision - p)
2150+ }
2151+
21102152fn asin_deferred ( signal : & Option < Signal > , c : & Computable , p : Precision ) -> BigInt {
21112153 // Generic asin uses asin(x) = 2*atan(x / (sqrt(1-x^2)+1)).
21122154 // Keeping this as one approximation node mirrors the deferred acos and
@@ -2285,6 +2327,42 @@ fn atanh_direct(signal: &Option<Signal>, c: &Computable, p: Precision) -> BigInt
22852327 . approx_signal ( signal, p)
22862328}
22872329
2330+ // Approximate atanh(c) for small |c|.
2331+ fn atanh_computable ( signal : & Option < Signal > , c : & Computable , p : Precision ) -> BigInt {
2332+ // Dedicated tiny-argument atanh series, also reused by the ln1p kernel after
2333+ // it transforms ln(1+x) into 2*atanh(x/(2+x)).
2334+ if p >= 1 {
2335+ return Zero :: zero ( ) ;
2336+ }
2337+
2338+ let iterations_needed: i32 = -p / 2 + 4 ;
2339+ let calc_precision = p - bound_log2 ( 2 * iterations_needed) - 5 ;
2340+ let op_prec = calc_precision - 3 ;
2341+ let op_appr = c. approx_signal ( signal, op_prec) ;
2342+ let op_squared = scale ( & op_appr * & op_appr, op_prec) ;
2343+
2344+ // Borrowed magnitude checks matter here because tiny inverse-hyperbolic
2345+ // benches run many short series from cold caches.
2346+ let max_trunc_error = BigUint :: one ( )
2347+ << usize:: try_from ( p - 4 - calc_precision) . expect ( "truncation shift is nonnegative" ) ;
2348+ let mut current_power = scale ( op_appr, op_prec - calc_precision) ;
2349+ let mut current_term = current_power. clone ( ) ;
2350+ let mut sum = current_term. clone ( ) ;
2351+ let mut n = 1_i32 ;
2352+
2353+ while current_term. magnitude ( ) > & max_trunc_error {
2354+ if should_stop ( signal) {
2355+ break ;
2356+ }
2357+ n += 2 ;
2358+ current_power = scale ( current_power * & op_squared, op_prec) ;
2359+ current_term = & current_power / n;
2360+ sum += & current_term;
2361+ }
2362+
2363+ scale ( sum, calc_precision - p)
2364+ }
2365+
22882366fn atanh_rational ( signal : & Option < Signal > , r : & Rational , p : Precision ) -> BigInt {
22892367 // Direct exact-rational variant of the tiny atanh series. This mirrors the
22902368 // direct rational trig kernels: preserve the symbolic rational payload and
0 commit comments