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
- Call
filesize(BigInt(2 ** 53 + 1)) — the +1 is silently dropped.
- Call
filesize(BigInt(10 ** 24 - 1)) — a value just below 1 YB.
- 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
- Detect BigInt input — In
filesize(), check typeof arg === "bigint" before the Number(arg) coercion at line 89. Route bigint inputs to a dedicated branch.
- 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.
- 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.
- 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.
- 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).
- Verify — Run
npm test and npm run coverage to confirm no regressions and 100% coverage maintained.
Summary
BigInt inputs lose precision and misdetect unit boundaries because
filesize()coerces the argument withNumber(arg), which silently rounds values aboveNumber.MAX_SAFE_INTEGER(2^53 - 1). Users passingbigintvalues get inaccurate results.Reproduction
filesize(BigInt(2 ** 53 + 1))— the+1is silently dropped.filesize(BigInt(10 ** 24 - 1))— a value just below 1 YB.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 fromfilesize(BigInt(2 ** 53))— the+1must 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))andfilesize(BigInt(2 ** 53))both return"9.01 PB"— the+1is 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
Code Sample
Additional Context
The root cause is the single coercion
num = Number(arg)insrc/filesize.js.Number()cannot represent integers above 2^53 exactly, so anybigintabove that threshold loses precision. This also affects exponent detection:Number(10 ** 24 - 1)rounds up to10 ** 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
bigintarithmetic (avoidingNumber()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)
src/filesize.js— line 89num = Number(arg)is the single root cause.Number()cannot represent integers above 2^53 exactly, so anybigintaboveNumber.MAX_SAFE_INTEGERloses precision.src/filesize.js—calculateExponent()(line 129) andcalculateOptimizedValue()(line 139) both operate on the coercednum. They useMath.log()and float division, which cannot recover the lost precision.src/helpers.js—calculateExponent()usesMath.log(num) / LOG_10_1000(orLOG_2_1024), andcalculateOptimizedValue()dividesnumbyDECIMAL_POWERS[e]/BINARY_POWERS[e]. Both are float paths.src/constants.js—BINARY_POWERSandDECIMAL_POWERSare arrays ofnumber(max 2^80 / 10^24). The ceiling is exponent 8 (YB / YiB), matching the user's stated limit.Fix Steps
filesize(), checktypeof arg === "bigint"before theNumber(arg)coercion at line 89. Route bigint inputs to a dedicated branch.bigintcomparisons (e.g.,num >= 10n ** BigInt(3 * (e + 1))for SI,num >= 1024n ** BigInt(e + 1)for IEC), clamped to exponent 8. This avoidsMath.log()precision loss.bigintby the appropriatebigintpower (e.g.,10n ** BigInt(3 * e)or1024n ** 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.valueande, feed the same values into the existingapplyRounding,applyPrecisionHandling,decorateResult, andformatOutputflow. No overlap in the BigInt branch until the returns.tests/unit/filesize.test.js, add cases forBigInt(2 ** 53 + 1)(must differ from2 ** 53),BigInt(10 ** 24 - 1)(must be ZB, not YB), andBigInt(1024 ** 8 - 1)withstandard: "iec"(must be ZiB, not YiB).npm testandnpm run coverageto confirm no regressions and 100% coverage maintained.