+12








fcd7c979df
Co-authored-by: Artur <145776561+arturict@users.noreply.github.com> Co-authored-by: arturict <arturict@users.noreply.github.com> Co-authored-by: Thomas <thomas@ajnart.fr> Co-authored-by: Afonso Jorge Ramos <afonsojorgeramos@gmail.com> Co-authored-by: Danura30082 <danura30082+github@gmail.com> Co-authored-by: Kushie <122986913+kushiemoon-dev@users.noreply.github.com> Co-authored-by: ajnart <thomas@ajnart.fr> Co-authored-by: ajnart <thomas@ajnart.dev> Co-authored-by: chenjun <chenjun@foxmail.com> Co-authored-by: Wikibear <wikibear@users.noreply.github.com> Co-authored-by: Bergasha <bergasha@gmail.com> Co-authored-by: leafyelin <43306196+leafyelin@users.noreply.github.com> Co-authored-by: Lucas Araujo <lucas.lra@gmail.com> Co-authored-by: GreenMonito <86085570+GreenMonito@users.noreply.github.com> Co-authored-by: Nerdy-Technician <roffo@nerdy-technician.social> Co-authored-by: Jun <34545896+jcstrong@users.noreply.github.com> Co-authored-by: Edmond Abraham <54119952+gitedmond@users.noreply.github.com> Co-authored-by: thrawnx <tight.bulb9384@fastmail.com> Co-authored-by: Chris <58918656+nordada@users.noreply.github.com> Co-authored-by: yi111 <153097222+Yi-111-a@users.noreply.github.com> Co-authored-by: Yanis <naamaneyanis@gmail.com> Co-authored-by: Wikibear <me@sej.de> Co-authored-by: alessiottavo <93441886+alessiottavo@users.noreply.github.com>
152 lines
5.5 KiB
TypeScript
152 lines
5.5 KiB
TypeScript
const ranges = [
|
|
{ divider: 1e18, suffix: "E" },
|
|
{ divider: 1e15, suffix: "P" },
|
|
{ divider: 1e12, suffix: "T" },
|
|
{ divider: 1e9, suffix: "G" },
|
|
{ divider: 1e6, suffix: "M" },
|
|
{ divider: 1e3, suffix: "k" },
|
|
];
|
|
|
|
export const formatNumber = (value: number, decimalPlaces: number) => {
|
|
for (const range of ranges) {
|
|
if (value < range.divider) continue;
|
|
|
|
return (value / range.divider).toFixed(decimalPlaces) + range.suffix;
|
|
}
|
|
return value.toFixed(decimalPlaces);
|
|
};
|
|
|
|
const BINARY_UNITS = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"] as const;
|
|
const DECIMAL_UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB"] as const;
|
|
|
|
export const defaultByteUnitSystem = "decimal" as const;
|
|
|
|
export type ByteUnitSystem = "binary" | "decimal";
|
|
|
|
export interface FormatBytesOptions {
|
|
/**
|
|
* Unit system to use. "binary" uses 1024 as the base and KiB/MiB/GiB/TiB suffixes,
|
|
* "decimal" uses 1000 as the base and KB/MB/GB/TB suffixes. Defaults to "decimal".
|
|
*/
|
|
unit?: ByteUnitSystem;
|
|
}
|
|
|
|
const pickUnitIndex = (bytes: number, base: number, lastIndex: number): number => {
|
|
if (!Number.isFinite(bytes) || bytes <= 0) return 0;
|
|
let index = 0;
|
|
let value = bytes;
|
|
while (value >= base && index < lastIndex) {
|
|
value /= base;
|
|
index++;
|
|
}
|
|
return index;
|
|
};
|
|
|
|
const pickRoundedUnitIndex = (value: number, base: number, lastIndex: number, fractionDigits: number): number => {
|
|
const index = pickUnitIndex(value, base, lastIndex);
|
|
if (index >= lastIndex) return index;
|
|
const scaled = value / base ** index;
|
|
return Number(scaled.toFixed(fractionDigits)) >= base ? index + 1 : index;
|
|
};
|
|
|
|
const sanitizeBytes = (bytes: number): number => (Number.isFinite(bytes) && bytes > 0 ? bytes : 0);
|
|
|
|
const resolveUnitConfig = (options: FormatBytesOptions) => {
|
|
const { unit = defaultByteUnitSystem } = options;
|
|
return {
|
|
units: unit === "binary" ? BINARY_UNITS : DECIMAL_UNITS,
|
|
base: unit === "binary" ? 1024 : 1000,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Format a byte value as a human-readable string with a unit suffix.
|
|
*
|
|
* Picks the largest unit that keeps the scaled value below the base. Use
|
|
* `formatBytesPair` when two related values (e.g. used and total) should
|
|
* share a common unit.
|
|
*
|
|
* @example
|
|
* formatBytes(0); // "0.0 B"
|
|
* formatBytes(1000); // "1.0 KB"
|
|
* formatBytes(985828802560); // "985.8 GB"
|
|
* formatBytes(985828802560, { unit: "binary" }); // "918.1 GiB"
|
|
*/
|
|
export const formatBytes = (bytes: number, options: FormatBytesOptions = {}): string => {
|
|
const { units, base } = resolveUnitConfig(options);
|
|
const safe = sanitizeBytes(bytes);
|
|
const index = pickRoundedUnitIndex(safe, base, units.length - 1, 1);
|
|
const scaled = safe / base ** index;
|
|
return `${scaled.toFixed(1)} ${units[index]}`;
|
|
};
|
|
|
|
/**
|
|
* Format two related byte values (typically `used` and `total`) with a shared
|
|
* unit. The unit is picked from the larger displayed value so the pair stays
|
|
* consistent without promoting both values before either crosses a boundary.
|
|
*
|
|
* @example
|
|
* formatBytesPair(985828802560, 2858736793190);
|
|
* // { used: "1.0 TB", total: "2.9 TB" }
|
|
*/
|
|
export const formatBytesPair = (
|
|
used: number,
|
|
total: number,
|
|
options: FormatBytesOptions = {},
|
|
): { used: string; total: string } => {
|
|
const { units, base } = resolveUnitConfig(options);
|
|
const safeUsed = sanitizeBytes(used);
|
|
const safeTotal = sanitizeBytes(total);
|
|
const index = pickRoundedUnitIndex(Math.max(safeUsed, safeTotal), base, units.length - 1, 1);
|
|
const suffix = units[index];
|
|
return {
|
|
used: `${(safeUsed / base ** index).toFixed(1)} ${suffix}`,
|
|
total: `${(safeTotal / base ** index).toFixed(1)} ${suffix}`,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Format a byte-per-second rate (e.g. network throughput) as a human-readable
|
|
* string with a `/s` suffix. The value is formatted with the same rules as
|
|
* `formatBytes` and a trailing `/s` is appended.
|
|
*
|
|
* @example
|
|
* formatByteRate(0); // "0.0 B/s"
|
|
* formatByteRate(1000); // "1.0 KB/s"
|
|
* formatByteRate(985828802560, { unit: "binary" }); // "918.1 GiB/s"
|
|
*/
|
|
export const formatByteRate = (bytes: number, options: FormatBytesOptions = {}): string =>
|
|
`${formatBytes(bytes, options)}/s`;
|
|
|
|
const BIT_RATE_UNITS = ["b/s", "Kb/s", "Mb/s", "Gb/s", "Tb/s", "Pb/s", "Eb/s"] as const;
|
|
|
|
export interface FormatBitRateOptions {
|
|
/** Maximum number of fractional digits. Trailing zeroes are omitted. Defaults to 1. */
|
|
maximumFractionDigits?: number;
|
|
}
|
|
|
|
/**
|
|
* Format a bit-per-second rate using decimal SI units.
|
|
*
|
|
* Inputs are always bits per second. Callers whose upstream contract uses
|
|
* kilobits must normalize explicitly before calling this function.
|
|
*
|
|
* @example
|
|
* formatBitRate(999); // "999 b/s"
|
|
* formatBitRate(1_000); // "1 Kb/s"
|
|
* formatBitRate(1_500_000); // "1.5 Mb/s"
|
|
*/
|
|
export const formatBitRate = (bitsPerSecond: number, options: FormatBitRateOptions = {}): string => {
|
|
const safeRate = Number.isFinite(bitsPerSecond) && bitsPerSecond > 0 ? bitsPerSecond : 0;
|
|
const requestedDigits = options.maximumFractionDigits ?? 1;
|
|
const maximumFractionDigits = Math.max(0, Math.min(20, Math.trunc(requestedDigits)));
|
|
const index = pickRoundedUnitIndex(safeRate, 1000, BIT_RATE_UNITS.length - 1, maximumFractionDigits);
|
|
const scaled = safeRate / 1000 ** index;
|
|
const formatted = Number(scaled.toFixed(maximumFractionDigits)).toString();
|
|
return `${formatted} ${BIT_RATE_UNITS[index]}`;
|
|
};
|
|
|
|
const IMPERIAL_MULTIPLIER = 1.609344;
|
|
|
|
export const metricToImperial = (metricValue: number) => metricValue / IMPERIAL_MULTIPLIER;
|