-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathparser.ts
More file actions
109 lines (91 loc) · 3.03 KB
/
parser.ts
File metadata and controls
109 lines (91 loc) · 3.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// src/lib/parser.ts
//
// Functions: parseBytes, formatBytes
const BINARY_UNITS: Record<string, bigint> = {
B: BigInt(1),
KiB: BigInt(1024),
MiB: BigInt(1024 ** 2),
GiB: BigInt(1024 ** 3),
TiB: BigInt(2) ** BigInt(40),
}
const DECIMAL_UNITS: Record<string, bigint> = {
KB: BigInt(1000),
MB: BigInt(1000 ** 2),
GB: BigInt(1000 ** 3),
TB: BigInt(1000 ** 4),
}
/**
* Multiplies a decimal string value by a bigint multiplier using exact bigint
* arithmetic to avoid floating-point precision loss. The value is decomposed
* into its integer and fractional parts, each multiplied by the multiplier,
* and then combined.
*/
function multiplyDecimalStringByBigInt(valueStr: string, multiplier: bigint): bigint {
const dotIndex = valueStr.indexOf(".")
if (dotIndex === -1) {
return BigInt(valueStr) * multiplier
}
const intPart = valueStr.slice(0, dotIndex)
const fracPart = valueStr.slice(dotIndex + 1)
const fracLen = fracPart.length
// Scale factor: 10^fracLen
const scale = BigInt(10) ** BigInt(fracLen)
// Combine into a single scaled integer: intPart * scale + fracPart
const scaledValue = BigInt(intPart) * scale + BigInt(fracPart)
// Multiply by the unit multiplier, then divide by scale (rounding)
const raw = scaledValue * multiplier
const quotient = raw / scale
const remainder = raw % scale
// Round half-up
if (remainder * BigInt(2) >= scale) {
return quotient + BigInt(1)
}
return quotient
}
export function parseBytes(formatted: string): bigint {
const trimmed = formatted.trim()
const match = trimmed.match(/^([\d.]+)\s*([A-Za-z]+)$/)
if (!match) {
throw new Error(`Invalid byte format: "${formatted}"`)
}
const valueStr = match[1]
const unit = match[2]
// Reject negative values (the regex already excludes the minus sign, but be
// explicit in case the input bypasses the regex somehow)
if (valueStr.startsWith("-")) {
throw new Error(`Negative byte values are not allowed: "${formatted}"`)
}
const binaryMultiplier = BINARY_UNITS[unit]
if (binaryMultiplier !== undefined) {
return multiplyDecimalStringByBigInt(valueStr, binaryMultiplier)
}
const decimalMultiplier = DECIMAL_UNITS[unit]
if (decimalMultiplier !== undefined) {
return multiplyDecimalStringByBigInt(valueStr, decimalMultiplier)
}
throw new Error(`Unknown unit: "${unit}"`)
}
const FORMAT_THRESHOLDS: Array<{
unit: string
threshold: bigint
divisor: number
}> = [
{
unit: "TiB",
threshold: BigInt(2) ** BigInt(40),
divisor: Number(BigInt(2) ** BigInt(40)),
},
{ unit: "GiB", threshold: BigInt(1024 ** 3), divisor: 1024 ** 3 },
{ unit: "MiB", threshold: BigInt(1024 ** 2), divisor: 1024 ** 2 },
{ unit: "KiB", threshold: BigInt(1024), divisor: 1024 },
]
export function formatBytes(bytes: bigint): string {
if (bytes === BigInt(0)) return "0 B"
for (const { unit, threshold, divisor } of FORMAT_THRESHOLDS) {
if (bytes >= threshold) {
const value = Number(bytes) / divisor
return `${value.toFixed(2)} ${unit}`
}
}
return `${bytes} B`
}