Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,18 @@ Input → Validation → Standard Normalization → Exponent Calculation

### Key Components

1. **`filesize(arg, options)`**: Main conversion function
2. **`partial(options)`**: Creates pre-configured function
1. **`filesize(arg, options)`**: Orchestrator — delegates validation, exponent, value calc, rounding, formatting, and output dispatch
2. **`partial(options)`**: Creates pre-configured formatter with immutable frozen options
3. **`handleZeroValue()`**: Special handling for zero input
4. **`calculateOptimizedValue()`**: Core conversion logic
5. **`applyPrecisionHandling()`**: Precision + scientific notation fix
4. **`calculateOptimizedValue()`**: Core conversion logic with bits handling
5. **`applyPrecisionHandling()`**: Precision + scientific notation correction
6. **`applyNumberFormatting()`**: Locale, separator, padding
7. **`getBaseConfiguration()`**: Cached base/standard lookup table
8. **`calculateExponent()`**: Log-based exponent calculation with clamping
9. **`applyRounding()`**: Rounding + auto-increment ceiling adjustment
10. **`resolveSymbol()`**: Symbol table lookup with SI override for e=1
11. **`decorateResult()`**: Negation, custom symbols, full form assembly
12. **`formatOutput()`**: Array/object/string output dispatch

### Standard Selection

Expand Down
247 changes: 177 additions & 70 deletions dist/filesize.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,148 @@ function applyNumberFormatting(value, locale, localeOptions, separator, pad, rou
return result;
}

/**
* Calculates exponent from the input value using pre-computed log values and clamps to supported range
* Also adjusts precision when exponent exceeds the lookup table bounds
* @param {number} num - Input file size in bytes
* @param {number} e - Current exponent value
* @param {number} exponent - Original user-provided exponent option (-1 for auto)
* @param {boolean} isDecimal - Whether to use decimal (SI) base
* @param {number} precision - Current precision value (modified when e > 8)
* @returns {Object} Object with computed e value and possibly adjusted precision
*/
function calculateExponent(num, e, exponent, isDecimal, precision) {
if (e === -1 || isNaN(e)) {
e = isDecimal
? Math.floor(Math.log(num) / LOG_10_1000)
: Math.floor(Math.log(num) / LOG_2_1024);
if (e < 0) {
e = 0;
}
}

if (e > 8) {
if (precision > 0) {
precision += 8 - e;
}
return { e: 8, precision };
}

return { e, precision };
}

/**
* Applies rounding to the raw calculated value and handles auto-increment ceiling
* @param {number} val - Raw value before rounding
* @param {number} ceil - Ceiling threshold (1000 for SI, 1024 for IEC)
* @param {number} e - Current exponent value
* @param {number} round - Number of decimal places
* @param {Function} roundingFunc - Rounding method (Math.round, Math.floor, Math.ceil)
* @param {boolean} autoExponent - Whether exponent is auto-calculated (-1 or NaN)
* @returns {Object} Object with rounded value and possibly incremented exponent
*/
function applyRounding(val, ceil, e, round, roundingFunc, autoExponent) {
const p = e > 0 && round > 0 ? Math.pow(10, round) : 1;
let r = p === 1 ? roundingFunc(val) : roundingFunc(val * p) / p;

if (r === ceil && e < 8 && autoExponent) {
r = 1;
e++;
}

return { value: r, e };
}

/**
* Resolves the unit symbol for the given standard, bits mode, and exponent
* Handles SI standard special case where exponent 1 always uses "kB" or "kbit"
* @param {string} actualStandard - The resolved standard (iec, jedec)
* @param {boolean} bits - Whether formatting bit values
* @param {number} e - Current exponent index
* @param {boolean} isDecimal - Whether using decimal (SI) base
* @returns {string} The resolved unit symbol string
*/
function resolveSymbol(actualStandard, bits, e, isDecimal) {
const symbolTable = STRINGS.symbol[actualStandard][bits ? BITS : BYTES];
return isDecimal && e === 1 ? (bits ? SI_KBIT : SI_KBYTE) : symbolTable[e];
}

/**
* Decorates the result: applies negation, custom symbols, number formatting, and full form names
* Mutates the result array in-place for both value (index 0) and symbol (index 1)
* @param {Array} result - Result array with numeric value at [0] and string symbol at [1]
* @param {boolean} neg - Whether the original input was negative
* @param {Object} symbols - Custom symbol override map
* @param {string|boolean} locale - Locale string for formatting
* @param {Object} localeOptions - Additional locale formatting options
* @param {string} separator - Custom decimal separator
* @param {boolean} pad - Whether zero-pad decimals
* @param {number} round - Target decimal count for padding
* @param {boolean} full - Whether to use full unit names
* @param {Array} fullforms - Custom full unit name overrides
* @param {string} actualStandard - Unit standard for full form lookup
* @param {number} e - Current exponent index
* @param {boolean} bits - Whether formatting bit values
* @returns {void} Mutates result array in place
*/
function decorateResult(
result,
neg,
symbols,
locale,
localeOptions,
separator,
pad,
round,
full,
fullforms,
actualStandard,
e,
bits,
) {
if (neg) {
result[0] = -result[0];
}

if (symbols[result[1]]) {
result[1] = symbols[result[1]];
}

result[0] = applyNumberFormatting(result[0], locale, localeOptions, separator, pad, round);

if (full) {
result[1] =
fullforms[e] ||
STRINGS.fullform[actualStandard][e] + (bits ? BIT : BYTE) + (result[0] === 1 ? EMPTY : S);
}
}

/**
* Formats the computed result array into the requested output type
* @param {Array} result - Result array with formatted value at [0] and symbol at [1]
* @param {number} e - Current exponent
* @param {string} u - Original resolved symbol (before custom override)
* @param {number} output - Output type (ARRAY, OBJECT, STRING)
* @param {string} spacer - String separator between value and unit
* @returns {string|Array|Object|number} Formatted result in requested type
*/
function formatOutput(result, e, u, output, spacer) {
if (output === ARRAY) {
return result;
}

if (output === OBJECT) {
return {
value: result[0],
symbol: result[1],
exponent: e,
unit: u,
};
}

return spacer === SPACE ? `${result[0]} ${result[1]}` : result.join(spacer);
}

/**
* Converts a file size in bytes to a human-readable string with appropriate units
* @param {number|string|bigint} arg - The file size in bytes to convert
Expand Down Expand Up @@ -337,7 +479,6 @@ function filesize(
val = 0,
u = EMPTY;

// Optimized base & standard configuration lookup
const { isDecimal, ceil, actualStandard } = getBaseConfiguration(standard, base);

const full = fullform === true,
Expand All @@ -352,12 +493,10 @@ function filesize(
throw new TypeError(INVALID_ROUND);
}

// Flipping a negative number to determine the size
if (neg) {
num = -num;
}

// Fast path for zero
if (num === 0) {
return handleZeroValue(
precision,
Expand All @@ -371,31 +510,21 @@ function filesize(
);
}

// Optimized exponent calculation using pre-computed log values
if (e === -1 || isNaN(e)) {
e = isDecimal
? Math.floor(Math.log(num) / LOG_10_1000)
: Math.floor(Math.log(num) / LOG_2_1024);
if (e < 0) {
e = 0;
}
}

// Exceeding supported length, time to reduce & multiply
if (e > 8) {
if (precision > 0) {
precision += 8 - e;
}
e = 8;
}

// Exponent calculation + clamp + precision adjustment
const { e: calculatedE, precision: precisionAdjusted } = calculateExponent(
num,
e,
exponent,
isDecimal,
precision,
);
e = calculatedE;
const autoExponent = exponent === -1 || isNaN(exponent);

if (output === EXPONENT) {
return e;
}

// Calculate value with optimized lookup and bits handling
const { result: valueResult, e: valueExponent } = calculateOptimizedValue(
num,
e,
Expand All @@ -407,20 +536,16 @@ function filesize(
val = valueResult;
e = valueExponent;

// Optimize rounding calculation
const p = e > 0 && round > 0 ? Math.pow(10, round) : 1;
result[0] = p === 1 ? roundingFunc(val) : roundingFunc(val * p) / p;

if (result[0] === ceil && e < 8 && autoExponent) {
result[0] = 1;
e++;
}
// Rounding + auto-increment ceiling
const rounded = applyRounding(val, ceil, e, round, roundingFunc, autoExponent);
result[0] = rounded.value;
e = rounded.e;

// Apply precision handling
if (precision > 0) {
// Precision handling
if (precisionAdjusted > 0) {
const precisionResult = applyPrecisionHandling(
result[0],
precision,
precisionAdjusted,
e,
num,
isDecimal,
Expand All @@ -434,44 +559,26 @@ function filesize(
e = precisionResult.e;
}

// Cache symbol lookup
const symbolTable = STRINGS.symbol[actualStandard][bits ? BITS : BYTES];
u = result[1] = isDecimal && e === 1 ? (bits ? SI_KBIT : SI_KBYTE) : symbolTable[e];

// Decorating a 'diff'
if (neg) {
result[0] = -result[0];
}

// Applying custom symbol
if (symbols[result[1]]) {
result[1] = symbols[result[1]];
}

// Apply locale, separator, and padding formatting
result[0] = applyNumberFormatting(result[0], locale, localeOptions, separator, pad, round);

if (full) {
result[1] =
fullforms[e] ||
STRINGS.fullform[actualStandard][e] + (bits ? BIT : BYTE) + (result[0] === 1 ? EMPTY : S);
}

// Optimized return logic
if (output === ARRAY) {
return result;
}

if (output === OBJECT) {
return {
value: result[0],
symbol: result[1],
exponent: e,
unit: u,
};
}
u = resolveSymbol(actualStandard, bits, e, isDecimal);
result[1] = u;

decorateResult(
result,
neg,
symbols,
locale,
localeOptions,
separator,
pad,
round,
full,
fullforms,
actualStandard,
e,
bits,
);

return spacer === SPACE ? `${result[0]} ${result[1]}` : result.join(spacer);
return formatOutput(result, e, u, output, spacer);
}

/**
Expand Down
Loading
Loading