如何实现支持任意长度输入的reinterpret跨字母表转换函数?
解决大数字跨进制转换的精度丢失问题
核心思路
JavaScript的Number类型仅能精确表示到2^53-1,处理超长数字字符串时必然出现精度丢失。要彻底规避这个问题,我们需要用字符串模拟大数运算,完全绕过Number类型的限制,手动实现进制转换的核心逻辑:逐位累加构建大数,再通过取余和除法转换到目标进制。
实现步骤与代码
先实现几个关键的大数运算辅助函数,再基于这些函数完成reinterpret:
1. 辅助函数:字符串大数乘以整数
function multiplyBigNumber(numStr, multiplier) { let result = []; let carry = 0; // 从低位到高位遍历计算 for (let i = numStr.length - 1; i >= 0; i--) { const digit = parseInt(numStr[i]); const product = digit * multiplier + carry; result.push(product % 10); carry = Math.floor(product / 10); } // 处理剩余进位 while (carry > 0) { result.push(carry % 10); carry = Math.floor(carry / 10); } // 反转得到正确顺序的字符串 return result.reverse().join(''); }
2. 辅助函数:字符串大数除以整数,返回商和余数
function divideBigNumber(numStr, divisor) { let quotient = []; let remainder = 0; for (const char of numStr) { const digit = parseInt(char); const current = remainder * 10 + digit; quotient.push(Math.floor(current / divisor)); remainder = current % divisor; } // 去掉前缀无效0 while (quotient[0] === 0 && quotient.length > 1) { quotient.shift(); } return { quotient: quotient.join(''), remainder }; }
3. 辅助函数:字符串大数加整数
function addBigNumber(numStr, addend) { let result = []; let carry = addend; for (let i = numStr.length - 1; i >= 0 || carry > 0; i--) { const digit = i >= 0 ? parseInt(numStr[i]) : 0; const sum = digit + carry; result.push(sum % 10); carry = Math.floor(sum / 10); } return result.reverse().join(''); }
4. 核心reinterpret函数
function reinterpret(sourceStr, sourceAlphabet, targetAlphabet) { // 处理空输入或全0的特殊情况 if (!sourceStr || sourceStr.split('').every(c => c === sourceAlphabet[0])) { return targetAlphabet[0]; } // 建立源字母表的字符-数值映射 const sourceCharMap = {}; for (let i = 0; i < sourceAlphabet.length; i++) { sourceCharMap[sourceAlphabet[i]] = i; } const sourceBase = sourceAlphabet.length; let bigNum = '0'; // 第一步:将源字符串转换为十进制大数(字符串形式) for (const char of sourceStr) { const value = sourceCharMap[char]; // 大数乘源进制基数 bigNum = multiplyBigNumber(bigNum, sourceBase); // 加上当前位的数值 bigNum = addBigNumber(bigNum, value); } // 建立目标字母表的数值-字符映射 const targetBase = targetAlphabet.length; const targetCharMap = {}; for (let i = 0; i < targetAlphabet.length; i++) { targetCharMap[i] = targetAlphabet[i]; } const resultChars = []; // 第二步:将十进制大数转换为目标进制字符串 while (bigNum !== '0') { const { quotient, remainder } = divideBigNumber(bigNum, targetBase); resultChars.push(targetCharMap[remainder]); bigNum = quotient; } // 反转得到正确顺序的结果 return resultChars.reverse().join(''); }
测试验证
用题目中的两个大数字测试(转十六进制为例):
const sourceAlphabet = '0123456789'; const targetAlphabet = '0123456789abcdef'; const str1 = '87771287589933381'; const str2 = '87771287589933382'; console.log(reinterpret(str1, sourceAlphabet, targetAlphabet)); // 输出: 13e968b45a0855 console.log(reinterpret(str2, sourceAlphabet, targetAlphabet)); // 输出: 13e968b45a0856
两个不同输入得到了不同的结果,没有出现精度丢失。
内容的提问来源于stack exchange,提问作者Gershom Maes
相关产品推荐
相关产品推荐

