( num: number | bigint, fractionalDigits?: string | number )
| 32 | |
| 33 | /** Output a shorthand if possible */ |
| 34 | export function numberToExpression( |
| 35 | num: number | bigint, |
| 36 | fractionalDigits?: string | number |
| 37 | ): MathJsonExpression { |
| 38 | if (typeof num === 'number') { |
| 39 | if (isNaN(num)) return 'NaN'; |
| 40 | if (!Number.isFinite(num)) |
| 41 | return num < 0 ? 'NegativeInfinity' : 'PositiveInfinity'; |
| 42 | |
| 43 | if (typeof fractionalDigits === 'number') |
| 44 | return { num: num.toFixed(fractionalDigits) }; |
| 45 | |
| 46 | return num; |
| 47 | } |
| 48 | |
| 49 | if (num >= Number.MIN_SAFE_INTEGER && num <= Number.MAX_SAFE_INTEGER) |
| 50 | return Number(num); |
| 51 | |
| 52 | // Only use the machine-number shorthand when the float is *exactly* equal to |
| 53 | // the integer. A string-display comparison is unsound: e.g. |
| 54 | // `Number(10n ** 23n).toString() === '1e+23'` is true because |
| 55 | // `Number.prototype.toString()` returns the shortest uniquely-identifying |
| 56 | // decimal, yet the float ≠ 10^23. Emitting that float as a JSON number would |
| 57 | // corrupt the value on reconstruction. `BigInt(n)` of an integral float is |
| 58 | // its exact value, so equality guarantees losslessness. |
| 59 | const n = Number(num); |
| 60 | if (Number.isFinite(n) && BigInt(n) === num) return n; |
| 61 | |
| 62 | return { num: numberToString(num) }; |
| 63 | } |
no test coverage detected