| 40 | * (e.g. `0.12345678901234567` → `0.12345678901234566`). So we test the exact |
| 41 | * round-trip condition: the value must equal the BigDecimal reconstructed from |
| 42 | * its own `toNumber()` (via the shortest-string form a JSON number would emit). |
| 43 | */ |
| 44 | export function isInMachineRange(d: BigNum): boolean { |
| 45 | if (!d.isFinite()) return true; // Infinity and NaN are in machine range |
| 46 | if (d.isZero()) return true; |
| 47 | |
| 48 | // Count significant digits in the significand |
| 49 | const absSig = d.significand < 0n ? -d.significand : d.significand; |
| 50 | const sigStr = absSig.toString(); |
| 51 | const digits = sigStr.length; |
| 52 | |
| 53 | // A float64's shortest round-tripping decimal has at most 17 significant |
| 54 | // digits, so anything longer cannot be exactly represented. |
| 55 | if (digits > 17) return false; |
| 56 | |
| 57 | // Check the value is within float64 range (avoid overflow to Infinity and |
| 58 | // subnormal precision loss). |
| 59 | // value = sig × 10^exp, order of magnitude ≈ digits + exponent - 1 |
| 60 | const orderOfMagnitude = digits + d.exponent - 1; |
| 61 | if (orderOfMagnitude >= 309 || orderOfMagnitude <= -308) return false; |
| 62 | |
| 63 | // Exact round-trip test: representable iff it survives float64 conversion. |
| 64 | return d.eq(new BigDecimal(d.toNumber())); |
| 65 | } |