( ops: ReadonlyArray<Expression>, mode: 'LCM' | 'GCD' )
| 2794 | // The node's own exponent, BEFORE numericization: `n` above may be a |
| 2795 | // double by now, which loses the exact rational terms that decide the |
| 2796 | // branch of a negative base. `expression.ops` are the raw operands |
| 2797 | // this call's `[x, n]` were evaluated from, so `op2` is `n`'s |
| 2798 | // provenance. (Absent when the handler is invoked outside the |
| 2799 | // evaluation driver — `pow` then falls back to reconstruction.) |
| 2800 | rawExponent: |
| 2801 | expression !== undefined && isFunction(expression) |
| 2802 | ? expression.op2 |
| 2803 | : undefined, |
| 2804 | }); |
| 2805 | }, |
| 2806 | // Defined as RealNumbers for all power in RealNumbers when base > 0; |
| 2807 | // when x < 0, only defined if n is an integer |
| 2808 | // if x is a non-zero complex, defined as ComplexNumbers |
| 2809 | // Square root of a prime is irrational (AlgebraicNumbers) |
| 2810 | // https://proofwiki.org/wiki/Square_Root_of_Prime_is_Irrational |
| 2811 | }, |
| 2812 | |
| 2813 | Rational: { |
| 2814 | description: |
| 2815 | 'Construct a rational number from a numerator and denominator.', |
| 2816 | complexity: 2400, |
| 2817 | |
| 2818 | // Two distinct forms (ruled 2026-08-24): `Rational(x)` approximates a |
| 2819 | // real by a rational; `Rational(n, d)` CONSTRUCTS the rational `n/d` |
| 2820 | // from two integers. There is no `(number, number)` form — the |
| 2821 | // two-argument constructor rewrites to `Divide`, so a non-integer |
| 2822 | // argument would silently become plain division (`Rational(3, 2.5)` |
| 2823 | // evaluated to `1.2`), which almost always indicates a caller error. |
| 2824 | signature: '((real) -> rational) | ((integer, integer) -> rational)', |
| 2825 | sgn: ([n]) => n.sgn, |
| 2826 | canonical: (args, { engine }) => { |
| 2827 | const ce = engine; |
| 2828 | args = flatten(args); |
| 2829 | |
| 2830 | if (args.length === 0) return ce._fn('Rational', [ce.error('missing')]); |
| 2831 | |
| 2832 | if (args.length === 1) |
| 2833 | return ce._fn('Rational', [checkType(ce, args[0], 'real')]); |
| 2834 | |
| 2835 | // `checkType` admits a merely-OVERLAPPING operand (deferred |
| 2836 | // validation: a `number`-typed symbol may turn out integral), so a |
| 2837 | // concrete non-integer — a literal or a symbol holding one — slipped |
| 2838 | // through to the `Divide` rewrite. Decide exactly on the evidence: |
| 2839 | // proven non-integers are rejected here, while a valueless symbol |
| 2840 | // stays admitted and rewrites to `Divide` as before. (The box-time |
| 2841 | // numeric constructor in `box.ts` makes the same check; this branch |
| 2842 | // covers the structural/partial-canonicalization routes that reach |
| 2843 | // the handler instead.) |
| 2844 | args = checkTypes(ce, args, ['integer', 'integer']).map((arg) => |
no test coverage detected