You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 00:24:50