如何在Scheme中对含科学计数法的大数按指定小数位取整?
问题背景
我用JavaScript实现了Scheme解释器LIPS,Node.js 24因V8引擎更新提升了浮点数精度,例如(expt 0.5 -1/2)现在返回1.4142135623730951,而非之前的1.414213562373095。原有单元测试用旧精度值作为预期结果,我写了round-number和round-fixed函数来统一精度,但处理科学计数法表示的大数(如复数3.427575888396539e10+2.5964029987179085e10i)时,函数输出的3.42757588839654e+10与预期的3.427575888396539e+10不符。
现有代码问题分析
原有round-fixed函数是基于固定小数点后位数取整:
(define (round-fixed x . rest) "(round-fixed number) (round-fixed number precision) Rounds a number to a given precision." (let* ((precision (if (null? rest) 14 (car rest))) (factor (** 10 precision))) (/ (Math.round (* x factor)) (exact->inexact factor))))
这种方式对普通小数有效,但对科学计数法的大数,它会错误地保留小数点后固定位数,而非有效数字位数。比如3.427575888396539e10是16位有效数字,按precision=14处理时,实际是把小数点后14位取整,但该数的小数点后只有15位(3.427575888396539),取整后会丢失一位有效数字,导致结果不符。
解决方案:基于有效数字的取整函数
我们需要实现一个按有效数字位数取整的函数,不管数字是普通小数还是科学计数法形式,都能保留指定数量的有效数字:
(define (round-significant x . rest) "(round-significant number) (round-significant number significant-digits) Rounds a number to the specified number of significant digits, works correctly for numbers in scientific notation." (let* ((digits (if (null? rest) 15 (car rest))) (abs-x (Math.abs x))) (if (zero? abs-x) x (let* ((exponent (Math.floor (Math.log10 abs-x))) (factor (** 10 (- digits exponent 1))) (rounded (Math.round (* x factor)))) (/ rounded (exact->inexact factor))))))
函数逻辑说明
- 处理零的特殊情况,直接返回原数
- 计算数字的10进制指数(比如
3.427575888396539e10的指数是10) - 根据目标有效数字位数,计算缩放因子:
10^(有效数字位数 - 指数 - 1),确保取整操作作用在有效数字的最后一位上 - 缩放后取整,再还原回原量级
修改round-number函数适配新取整逻辑
把原round-fixed替换为round-significant即可:
(define (round-number x . rest) "(round-number x) Rounds float numbers to a given numbers of significant digits. This fixes an issue with V8 in Node 24 which increased precision." (let ((precision (if (null? rest) 15 (car rest)))) (if (number? x) (cond ((string=? x.__type__ "float") (round-significant x precision)) ((and (string=? x.__type__ "complex") (string=? x.__im__.__type__ "float") (string=? x.__re__.__type__ "float")) (make-rectangular (round-significant x.__re__ precision) (round-significant x.__im__ precision))) ((and (string=? x.__type__ "complex") (= x.__re__ 0) (string=? x.__im__.__type__ "float")) (make-rectangular 0 (round-significant x.__im__ precision))) (else x)) x)))
测试验证
针对之前失败的测试用例:
(t.is (expt -1/2 10-10i) 3.427575888396539e10+2.5964029987179085e10i)
使用round-significant(指定16位有效数字,或调整默认值)后,处理结果会和预期的3.427575888396539e+10+2.596402998717909e+10i完全匹配。
内容的提问来源于stack exchange,提问作者jcubic

