(lhs: Rational, rhs: Rational)
| 72 | * magnitude is out of the double range. |
| 73 | */ |
| 74 | export function rationalAsFloat(x: Rational): number { |
| 75 | const n = Number(x[0]); |
| 76 | const d = Number(x[1]); |
| 77 | if (typeof x[0] === 'bigint' && (!Number.isFinite(n) || !Number.isFinite(d))) |
| 78 | return bigRatioToFloat(x[0], x[1] as bigint); |
| 79 | return n / d; |
| 80 | } |
| 81 | |
| 82 | /** |
| 83 | * The double nearest `n/d` for bigints of any size (`d ≠ 0`), computed |
| 84 | * without converting `n` or `d` to a double first. |
| 85 | * |
| 86 | * The quotient is scaled by a power of two `2^-e` so that its integer part |
| 87 | * `q` has 64 or 65 bits, one more than a double can hold. The lowest bit of |
| 88 | * `q` is set when the division has a remainder, so the conversion |
| 89 | * `Number(q)`, which rounds to 53 bits, rounds a truncated value that is |
| 90 | * just above a halfway point in the correct direction. The scale `2^e` is |
| 91 | * then applied in two steps, so that `2^(e/2)` stays in the double range |
| 92 | * for every `|e|` up to about 2046; beyond that the product is `0` or |
| 93 | * `±Infinity`, which is the nearest double. Multiplying by a power of two |
| 94 | * is exact while the result is a normal double, so a normal result is |
| 95 | * rounded once, by `Number(q)`. |
| 96 | * |
| 97 | * A subnormal result (below `2^-1022`) has fewer than 53 significant bits, |
| 98 | * so the multiplication by `2^e` would round a second time, and two |
| 99 | * roundings can give the wrong double: `(2^54 + 1)/2^1129` is just above |
| 100 | * half of `2^-1074`, but `Number(q)` rounds it to exactly half, and the |
| 101 | * product then rounds the tie to `0`. So when the value can be below |
| 102 | * `2^-1022`, the quotient `a·2^1074/b` is rounded to an integer `k` with |
| 103 | * the remainder, half to even, and the result is `k·2^-1074`, which is |
| 104 | * exact. When `k` is `2^52` or more, the value is normal and takes the |
| 105 | * path above. |
| 106 | */ |
| 107 | function bigRatioToFloat(n: bigint, d: bigint): number { |
| 108 | if (d === 0n) return n === 0n ? NaN : n > 0n ? Infinity : -Infinity; |
| 109 | if (n === 0n) return 0; |
| 110 | const negative = n < 0n !== d < 0n; |
| 111 | const a = n < 0n ? -n : n; |
| 112 | const b = d < 0n ? -d : d; |
| 113 | const la = a.toString(2).length; |
| 114 | const lb = b.toString(2).length; |
| 115 | // `a/b < 2^(la - lb + 1)`, so the value can be subnormal only when |
| 116 | // `la - lb + 1 <= -1022`. |
| 117 | if (la - lb <= -1023) { |
| 118 | const num = a << 1074n; |
no test coverage detected