如何在JavaScript中实现支持精确有理数的Scheme floor/ceil/round函数?
解决Scheme解释器中精确有理数取整返回BigInt的问题
我在Scheme中测试以下两个表达式:
(quotient (expt 1000 999) 998001) (floor (/ (expt 1000 999) 998001))
它们本该返回形如100200300 ... 999的BigInt(包含除998外的所有数字),但我用JavaScript开发的Scheme解释器却返回+inf.0——原因是计算时把精确数值转成了不精确的浮点数,超大整数直接溢出成无穷大。
当前实现的核心问题
现有floor/ceil/round直接调用JavaScript的Math方法,而有理数的valueOf()会返回浮点数。对于1000^999这种远超浮点数精度范围的整数,转浮点数后会丢失精度甚至溢出,导致取整结果错误。
修改方案
1. 给LRational实现精确取整方法
放弃浮点数转换,用纯整数运算处理有理数的取整,直接基于分子和分母的BigInt计算:
// 为LRational重写floor/ceil/round,用精确整数运算实现 ['floor', 'ceil', 'round'].forEach(fn => { LRational.prototype[fn] = function() { // 假设内部__num__和__denom__存储的是可转为BigInt的整数 const numBig = BigInt(this.__num__.__value__); const denomBig = BigInt(this.__denom__.__value__); const isNegative = (numBig < 0n) !== (denomBig < 0n); const absNum = numBig < 0n ? -numBig : numBig; const absDenom = denomBig < 0n ? -denomBig : denomBig; let resultBig; switch(fn) { case 'floor': if (isNegative) { resultBig = -(absNum / absDenom); // 有余数时,负数floor需要额外减1 if (absNum % absDenom !== 0n) resultBig -= 1n; } else { resultBig = absNum / absDenom; } break; case 'ceil': if (isNegative) { resultBig = -(absNum / absDenom); } else { // 正数ceil = (分子 + 分母 -1) // 分母 resultBig = (absNum + absDenom - 1n) / absDenom; } break; case 'round': const halfDenom = absDenom / 2n; const remainder = absNum % absDenom; // 四舍五入逻辑:余数大于半分母,或等于半分母且分子为奇数时进1 const needRoundUp = remainder > halfDenom || (remainder === halfDenom && (absNum % 2n === 1n)); if (isNegative) { resultBig = -(absNum / absDenom); if (needRoundUp) resultBig -= 1n; } else { resultBig = absNum / absDenom; if (needRoundUp) resultBig += 1n; } break; } // 返回BigInt类型的LNumber实例 return LNumber(resultBig); }; });
2. 调整LNumber的取整方法分支逻辑
让精确整数/有理数走上面的精确运算分支,只有浮点数才用Math方法:
['floor', 'ceil', 'round'].forEach(fn => { LNumber.prototype[fn] = function() { if (this.__type__ === 'rational') { // 交给LRational的精确实现处理 return this[fn](); } else if (this.float || LNumber.isFloat(this.__value__)) { // 浮点数仍用Math方法 return LNumber(Math[fn](this.__value__)); } else { // 整数取整就是自身,直接返回 return this; } }; });
3. 优化quotient实现(可选)
避免先做除法生成有理数,直接用整数除法逻辑实现,更高效且精准:
(define (quotient a b) "(quotient a b) Return quotient from division as integer." (typecheck "quotient" a "number") (typecheck "quotient" b "number") (if (zero? b 0) (throw (new Error "quotient: division by zero")) (let ((sign (if (or (and (> a 0) (> b 0)) (and (< a 0) (< b 0))) 1 -1)) (abs-a (abs a)) (abs-b (abs b))) (* sign (floor (/ abs-a abs-b))))))
这样修改后,处理超大整数的有理数取整时,会全程用BigInt做精确运算,不会出现浮点数溢出的问题,就能得到正确的BigInt结果。
内容的提问来源于stack exchange,提问作者jcubic
相关产品推荐
相关产品推荐

