| 259 | export function halfTurnAngle(ce: ComputeEngine): Expression { |
| 260 | const unit = ce.angularUnit; |
| 261 | if (unit === 'deg') return ce.number(180); |
| 262 | if (unit === 'grad') return ce.number(200); |
| 263 | if (unit === 'turn') return ce.number([1, 2]); |
| 264 | return ce.Pi; |
| 265 | } |
| 266 | |
| 267 | /** Assuming x in an expression in radians, convert to current angular unit. */ |
| 268 | export function radiansToAngle( |
| 269 | x: Expression | undefined |
| 270 | ): Expression | undefined { |
| 271 | if (!x) return x; |
| 272 | const ce = x.engine; |
| 273 | const angularUnit = ce.angularUnit; |
| 274 | if (angularUnit === 'rad') return x; |
| 275 | |
| 276 | const n = x.N(); |
| 277 | const theta = n.re; |
| 278 | if (Number.isNaN(theta)) return x; |
| 279 | const scale = |
| 280 | angularUnit === 'deg' |
| 281 | ? 180 / Math.PI |
| 282 | : angularUnit === 'grad' |
| 283 | ? 200 / Math.PI |
| 284 | : angularUnit === 'turn' |
| 285 | ? 1 / (2 * Math.PI) |
| 286 | : null; |
| 287 | if (scale === null) return x; |
| 288 | // The unit conversion is linear, so it applies to the whole complex value: |
| 289 | // reading only `.re` silently returned a wrong REAL angle for a complex |
| 290 | // one (`arcsin(2.5)` in deg mode gave `90`, dropping the imaginary part). |
| 291 | // A dust-sized imaginary part (kernel roundoff, not `ce.tolerance`) chops |
| 292 | // to the real path. |
| 293 | if (!Number.isNaN(n.im) && chop(n.im, ROUNDOFF_TOLERANCE) !== 0) |
| 294 | return ce.number(ce.complex(theta * scale, n.im * scale)); |
| 295 | return ce.number(theta * scale); |
| 296 | } |
| 297 | |
| 298 | /** |
| 299 | * Chop numericization dust from a bignum trig kernel. `.N()` substitutes a |
| 300 | * precision-limited approximation for a symbolic zero-crossing argument |
| 301 | * (`Sin(π)` becomes sin of π-to-`precision`-digits ≈ 10^−precision), so the |
| 302 | * dust scale is the BIGNUM roundoff, 10^(2−precision) — NOT `ce.tolerance`, |
| 303 | * which destroyed legitimately-computed small results (`sin(3.141592653588793)` |
| 304 | * ≈ 1.0e−12 chopped to 0 at the default 1e-10 tolerance; the #231 failure |
| 305 | * class). See ARCHITECTURE.md § "Chopping and the `im === 0` convention". |
| 306 | */ |
| 307 | function chopBignumDust(ce: ComputeEngine, value: BigDecimal): BigDecimal | 0 { |
| 308 | if (value.abs().lte(new BigDecimal(`1e${2 - ce.precision}`))) return 0; |
| 309 | return value; |
| 310 | } |
| 311 | |
| 312 | export function evalTrig( |
| 313 | name: string, |
| 314 | op: Expression | undefined |
| 315 | ): Expression | undefined { |
| 316 | if (!op) return undefined; |
| 317 | const ce = op.engine; |
| 318 | |