es-toolkit 的 BigInt 版 range 函数:从入门到源码级解析
2026/9/16 17:29:19 网站建设 项目流程

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-toolkites-toolkit/bigint子路径下提供了专用于BigInt类型的range函数,用于生成从起始值(含)到终止值(不含)之间的BigInt数组。与Number版本不同,BigIntrange不受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工具(clampinRangemaxmedianpercentilesum等)一同导出。

使用方式详解

形式一:range(end)—— 从0n数到end之前

当只需从0n开始计数时,使用单参数形式:

import { range } from 'es-toolkit/bigint'; console.log(range(4n)); // [0n, 1n, 2n, 3n] console.log(range(0n)); // []
  • 参数endbigint):范围的终止值,不包含在结果中。
  • 返回值(bigint[]):从0nend之前的所有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)); // []
  • 参数startbigint):范围的起始值,包含在结果中。
  • 参数endbigint):范围的终止值,不包含在结果中。
  • 返回值(bigint[]):从startend之前的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)); // []
  • 参数startbigint):范围的起始值,包含在结果中。
  • 参数endbigint):范围的终止值,不包含在结果中。
  • 参数stepbigint,可选):计数间隔,默认值为1n
  • 返回值(bigint[]):按step间隔从start数到end之前的BigInt数组。

异常规则:当step0n时,函数会抛出错误(详见下文源码分析)。

源码级实现原理

查看 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); }

该函数本质上是NumberMath.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,即从最后一个元素开始反向填充,从而在结果顺序上实现倒序。也就是说,rangerangeRight生成的是同一组元素、不同排列顺序,两者共享同一套边界与异常语义。

测试用例对行为的验证

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
  • step0n时抛出异常:range(0n, 5n, 0n)抛出'The step value must be a non-zero bigint.'

Numberrange的对比与选型建议

es-toolkit同时提供Numberrange(src/math/range.ts,从主入口es-toolkit导入)。两者的核心差异体现在三方面:

维度NumberrangeBigIntrange
导入路径es-toolkites-toolkit/bigint
元素类型number[]bigint[]
精度上限Number.MAX_SAFE_INTEGER限制,超出后可能静默碰撞任意大小保持精确
步长校验要求非零整数仅要求非零(BigInt无小数)
长度计算Math.ceil((end - start) / step)bigIntRangeLengthBigInt向上取整除法

在普通业务数据(索引、分页、常规计数)场景下,Numberrange已足够;而当范围涉及大整数(如雪花 ID 区间、超大序列号、需要跨Number.MAX_SAFE_INTEGER的区间枚举)时,应选用es-toolkit/bigintBigIntrange,以确保每个值都精确无碰撞。

【免费下载链接】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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询