( base: Interval | IntervalResult, p: number, q: number )
| 259 | * bracket the range. A negative exponent has a pole at 0. |
| 260 | */ |
| 261 | export function powRational( |
| 262 | base: Interval | IntervalResult, |
| 263 | p: number, |
| 264 | q: number |
| 265 | ): IntervalResult { |
| 266 | const unwrapped = unwrapOrPropagate(base); |
| 267 | if (!Array.isArray(unwrapped)) return unwrapped; |
| 268 | const [b] = unwrapped; |
| 269 | const exp = p / q; |
| 270 | |
| 271 | // Even denominator: real only for a non-negative base — `pow` already models |
| 272 | // the empty/partial/monotone cases correctly. |
| 273 | if (q % 2 === 0) return pow(ok(b), exp); |
| 274 | |
| 275 | // Odd denominator, non-negative base: ordinary `pow` is exact. |
| 276 | if (b.lo >= 0) return pow(ok(b), exp); |
| 277 | |
| 278 | const hasZero = b.lo <= 0 && b.hi >= 0; |
| 279 | // Negative exponent has a pole at 0. |
| 280 | if (p < 0 && hasZero) return { kind: 'singular', at: 0 }; |
| 281 | |
| 282 | const candidates = [realRatPow(b.lo, p, exp), realRatPow(b.hi, p, exp)]; |
| 283 | // 0 is the interior minimum for an even numerator (positive exponent). |
| 284 | if (hasZero && p > 0) candidates.push(0); |
| 285 | return ok({ lo: Math.min(...candidates), hi: Math.max(...candidates) }); |
| 286 | } |
| 287 | |
| 288 | /** |
| 289 | * Integer nth root on an interval (`Root(x, n)`). |
no test coverage detected