es-toolkit BigInt 降序生成:rangeRight 用法、源码原理与边界行为全解析
【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit
es-toolkit 在es-toolkit/bigint子路径下提供了专门面向BigInt的数值工具集,rangeRight便是其中用于按降序生成BigInt序列的函数。本文以官方文档 docs/ja/reference/bigint/rangeRight.md 为骨架,结合 src/bigint/rangeRight.ts 源码与 src/bigint/rangeRight.spec.ts 测试,完整讲解它的三种调用形式、与range的镜像关系、step边界规则以及底层实现原理,帮助你安全处理超出Number.MAX_SAFE_INTEGER的降序区间枚举。
函数签名与三种调用形式
rangeRight与普通数值类型的rangeRight语义一致,但输入输出全部使用BigInt,因此可以精确表示任意大的整数区间。官方文档给出的类型签名如下:
const numbers = rangeRight(end); const numbers = rangeRight(start, end); const numbers = rangeRight(start, end, step);从源码 src/bigint/rangeRight.ts 可以看出,函数通过 TypeScript 重载声明了三种形态,最终合并为一个实现:
export function rangeRight(start: bigint, end?: bigint, step = 1n): bigint[] { if (end == null) { end = start; start = 0n; } if (step === 0n) { throw new Error('The step value must be a non-zero bigint.'); } const length = bigIntRangeLength(start, end, step); const result = new Array<bigint>(length); for (let i = 0; i < length; i++) { result[i] = start + BigInt(length - i - 1) * step; } return result; }注意一个易被忽略的设计:当只传入一个参数时,该参数被当作end,而start自动置为0n。这一点与range完全一致,也解释了为什么rangeRight(4n)得到的是[3n, 2n, 1n, 0n]而非包含4n的序列。
专属入口:为什么只能从 es-toolkit/bigint 导入
文档中特别注明:该函数仅能从es-toolkit/bigint独家导入,以避免与其他数值类型的同名函数产生潜在冲突。确实,es-toolkit 在es-toolkit/compat中还提供了面向Number的rangeRight(见 benchmarks/performance/rangeRight.bench.ts),两者重名但类型不同。因此使用时请务必显式从子路径导入:
import { rangeRight } from 'es-toolkit/bigint';它由 src/bigint/index.ts 统一导出,与range、clamp、inRange、sum等 BigInt 工具并列。
使用一:rangeRight(end) —— 从 end 前一位倒数到 0n
当你希望从end的紧邻前一个值开始,一路递减到0n(含)时,使用单参数形式。文档示例:
import { rangeRight } from 'es-toolkit/bigint'; console.log(rangeRight(4n)); // [3n, 2n, 1n, 0n] console.log(rangeRight(0n)); // []- 参数:
end(bigint)—— 范围的结束值,不包含在结果中。 - 返回值:
bigint[]—— 从end紧邻前一位递减到0n的BigInt数组。
当end为0n时,end紧邻前一位是-1n,低于起点0n,没有任何可生成的值,因此返回空数组。测试 src/bigint/rangeRight.spec.ts 对rangeRight(4n) === [3n, 2n, 1n, 0n]做了断言。
使用二:rangeRight(start, end) —— 倒数到任意起点
当你的降序区间终点不是0n时,传入两个参数指定区间。文档示例:
import { rangeRight } from 'es-toolkit/bigint'; console.log(rangeRight(2n, 5n)); // [4n, 3n, 2n] console.log(rangeRight(-3n, 0n)); // [-1n, -2n, -3n]- 参数:
start(bigint)—— 范围的起始值,包含在结果中(作为序列的最后一个元素);end(bigint)—— 范围的结束值,不包含在结果中(作为序列的第一个元素的前驱)。
- 返回值:
bigint[]—— 从end紧邻前一位递减到start(含)的数组。
第二个例子直观体现了负数区间:rangeRight(-3n, 0n)从0n之前(即-1n)开始递减到-3n,结果为[-1n, -2n, -3n]。注意此时的start(-3n)小于end(0n),序列是递减方向,这与数学直觉一致。
使用三:rangeRight(start, end, step) —— 自定义步长与镜像关系
当你需要以1n以外的间隔枚举时,使用三参数形式。官方文档特别强调了一个重要性质:返回结果与相同参数调用range的结果完全相反:
import { range, rangeRight } from 'es-toolkit/bigint'; console.log(rangeRight(0n, 10n, 2n)); // [8n, 6n, 4n, 2n, 0n] console.log(rangeRight(5n, 0n, -1n)); // [1n, 2n, 3n, 4n, 5n] // 始终是相同参数 range 的逆序 console.log(rangeRight(0n, 10n, 3n)); // [9n, 6n, 3n, 0n] console.log(range(0n, 10n, 3n)); // [0n, 3n, 6n, 9n]其中第二个例子展示了负步长的语义:rangeRight(5n, 0n, -1n)等价于先求出升序(此处实为降序)的range(5n, 0n, -1n) === [5n, 4n, 3n, 2n, 1n],再反转得到[1n, 2n, 3n, 4n, 5n]。也就是说,rangeRight并不改变步长的“方向语义”,它只是把range的产出整体反转——正步长对应降序输出,负步长反而得到升序输出。
- 参数:
start(bigint)—— 范围的起始值,包含;end(bigint)—— 范围的结束值,不包含;step(bigint,可选)—— 计数间隔,默认1n。
- 返回值:
bigint[]——range(start, end, step)的降序版本。
步长背离 end 时返回空数组
文档明确指出:当步长指向远离结束值的方向时,没有任何可生成的值,返回空数组:
import { rangeRight } from 'es-toolkit/bigint'; console.log(rangeRight(0n, 5n, -1n)); // []这里start = 0n、end = 5n,步长-1n指向负方向,而end在正方向,步长背离结束值,故结果为空。对应测试见 src/bigint/rangeRight.spec.ts。同理可推,rangeRight(5n, 0n, 1n)也会得到[]。
错误处理:step 为 0n 时抛出异常
由于步长为0n会导致死循环或歧义,rangeRight与range一样会直接抛出错误:
import { rangeRight } from 'es-toolkit/bigint'; rangeRight(0n, 5n, 0n); // Error: The step value must be a non-zero bigint.从 src/bigint/rangeRight.ts 可以看到,该错误在参数归一化之后、长度计算之前被抛出,错误信息为The step value must be a non-zero bigint.。测试 src/bigint/rangeRight.spec.ts 用toThrowError对该信息做了精确断言。
源码级原理:一次分配 + 等差数列填充
相比逐项push的朴素实现,rangeRight采用了“先算长度、预分配数组、再按等差公式填充”的策略,这正是 es-toolkit 保持高性能的原因之一。关键步骤:
- 长度计算:调用内部工具 src/_internal/bigIntRangeLength.ts,它用纯
BigInt算术实现Math.ceil((end - start) / step)的天花板除法:
export function bigIntRangeLength(start: bigint, end: bigint, step: bigint): number { const difference = end - start; if (step > 0n) { return difference <= 0n ? 0 : Number((difference + step - 1n) / step); } return difference >= 0n ? 0 : Number((difference + step + 1n) / step); }正步长时,若difference <= 0n直接返回 0(即“步长背离 end”的情形);负步长时,若difference >= 0n返回 0。注释中也说明这与 Number 版实现Math.ceil((end - start) / step)语义完全对齐,只是换成了无精度损失的 BigInt 运算。
预分配:
new Array<bigint>(length)一次性确定数组容量,避免动态扩容。反向填充:核心公式
result[i] = start + BigInt(length - i - 1) * step直接按索引倒推位置,无需先构造升序数组再reverse(),既省一次遍历也省一份临时内存。对比 src/bigint/range.ts 中result[i] = start + BigInt(i) * step的正向填充,两者共用bigIntRangeLength与同样的参数归一化逻辑,结构高度对称。
与 range 的关系及超越安全整数的价值
rangeRight与 range 是一对“镜像”函数:rangeRight(start, end, step)始终等于range(start, end, step).reverse()。测试 src/bigint/rangeRight.spec.ts 用toEqual(range(0n, 10n, 3n).reverse())固化了这个不变量。
两者的共同价值在于BigInt 在任何大小下都保持精确,因此可以安全构建超出Number.MAX_SAFE_INTEGER(即9007199254740991)的区间而不会出现数值悄悄碰撞。例如与 range 配合:
import { range } from 'es-toolkit/bigint'; // Number 无法精确表示这些值,BigInt 可以 console.log(range(9007199254740993n, 9007199254740996n)); // [9007199254740993n, 9007199254740994n, 9007199254740995n]同理,rangeRight也能对这类超大区间做精确的降序枚举,这在处理时间戳(纳秒级)、哈希值或任意精度业务 ID 的区间倒序场景中非常实用。
性能基准
仓库在 benchmarks/performance/rangeRight.bench.ts 中提供了rangeRight的基准测试,使用 Vitest 的bench分别测量es-toolkit/rangeRight、es-toolkit/compat/rangeRight与lodash/rangeRight在rangeRight(0, 100, 1)场景下的耗时。需要说明的是,该基准针对的是 Number 版rangeRight,但它验证了 es-toolkit 在同类函数上的实现竞争力;BigInt 版虽未被单独纳入该基准,却共享了“预分配 + 等差数列填充”的同一套优化思路。
总结
| 调用形式 | 示例 | 结果 |
|---|---|---|
rangeRight(end) | rangeRight(4n) | [3n, 2n, 1n, 0n] |
rangeRight(start, end) | rangeRight(2n, 5n) | [4n, 3n, 2n] |
rangeRight(start, end, step) | rangeRight(0n, 10n, 2n) | [8n, 6n, 4n, 2n, 0n] |
| 负步长 | rangeRight(5n, 0n, -1n) | [1n, 2n, 3n, 4n, 5n] |
| 步长背离 end | rangeRight(0n, 5n, -1n) | [] |
step === 0n | rangeRight(0n, 5n, 0n) | 抛出Error |
使用要点回顾:
- 从
es-toolkit/bigint导入,避免与 Number 版同名函数混淆; end永远不包含在结果中,start始终包含;- 结果恒等于相同参数
range的逆序; step默认1n,不可为0n,背离end时返回空数组;- 底层由 bigIntRangeLength 计算长度后预分配数组,通过等差公式一次填满,兼顾正确性与性能。
需要进一步了解正向枚举、区间判断或求和等 BigInt 工具,可继续阅读 docs/ja/reference/bigint/range.md 及 src/bigint/index.ts 中导出的其余函数。
【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考