(x: Interval | IntervalResult)
| 88 | |
| 89 | /** |
| 90 | * Tangent of an interval. |
| 91 | * |
| 92 | * Has singularities at pi/2 + n*pi. Within a single branch, |
| 93 | * tan is monotonically increasing. |
| 94 | */ |
| 95 | export function tan(x: Interval | IntervalResult): IntervalResult { |
| 96 | const unwrapped = unwrapOrPropagate(x); |
| 97 | if (!Array.isArray(unwrapped)) return unwrapped; |
| 98 | const [xVal] = unwrapped; |
| 99 | // Case 1: Interval spans a full period - certainly crosses a singularity |
| 100 | if (xVal.hi - xVal.lo >= PI) { |
| 101 | return { kind: 'singular' }; |
| 102 | } |
| 103 | |
| 104 | // Case 2: Check if interval contains a pole at pi/2 + n*pi |
| 105 | if (containsExtremum(xVal, HALF_PI, PI)) { |
| 106 | // Find the pole location for refinement hints |
| 107 | const n = Math.ceil((xVal.lo - HALF_PI) / PI); |
| 108 | const poleAt = HALF_PI + n * PI; |
| 109 | return { kind: 'singular', at: poleAt }; |
| 110 | } |
| 111 | |
| 112 | // Case 3: Safe interval - tan is monotonic on this branch |
| 113 | const tanLo = Math.tan(xVal.lo); |
| 114 | const tanHi = Math.tan(xVal.hi); |
| 115 | |
| 116 | // Sanity check: if results have opposite signs with large magnitude, |
| 117 | // we may have crossed a branch due to floating-point error |
| 118 | if ((tanLo > 1e10 && tanHi < -1e10) || (tanLo < -1e10 && tanHi > 1e10)) { |
| 119 | return { kind: 'singular' }; |
| 120 | } |
| 121 |
no test coverage detected