es-toolkit 的 BigInt 版 range 函数:从入门到源码级解析
【免费下载链接】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类型的range函数,用于生成从起始值(含)到终止值(不含)之间的BigInt数组。与Number版本不同,BigInt版range不受Number.MAX_SAFE_INTEGER精度限制,可以安全地构造超大范围。阅读本文后,你将掌握range的三种调用形式、参数约定、边界行为与异常规则,并通过源码与测试用例理解其底层实现原理。
函数签名与基本约定
range支持三种重载形式,核心语义是"从start(含)数到end(不含)":
const numbers = range(end); const numbers = range(start, end); const numbers = range(start, end, step);- 单参数形式从
0n开始计数; - 双参数形式从指定的
start开始计数; - 三参数形式额外指定步长
step(默认1n,可为负数实现降序)。
该函数仅在es-toolkit/bigint子路径下提供,以避免与Number、其他数值类型下同名的类似函数产生潜在冲突。对应的实现文件为 src/bigint/range.ts,并在 src/bigint/index.ts 中与其他BigInt工具(clamp、inRange、max、median、percentile、sum等)一同导出。
使用方式详解
形式一:range(end)—— 从0n数到end之前
当只需从0n开始计数时,使用单参数形式:
import { range } from 'es-toolkit/bigint'; console.log(range(4n)); // [0n, 1n, 2n, 3n] console.log(range(0n)); // []- 参数
end(bigint):范围的终止值,不包含在结果中。 - 返回值(
bigint[]):从0n到end之前的所有BigInt数组。
形式二:range(start, end)—— 从任意起始值开始
当需要从非0n的起始值开始计数时,使用双参数形式:
import { range } from 'es-toolkit/bigint'; console.log(range(2n, 5n)); // [2n, 3n, 4n] console.log(range(-3n, 0n)); // [-3n, -2n, -1n] // 起始值与终止值相同时,没有可数的值 console.log(range(3n, 3n)); // []- 参数
start(bigint):范围的起始值,包含在结果中。 - 参数
end(bigint):范围的终止值,不包含在结果中。 - 返回值(
bigint[]):从start到end之前的BigInt数组。
由于BigInt在任意大小下都保持精确,你可以构造跨越Number.MAX_SAFE_INTEGER(即9007199254740991)的范围,而不会像Number那样发生值的静默碰撞:
import { range } from 'es-toolkit/bigint'; console.log(range(9007199254740993n, 9007199254740996n)); // [9007199254740993n, 9007199254740994n, 9007199254740995n]这一能力在需要处理大整数 ID、哈希区间或高精度序列场景时尤为实用。
形式三:range(start, end, step)—— 自定义步长与降序
当需要以1n以外的间隔计数时,使用三参数形式;step为负数时则降序计数:
import { range } from 'es-toolkit/bigint'; console.log(range(0n, 10n, 2n)); // [0n, 2n, 4n, 6n, 8n] console.log(range(5n, 0n, -1n)); // [5n, 4n, 3n, 2n, 1n] console.log(range(5n, 0n, -2n)); // [5n, 3n, 1n]如果step指向远离end的方向(即正步长但start > end,或负步长但start < end),则没有任何可生成的值,直接返回空数组:
import { range } from 'es-toolkit/bigint'; console.log(range(0n, 5n, -1n)); // [] console.log(range(5n, 0n, 1n)); // []- 参数
start(bigint):范围的起始值,包含在结果中。 - 参数
end(bigint):范围的终止值,不包含在结果中。 - 参数
step(bigint,可选):计数间隔,默认值为1n。 - 返回值(
bigint[]):按step间隔从start数到end之前的BigInt数组。
异常规则:当step为0n时,函数会抛出错误(详见下文源码分析)。
源码级实现原理
查看 src/bigint/range.ts 的实现,可以发现range的核心流程分为三步:参数归一化、长度预计算、循环填充。
export function range(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(i) * step; } return result; }1. 参数归一化
当只传入一个参数时(end == null),实现将end赋值为传入值,并把start置为0n,从而统一走同一套内部逻辑。这与Number版本的 src/math/range.ts 中if (end == null) { end = start; start = 0; }的处理方式完全一致。
2. 零步长校验
step === 0n时直接抛出Error('The step value must be a non-zero bigint.')。注意这里只校验是否为0n,负步长是被允许的(用于降序计数)。Number版本则额外要求step必须是整数(Number.isInteger(step)且非零),因为BigInt本身不存在小数概念,所以无需该检查。
3. 基于长度的预分配与填充
range并没有采用"边推边判断"的逐项循环,而是先用内部工具函数bigIntRangeLength计算出结果数组的长度,再通过new Array<bigint>(length)预分配数组,随后用result[i] = start + BigInt(i) * step逐项填充。这种"先算长度、再按索引写入"的做法避免了数组反复扩容,同时保证了大范围场景下的性能表现。
长度计算函数位于 src/_internal/bigIntRangeLength.ts:
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); }该函数本质上是Number版Math.ceil((end - start) / step)的BigInt实现:通过(difference + step - 1n) / step(正步长)与(difference + step + 1n) / step(负步长)完成BigInt上的向上取整除法。同时它统一处理了两类边界情况:
- 当
step指向远离end的方向(正步长下difference <= 0n,或负步长下difference >= 0n)时返回0,对应文档中的空数组行为; - 当
start === end时(difference === 0n),正负步长路径都会返回0,因此range(3n, 3n)的结果是[]。
4. 与rangeRight的关联
es-toolkit/bigint还提供了按降序输出的 src/bigint/rangeRight.ts。它复用了完全相同的bigIntRangeLength长度计算与step === 0n校验逻辑,唯一的区别在于填充方式:result[i] = start + BigInt(length - i - 1) * step,即从最后一个元素开始反向填充,从而在结果顺序上实现倒序。也就是说,range与rangeRight生成的是同一组元素、不同排列顺序,两者共享同一套边界与异常语义。
测试用例对行为的验证
src/bigint/range.spec.ts 使用 Vitest 对上述全部行为做了覆盖验证,可以作为理解函数契约的权威参考:
- 单参数从
0n计数:range(4n)等于[0n, 1n, 2n, 3n]; - 双参数从指定值计数:
range(2n, 5n)等于[2n, 3n, 4n]; - 自定义步长:
range(0n, 10n, 2n)等于[0n, 2n, 4n, 6n, 8n],range(0n, 5n, 2n)等于[0n, 2n, 4n](验证最后一个元素不会越过end); - 负步长降序:
range(5n, 0n, -1n)等于[5n, 4n, 3n, 2n, 1n],range(5n, 0n, -2n)等于[5n, 3n, 1n]; - 步长方向远离终点时返回空数组:
range(0n, 5n, -1n)与range(5n, 0n, 1n)均等于[]; start === end返回空数组:range(3n, 3n)等于[];- 支持负区间:
range(-3n, 0n)等于[-3n, -2n, -1n]; - 超越
Number.MAX_SAFE_INTEGER依然精确:range(9007199254740993n, 9007199254740996n)精确返回三个连续的BigInt; step为0n时抛出异常:range(0n, 5n, 0n)抛出'The step value must be a non-zero bigint.'。
与Number版range的对比与选型建议
es-toolkit同时提供Number版range(src/math/range.ts,从主入口es-toolkit导入)。两者的核心差异体现在三方面:
| 维度 | Number版range | BigInt版range |
|---|---|---|
| 导入路径 | es-toolkit | es-toolkit/bigint |
| 元素类型 | number[] | bigint[] |
| 精度上限 | 受Number.MAX_SAFE_INTEGER限制,超出后可能静默碰撞 | 任意大小保持精确 |
| 步长校验 | 要求非零整数 | 仅要求非零(BigInt无小数) |
| 长度计算 | Math.ceil((end - start) / step) | bigIntRangeLength的BigInt向上取整除法 |
在普通业务数据(索引、分页、常规计数)场景下,Number版range已足够;而当范围涉及大整数(如雪花 ID 区间、超大序列号、需要跨Number.MAX_SAFE_INTEGER的区间枚举)时,应选用es-toolkit/bigint的BigInt版range,以确保每个值都精确无碰撞。
【免费下载链接】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),仅供参考