尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

effect 科学计数法数值修复解析:Number.remainder 对极值与超精度小数余数计算的安全实现

effect 科学计数法数值修复解析:Number.remainder 对极值与超精度小数余数计算的安全实现 effect 科学计数法数值修复解析Number.remainder 对极值与超精度小数余数计算的安全实现【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code本文围绕 effecteffect-smol 仓库中一次针对Number.remainder的补丁级修复展开深入解析该函数在处理科学计数法表示的极小值、极大值、超精度小数与 subnormal 数值时的取余实现原理并结合源码与测试用例说明修复前后的行为差异。读者读完可掌握 effectNumber.remainder的完整语义符号规则、除零与非有限值处理、data-first/data-last 两种调用方式以及一套可复用的十进制科学计数法 → BigInt 整数取模 → 还原小数位的稳健数值算法。修复背景JavaScript%运算符在余数计算上的精度缺陷JavaScript 的原生取余运算符%直接作用于 IEEE 754 双精度浮点数对看似整除的小数场景经常给出错误结果。例如0.3 % 0.2 // 期望 0.1实际得到 0.09999999999999998 1e21 % 3 // 1e21 已超出 Number 的安全整数范围2^53 ≈ 9.007e15 // 参与取模时大数精度已丢失结果不可靠这正是 effect 提供Number.remainder的原因。在 Number.remainder 的 JSDoc 中明确写道Use to compute a numeric remainder while preserving decimal precision better than direct JavaScript%for decimal operands.计算余数时比直接使用 JS%更好地保留十进制精度。但该实现历史上并非一开始就覆盖所有边界。本次补丁effect: patch要修复的正是此前版本在科学计数法格式的极小值与极大值上计算错误的问题变更说明原文为FixNumber.remainderfor very small and large values formatted in scientific notation.该补丁由 .changeset/pre/fix-number-remainder-scientific-notation.md 记录并出现在 effect 包的 CHANGELOG 中PR #2605感谢 gcanti。Number.remainder基础语义与两种调用形式Number.remainder通过dual(2, ...)同时支持>import { Number, pipe } from effect //>export const remainder: { (divisor: number): (self: number) number (self: number, divisor: number): number } dual(2, (self: number, divisor: number): number { const selfString self.toString() const divisorString divisor.toString() if (selfString.includes(e) || divisorString.includes(e)) { if (!globalThis.Number.isFinite(self) || !globalThis.Number.isFinite(divisor) || divisor 0) { return NaN } return remainderWithScientificNotation(self, divisor) } const selfDecCount (selfString.split(.)[1] || ).length const divisorDecCount (divisorString.split(.)[1] || ).length const decCount selfDecCount divisorDecCount ? selfDecCount : divisorDecCount const selfInt parseInt(self.toFixed(decCount).replace(., )) const divisorInt parseInt(divisor.toFixed(decCount).replace(., )) return (selfInt % divisorInt) / Math.pow(10, decCount) })路径一普通十进制数非科学计数法当两个操作数toString()后都不含e即都在常规十进制表示范围内分别统计两个数的小数位数取较大者作为统一精度decCount用toFixed(decCount)补齐小数位再删除小数点得到两个整数化字符串用parseInt转整数后做%取模最后除以10^decCount把结果还原到原来的小数位。例如0.3 % 0.2decCount 1整数化为3 % 2 1再1 / 10 0.1得到精确的0.1而不是浮点噪声。这就是该 API 对比原生%的核心优势。路径二科学计数法本次修复的核心一旦检测到任一操作数toString()含e立即进入专门函数remainderWithScientificNotationsrc/Number.ts#L649-L661其配套工具toScientificIntegersrc/Number.ts#L663-L669负责把科学计数法数字无损转换为 BigInt 系数与指数function remainderWithScientificNotation(self: number, divisor: number): number { const [selfCoefficient, selfExponent] toScientificInteger(self) const [divisorCoefficient, divisorExponent] toScientificInteger(divisor) const exponent Math.min(selfExponent, divisorExponent) const selfInteger selfCoefficient * BigInt(10) ** BigInt(selfExponent - exponent) const divisorInteger divisorCoefficient * BigInt(10) ** BigInt(divisorExponent - exponent) const out selfInteger % divisorInteger if (out BigInt(0)) { return self 0 || Object.is(self, -0) ? -0 : 0 } const remainder globalThis.Number(${out}e${exponent}) return remainder 0 ? Math.sign(self) * globalThis.Number.MIN_VALUE : remainder } function toScientificInteger(n: number): readonly [coefficient: bigint, exponent: number] { const scientific Math.abs(n).toExponential() const eIndex scientific.indexOf(e) const digits scientific.slice(0, eIndex).replace(., ) const coefficient BigInt(digits) * (n 0 ? -BigInt(1) : BigInt(1)) return [coefficient, globalThis.Number(scientific.slice(eIndex 1)) - digits.length 1] }整个算法分为四步科学计数法无损分解toScientificInteger借助Number.prototype.toExponential()把任意数字规整为d.dddde±xx形式去掉小数点得到纯数字字符串后转为BigInt系数并用指数 − 有效数字位数 1还原真实的数量级指数。由于 BigInt 可以表示任意精度整数1e-101、1e21乃至 subnormal 级别的小数都能被精确捕获不再受toFixed的 100 位上限与双精度整数安全范围限制。对齐数量级后转 BigInt 整数取两个指数中的较小者exponent作为公共基准通过BigInt(10) ** BigInt(差值)把两个操作数各自放大为同数量级的大整数。此处的对齐完全在 BigInt 域内进行精度无损。BigInt 取模selfInteger % divisorInteger得到精确的整数余数。若余数为0按语义返回0或-0当被除数为负数或-0时保持 dividend sign。还原小数位并保护 subnormal 边界用模板字符串${out}e${exponent}把 BigInt 余数重新组装回科学计数法字符串交给Number()解析为浮点数。最后一行remainder 0 ? Math.sign(self) * Number.MIN_VALUE : remainder是一处精心设计的兜底当真实余数小到被 IEEE 754 舍入为 0 时非零 subnormal 余数退而返回带符号的Number.MIN_VALUE约 5e-324保证非零余数这一事实在结果中仍然可见而不是静默丢失成 0。测试用例修复边界的完整证据Number.remainder的修复边界在 packages/effect/test/Number.test.ts 中有逐条断言可直接作为行为契约常规小数N.remainder(0.3, 0.2) 0.1data-first 与>--- effect: patch --- Fix Number.remainder for very small and large values formatted in scientific notation.effect: patch表示该变更对 effect 主包做补丁级不破坏 API发布该文件位于.changeset/pre/目录属于预发布批次合并发布后即进入正式的 CHANGELOG。这也是理解 effect 仓库发布流程的一个典型样例每个行为修正先以独立 changeset 文件沉淀变更说明再在发版时归并进 CHANGELOG。使用建议与注意事项优先使用Number.remainder而非原生%只要涉及十进制小数的余数、或操作数可能以科学计数法呈现极小/极大的物理量、时间戳差值、货币换算等Number.remainder都能给出比%更符合十进制直觉的结果。符号语义以被除数为准结果始终跟随被除数符号且保留-0判断时若需区分正负零请使用Object.is或1 / value -Infinity。除零与非有限输入返回NaN调用前无需自行预判除零函数统一返回NaN可配合Number.isNaN做防御。subnormal 兜底语义当真实余数小于Number.MIN_VALUE时函数返回Math.sign(self) * Number.MIN_VALUE以保留非零信息接收方应意识到这是最小正数的近似而非精确值。源码研读入口实现见 packages/effect/src/Number.ts#L606-L669行为契约见 packages/effect/test/Number.test.ts#L285-L372补丁记录见 .changeset/pre/fix-number-remainder-scientific-notation.md三处对照阅读即可完整还原本次修复的来龙去脉。【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表