Skip to content

Commit b1b89a6

Browse files
committed
fix: 进制转换大数字转换异常
1 parent af30c96 commit b1b89a6

3 files changed

Lines changed: 542 additions & 116 deletions

File tree

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"i18n-ally.localesPaths": [
3+
"lang"
4+
]
5+
}

libs/radixc.js

Lines changed: 237 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,259 @@
11
"use strict";
22

3-
const BigInt = require('bigi');
3+
/**
4+
* 验证数字字符串是否符合指定进制
5+
* @param {string} str - 要验证的字符串
6+
* @param {number} radix - 进制 (2-36)
7+
* @returns {boolean} 是否有效
8+
*/
9+
function isValidRadixString(str, radix) {
10+
if (!str || typeof str !== 'string') return false;
11+
12+
const validChars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'.slice(0, radix);
13+
const regex = new RegExp(`^[${validChars}]+$`, 'i');
14+
return regex.test(str);
15+
}
416

17+
/**
18+
* 手动将任意进制字符串转换为 BigInt,避免 parseInt 的精度限制
19+
* @param {string} str - 要转换的字符串
20+
* @param {number} radix - 源进制 (2-36)
21+
* @returns {bigint} 转换后的 BigInt 值
22+
*/
23+
function convertToBigIntFromRadix(str, radix) {
24+
if (!str || str.length === 0) {
25+
return 0n;
26+
}
27+
28+
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
29+
const radixBigInt = BigInt(radix);
30+
let result = 0n;
31+
32+
for (let i = 0; i < str.length; i++) {
33+
const char = str[i].toUpperCase();
34+
const digitValue = chars.indexOf(char);
35+
36+
if (digitValue === -1 || digitValue >= radix) {
37+
throw new Error(`Invalid character '${char}' for radix ${radix}`);
38+
}
39+
40+
result = result * radixBigInt + BigInt(digitValue);
41+
}
42+
43+
return result;
44+
}
45+
46+
/**
47+
* 进制转换函数,使用原生 BigInt 确保精度
48+
* @param {string|number|bigint} str - 要转换的数字
49+
* @param {number|string} from - 源进制 (2-36)
50+
* @param {number|string} to - 目标进制 (2-36)
51+
* @returns {string} 转换后的字符串
52+
* @throws {Error} 当输入无效时抛出错误
53+
*/
554
function radixc(str, from, to) {
6-
if (typeof str !== 'string') str = str + '';
7-
if (from === to) {
8-
return str.toUpperCase();
55+
// 转换参数类型
56+
const fromRadix = parseInt(from);
57+
const toRadix = parseInt(to);
58+
59+
// 验证进制范围
60+
if (fromRadix < 2 || fromRadix > 36 || toRadix < 2 || toRadix > 36) {
61+
throw new Error('Radix must be between 2 and 36');
62+
}
63+
64+
// 转换输入为字符串
65+
let inputStr = String(str).trim();
66+
if (!inputStr) {
67+
return '0';
968
}
10-
if (from === '10') {
11-
return BigInt(str).toString(to).toUpperCase();
69+
70+
// 处理负数
71+
const isNegative = inputStr.startsWith('-');
72+
if (isNegative) {
73+
inputStr = inputStr.slice(1);
1274
}
13-
if (to === '10') {
14-
return BigInt(str, from).toString().toUpperCase();
75+
76+
// 验证输入字符串是否符合源进制
77+
if (!isValidRadixString(inputStr, fromRadix)) {
78+
throw new Error(`Invalid characters for radix ${fromRadix}`);
79+
}
80+
81+
// 如果进制相同,直接返回标准化的字符串
82+
if (fromRadix === toRadix) {
83+
const result = inputStr.toUpperCase();
84+
return isNegative ? '-' + result : result;
85+
}
86+
87+
try {
88+
// 使用手动转换确保大数字的精度
89+
let bigIntValue;
90+
91+
if (fromRadix === 10) {
92+
// 十进制直接使用 BigInt
93+
bigIntValue = BigInt(inputStr);
94+
} else {
95+
// 对于非十进制,手动转换为避免 parseInt 的精度限制
96+
bigIntValue = convertToBigIntFromRadix(inputStr, fromRadix);
97+
}
98+
99+
// 处理负数
100+
if (isNegative) {
101+
bigIntValue = -bigIntValue;
102+
}
103+
104+
// 转换为目标进制
105+
const result = bigIntValue.toString(toRadix).toUpperCase();
106+
return result;
107+
} catch (error) {
108+
throw new Error(`Conversion failed: ${error.message}`);
15109
}
16-
return BigInt(str, from).toString(to).toUpperCase();
17110
}
18111

19112
/**
20-
* @returns {string} Human-readable string
21-
* @param {number} n Decimal number
113+
* 将十进制数字转换为可读格式 (使用 BigInt)
114+
* @param {string|number|bigint} n - 十进制数字
115+
* @returns {string} 可读格式字符串
22116
*/
23117
function decimal_to_readable(n) {
24-
// const u = "BKMGT";
25-
const u = ['B', 'KB', 'MB', 'GB', 'TB'];
26-
let t = "";
27-
let r = n;
28-
let i = 0;
29-
while (r > 0) {
30-
t = (r % 1024) + u[i] + ' ' + t;
31-
r = Math.trunc(r / 1024);
32-
i++;
118+
try {
119+
let value = BigInt(n);
120+
121+
if (value === 0n) {
122+
return '0B';
123+
}
124+
125+
const isNegative = value < 0n;
126+
if (isNegative) {
127+
value = -value;
128+
}
129+
130+
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
131+
const base = 1024n;
132+
const parts = [];
133+
134+
let remainder = value;
135+
136+
// 从最小单位开始处理
137+
for (let i = 0; i < units.length; i++) {
138+
if (remainder === 0n) break;
139+
140+
const currentUnit = remainder % base;
141+
if (currentUnit > 0n) {
142+
parts.unshift(`${currentUnit}${units[i]}`);
143+
}
144+
remainder = remainder / base;
145+
}
146+
147+
// 如果还有剩余,说明数值超出了最大单位的范围
148+
if (remainder > 0n) {
149+
// 计算完整的最大单位值
150+
const maxUnitIndex = units.length - 1;
151+
const maxUnitMultiplier = base ** BigInt(maxUnitIndex);
152+
const totalInMaxUnit = value / maxUnitMultiplier;
153+
const remainderInBytes = value % maxUnitMultiplier;
154+
155+
// 如果剩余部分为0,只显示最大单位
156+
if (remainderInBytes === 0n) {
157+
return isNegative ? `-${totalInMaxUnit}${units[maxUnitIndex]}` : `${totalInMaxUnit}${units[maxUnitIndex]}`;
158+
} else {
159+
// 否则显示最大单位加上剩余部分的可读格式
160+
const remainderReadable = decimal_to_readable(remainderInBytes.toString());
161+
return isNegative ? `-${totalInMaxUnit}${units[maxUnitIndex]} ${remainderReadable}` : `${totalInMaxUnit}${units[maxUnitIndex]} ${remainderReadable}`;
162+
}
163+
}
164+
165+
// 如果没有剩余,返回组合的结果
166+
if (parts.length === 0) {
167+
return '0B';
168+
}
169+
170+
const result = parts.join(' ');
171+
return isNegative ? `-${result}` : result;
172+
} catch (error) {
173+
throw new Error(`Failed to convert to readable format: ${error.message}`);
33174
}
34-
return t.trim();
35175
}
36176

37-
38177
/**
39-
* @returns {number|null} Decimal number
40-
* @param {string} s Human-readable string
41-
* similar to '12GB 34MB 56KB 78B','12GiB 34MiB 56KiB 78B','12G34M56K78B'
178+
* 将可读格式字符串转换为十进制数字 (返回 BigInt)
179+
* @param {string} s - 可读格式字符串
180+
* @returns {bigint} 十进制 BigInt 值
181+
* @throws {Error} 当格式无效时抛出错误
42182
*/
43183
function readable_to_decimal(s) {
44-
s = s.split(' ').join('').toUpperCase()
45-
.replaceAll('IB', '')
46-
.replaceAll('KB', 'K')
47-
.replaceAll('MB', 'M')
48-
.replaceAll('GB', 'G')
49-
.replaceAll('TB', 'T');
50-
let n = 0;
51-
let j = 0;
52-
for (let i = 0; i < s.length; i++) {
53-
switch (s[i]) {
54-
case "B":
55-
n += +s.slice(j, i);
56-
j = i + 1;
57-
break;
58-
case "K":
59-
n += +s.slice(j, i) * 1024;
60-
j = i + 1;
61-
break;
62-
case "M":
63-
n += +s.slice(j, i) * 1024 * 1024;
64-
j = i + 1;
65-
break;
66-
case "G":
67-
n += +s.slice(j, i) * 1024 * 1024 * 1024;
68-
j = i + 1;
69-
break;
70-
case "T":
71-
n += +s.slice(j, i) * 1024 * 1024 * 1024 * 1024;
72-
j = i + 1;
73-
break;
74-
default:
75-
// should we throw error here?
76-
// throw new Error('Unexpected character in readable string')
184+
if (!s || typeof s !== 'string') {
185+
throw new Error('Input must be a non-empty string');
186+
}
187+
188+
try {
189+
let input = s.trim();
190+
const isNegative = input.startsWith('-');
191+
if (isNegative) {
192+
input = input.slice(1);
193+
}
194+
195+
// 标准化输入:移除空格并统一单位格式
196+
input = input
197+
.split(/\s+/).join('')
198+
.toUpperCase()
199+
.replace(/IB/g, '') // 移除 IB 后缀 (KiB -> K)
200+
.replace(/KB/g, 'K')
201+
.replace(/MB/g, 'M')
202+
.replace(/GB/g, 'G')
203+
.replace(/TB/g, 'T')
204+
.replace(/PB/g, 'P')
205+
.replace(/EB/g, 'E')
206+
.replace(/ZB/g, 'Z')
207+
.replace(/YB/g, 'Y');
208+
209+
const unitMultipliers = {
210+
'B': 1n,
211+
'K': 1024n,
212+
'M': 1024n ** 2n,
213+
'G': 1024n ** 3n,
214+
'T': 1024n ** 4n,
215+
'P': 1024n ** 5n,
216+
'E': 1024n ** 6n,
217+
'Z': 1024n ** 7n,
218+
'Y': 1024n ** 8n
219+
};
220+
221+
let total = 0n;
222+
let currentNumber = '';
223+
224+
for (let i = 0; i < input.length; i++) {
225+
const char = input[i];
226+
227+
if (/\d/.test(char)) {
228+
currentNumber += char;
229+
} else if (unitMultipliers.hasOwnProperty(char)) {
230+
if (currentNumber === '') {
231+
throw new Error(`Invalid format: missing number before unit '${char}'`);
232+
}
233+
234+
const value = BigInt(currentNumber);
235+
const multiplier = unitMultipliers[char];
236+
total += value * multiplier;
237+
currentNumber = '';
238+
} else {
239+
throw new Error(`Invalid character '${char}' in readable format`);
240+
}
241+
}
242+
243+
// 如果最后还有数字但没有单位,按字节处理
244+
if (currentNumber !== '') {
245+
total += BigInt(currentNumber);
77246
}
247+
248+
return isNegative ? -total : total;
249+
} catch (error) {
250+
throw new Error(`Failed to parse readable format: ${error.message}`);
78251
}
79-
return n;
80252
}
81253

82-
module.exports = {radixc, decimal_to_readable, readable_to_decimal};
254+
module.exports = {
255+
radixc,
256+
decimal_to_readable,
257+
readable_to_decimal,
258+
isValidRadixString
259+
};

0 commit comments

Comments
 (0)