( base: Interval | IntervalResult, exp: number )
| 111 | if (isNaNInterval(xVal)) return ok(NAN_INTERVAL); |
| 112 | if (xVal.lo >= 0) { |
| 113 | // Entirely non-negative: monotonically increasing |
| 114 | return ok({ lo: xVal.lo * xVal.lo, hi: xVal.hi * xVal.hi }); |
| 115 | } else if (xVal.hi <= 0) { |
| 116 | // Entirely non-positive: monotonically decreasing, flip bounds |
| 117 | return ok({ lo: xVal.hi * xVal.hi, hi: xVal.lo * xVal.lo }); |
| 118 | } else { |
| 119 | // Interval contains 0 - minimum is 0 |
| 120 | return ok({ lo: 0, hi: Math.max(xVal.lo * xVal.lo, xVal.hi * xVal.hi) }); |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | /** |
| 125 | * Integer power helper for non-negative integer exponents. |
| 126 | */ |
| 127 | function intPow(base: Interval, n: number): Interval { |
| 128 | if (n === 0) return { lo: 1, hi: 1 }; |
| 129 | if (n === 1) return base; |
| 130 | |
| 131 | // For even powers, the function has a minimum at 0 |
| 132 | if (n % 2 === 0) { |
| 133 | if (base.lo >= 0) { |
| 134 | return { lo: Math.pow(base.lo, n), hi: Math.pow(base.hi, n) }; |
| 135 | } else if (base.hi <= 0) { |
| 136 | return { lo: Math.pow(base.hi, n), hi: Math.pow(base.lo, n) }; |
| 137 | } else { |
| 138 | // Contains zero - minimum is 0 |
| 139 | return { |
| 140 | lo: 0, |
| 141 | hi: Math.max(Math.pow(base.lo, n), Math.pow(base.hi, n)), |
| 142 | }; |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | // For odd powers, the function is monotonically increasing |
| 147 | return { lo: Math.pow(base.lo, n), hi: Math.pow(base.hi, n) }; |
| 148 | } |
| 149 | |
| 150 | /** |
| 151 | * Power function for intervals. |
| 152 | * |
| 153 | * Handles integer and fractional exponents differently: |
| 154 | * - Integer exponents: consider sign and parity |
| 155 | * - Negative integer: x^(-n) = 1/x^n, singular if base contains 0 |
| 156 | * - Fractional: requires non-negative base for real result |
| 157 | */ |
| 158 | function powRaw(base: Interval | IntervalResult, exp: number): IntervalResult { |
| 159 | const unwrapped = unwrapOrPropagate(base); |
| 160 | if (!Array.isArray(unwrapped)) return unwrapped; |
| 161 | const [baseVal] = unwrapped; |
| 162 | if (Number.isInteger(exp)) { |
| 163 | if (exp >= 0) { |
| 164 | return ok(intPow(baseVal, exp)); |
| 165 | } else { |
| 166 | // Negative integer: x^(-n) = 1/x^n - singularity if base contains 0 |
| 167 | if (containsZero(baseVal)) { |
| 168 | return { kind: 'singular' }; |
| 169 | } |
| 170 | const denom = intPow(baseVal, -exp); |
no test coverage detected