如何在JavaScript中实现大数字分数无精度损失运算?
在JavaScript中实现无精度损失的数学运算
问题背景
需要实现无精度损失的数学运算,但尝试多款常用库后均遇到问题:
- decimal.js:无法将无限循环小数转换为最简分数形式,返回近似的十进制分数
- fraction.js:错误转换结果类型导致大数字运算丢失精度
- mathjs:底层依赖fraction.js,同样存在大数字处理缺陷
各库问题复现
decimal.js的分数精度问题
import {Decimal} from 'decimal.js'; const a1 = new Decimal(1); const a2 = new Decimal(3); console.log(a1.div(a2).toFraction()); // 预期为`1/3`,实际得到`33333333333333333333/100000000000000000000`
fraction.js的大数字处理误区
import Fraction from 'fraction.js'; const n1 = new Fraction("99999999999999999999999999999999999999999999999"); const n2 = new Fraction("99999999999999999999999999999999999999999999990"); console.log(+(n1.sub(n2))) // 预期为9,实际得到0
mathjs的大数字运算缺陷
import * as math from "mathjs"; const a1 = math.fraction(math.number("99999999999999999999999999999999999999999999999")) const a2 = math.fraction(math.number("99999999999999999999999999999999999999999999990")) console.log(+(math.subtract(a1, a2))) // 预期为9,实际得到0
解决方案
1. 正确使用fraction.js处理大数字
fraction.js本身支持任意精度的大数字运算,问题出在将结果转换为Number类型——JavaScript的Number基于双精度浮点数,仅能精确表示小于2^53的整数,超出范围会丢失精度。直接使用fraction.js的API获取结果即可:
import Fraction from 'fraction.js'; const n1 = new Fraction("99999999999999999999999999999999999999999999999"); const n2 = new Fraction("99999999999999999999999999999999999999999999990"); const result = n1.sub(n2); console.log(result.toString()); // 输出 "9" console.log(result.numerator); // 输出 9(BigInt类型)
2. 优化decimal.js的分数输出
decimal.js的toFraction方法支持传入参数识别循环小数的最简分数,通过配置精度并启用循环小数检测即可:
import {Decimal} from 'decimal.js'; // 配置decimal.js以识别循环小数并输出最简分数 Decimal.set({precision: 20, rounding: Decimal.ROUND_HALF_UP}); const a1 = new Decimal(1); const a2 = new Decimal(3); console.log(a1.div(a2).toFraction(true)); // 输出 "1/3"
toFraction(true)会自动检测循环模式,将无限循环小数转换为最简分数形式。
3. 自定义基于BigInt的精确分数类
如果需要完全自主控制运算逻辑,可以基于BigInt实现分数运算,确保所有操作都在整数范围内进行,彻底避免精度损失:
class PreciseFraction { constructor(numerator, denominator = 1) { this.numerator = BigInt(numerator); this.denominator = BigInt(denominator); this.simplify(); } // 约分处理 simplify() { const gcd = this.#gcd(this.numerator, this.denominator); this.numerator /= gcd; this.denominator /= gcd; // 确保分母为正 if (this.denominator < 0n) { this.numerator *= -1n; this.denominator *= -1n; } } // 计算最大公约数 #gcd(a, b) { a = a < 0n ? -a : a; b = b < 0n ? -b : b; while (b !== 0n) { [a, b] = [b, a % b]; } return a; } // 减法运算 subtract(other) { const newNumerator = this.numerator * other.denominator - other.numerator * this.denominator; const newDenominator = this.denominator * other.denominator; return new PreciseFraction(newNumerator, newDenominator); } // 除法运算 divide(other) { return new PreciseFraction(this.numerator * other.denominator, this.denominator * other.numerator); } // 转为字符串输出 toString() { return this.denominator === 1n ? this.numerator.toString() : `${this.numerator}/${this.denominator}`; } } // 测试分数除法 const frac1 = new PreciseFraction(1); const frac2 = new PreciseFraction(3); console.log(frac1.divide(frac2).toString()); // 输出 "1/3" // 测试大数字减法 const bigFrac1 = new PreciseFraction("99999999999999999999999999999999999999999999999"); const bigFrac2 = new PreciseFraction("99999999999999999999999999999999999999999999990"); console.log(bigFrac1.subtract(bigFrac2).toString()); // 输出 "9"
内容的提问来源于stack exchange,提问作者valerii15298
相关产品推荐
相关产品推荐

