Skip to content

fix: BigInt inputs lose precision and misdetect unit boundaries #354

Description

@avoidwork

Summary

BigInt inputs lose precision and misdetect unit boundaries because filesize() coerces the argument with Number(arg), which silently rounds values above Number.MAX_SAFE_INTEGER (2^53 - 1). Users passing bigint values get inaccurate results.

Reproduction

  1. Call filesize(BigInt(2 ** 53 + 1)) — the +1 is silently dropped.
  2. Call filesize(BigInt(10 ** 24 - 1)) — a value just below 1 YB.
  3. Call filesize(BigInt(1024 ** 8 - 1), { standard: "iec" }) — a value just below 1 YiB.

Expected Behavior

  • filesize(BigInt(2 ** 53 + 1)) should produce a distinct, accurate result from filesize(BigInt(2 ** 53)) — the +1 must not be lost.
  • filesize(BigInt(10 ** 24 - 1)) should report the value in ZB (exponent 7), not YB (exponent 8).
  • filesize(BigInt(1024 ** 8 - 1), { standard: "iec" }) should report ZiB (exponent 7), not YiB (exponent 8).

Actual Behavior

  • filesize(BigInt(2 ** 53 + 1)) and filesize(BigInt(2 ** 53)) both return "9.01 PB" — the +1 is lost.
  • filesize(BigInt(10 ** 24 - 1)) returns "1 YB" (exponent 8) — the value is rounded up across the boundary.
  • filesize(BigInt(1024 ** 8 - 1), { standard: "iec" }) returns "1 YiB" (exponent 8) — same boundary misdetection.

Environment

  • Node.js version: v25.8.1
  • OS: Linux 7.0.14-17-pve
  • filesize.js version: 11.0.24

Code Sample

import { filesize } from "filesize";

filesize(BigInt(2 ** 53 + 1)); // "9.01 PB" — should differ from 2^53
filesize(BigInt(10 ** 24 - 1)); // "1 YB" — should be "1000 ZB"
filesize(BigInt(1024 ** 8 - 1), { standard: "iec" }); // "1 YiB" — should be "1024 ZiB"

Additional Context

The root cause is the single coercion num = Number(arg) in src/filesize.js. Number() cannot represent integers above 2^53 exactly, so any bigint above that threshold loses precision. This also affects exponent detection: Number(10 ** 24 - 1) rounds up to 10 ** 24, crossing the YB boundary and producing the wrong unit.

The fix is a BigInt-specific branch that performs exponent detection and value division using bigint arithmetic (avoiding Number() until the final division), then rejoins the common output path. The unit ceiling is YB (exponent 8) for SI and YiB (exponent 8) for IEC.

Audit Findings (for Issue #354)

  • File: src/filesize.js — line 89 num = Number(arg) is the single root cause. Number() cannot represent integers above 2^53 exactly, so any bigint above Number.MAX_SAFE_INTEGER loses precision.
  • src/filesize.js — calculateExponent() (line 129) and calculateOptimizedValue() (line 139) both operate on the coerced num. They use Math.log() and float division, which cannot recover the lost precision.
  • src/helpers.js — calculateExponent() uses Math.log(num) / LOG_10_1000 (or LOG_2_1024), and calculateOptimizedValue() divides num by DECIMAL_POWERS[e] / BINARY_POWERS[e]. Both are float paths.
  • src/constants.js — BINARY_POWERS and DECIMAL_POWERS are arrays of number (max 2^80 / 10^24). The ceiling is exponent 8 (YB / YiB), matching the user's stated limit.

Fix Steps

  1. Detect BigInt input — In filesize(), check typeof arg === "bigint" before the Number(arg) coercion at line 89. Route bigint inputs to a dedicated branch.
  2. Add a BigInt exponent path — Compute the exponent with bigint comparisons (e.g., num >= 10n ** BigInt(3 * (e + 1)) for SI, num >= 1024n ** BigInt(e + 1) for IEC), clamped to exponent 8. This avoids Math.log() precision loss.
  3. Add a BigInt value path — Divide the bigint by the appropriate bigint power (e.g., 10n ** BigInt(3 * e) or 1024n ** BigInt(e)) using a scaled division that preserves precision (e.g., Number((num * 10n ** 16n) / power) / Number(10n ** 16n)), then apply the existing bits/auto-increment logic.
  4. Rejoin the common path — After computing value and e, feed the same values into the existing applyRounding, applyPrecisionHandling, decorateResult, and formatOutput flow. No overlap in the BigInt branch until the returns.
  5. Add regression tests — In tests/unit/filesize.test.js, add cases for BigInt(2 ** 53 + 1) (must differ from 2 ** 53), BigInt(10 ** 24 - 1) (must be ZB, not YB), and BigInt(1024 ** 8 - 1) with standard: "iec" (must be ZiB, not YiB).
  6. Verify — Run npm test and npm run coverage to confirm no regressions and 100% coverage maintained.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions