阶乘计算器

用 BigInt 逐个相乘得到精确的 n!,同时把双精度的精度边界摊开给你看:20! 已经超过 2^53−1 却仍然精确,真正开始丢精度的是 23!。

精确值位数19 位
双精度精确?是
Stirling 相对误差−0.415765 %
是 2^53−1 的270.11 倍
n = 20

【精确值 · BigInt】
20! = 2432902008176640000
位数:19 位

【双精度 · Number】
Number 值:2432902008176640000
与精确值一致:是
2^53 − 1 = 9007199254740991,20! 是它的 270.1064 倍,已超出安全整数范围
超出安全范围 ≠ 必然丢精度:20! 含 2^18 因子,奇部只有 44 位,仍然精确;第一个不精确的是 23!

【Stirling 近似】
√(2πn)·(n/e)^n = 2.422757×10^18
相对误差 = -0.415765 %(Stirling 主项永远偏小)

提示:n 输入框里按 Enter 直接计算;正整数走 BigInt 精确相乘,小数与负的非整数走 Gamma 函数。负整数没有阶乘,会被明确拒绝。

使用方法

  1. 阶乘计算器在「n」里默认填 20(示例值),允许 0~10000 的整数;小数与负的非整数也可以填,会走 Gamma 函数分支。
  2. 「Stirling 近似对比」默认开启第二项,结果区会多出 Stirling 公式的估算值与相对误差;只想要精确值时把它切成「只显示精确值与位数」。
  3. 点「开始计算」(或在 n 输入框里直接按 Enter),结果区给出三段:BigInt 精确值、双精度 Number 值与是否一致、Stirling 近似与误差。
  4. 统计条会同时显示四项:精确值位数、双精度是否精确、Stirling 相对误差、以及结果相当于 2^53−1 的多少倍。
  5. 正文下方的 n! 对照表列出 0 到 25 的每一档,默认显示前 12 行,点「加载更多」每次多展开 12 行,「导出 CSV」把整张表导成带 BOM 的 UTF-8 文件。

这个阶乘计算器能解决什么问题

一是把「算得出来」和「算得准」分开。用 Number 循环乘到 23 就开始悄悄出错,但结果看上去仍然是一串正常数字,肉眼完全看不出来。本页每次都同时给出 BigInt 精确值与 Number 值,并把两者是否一致写明白,你一眼就知道这次的结果能不能直接抄。

二是算位数而不是算数字。很多场景只关心 n! 有多少位,例如判断一个数组的排列数会不会撑爆字段长度。100! 是 158 位,1000! 是 2568 位,10000! 是 35660 位——这些位数用 Stirling 的对数式就能估,不需要真的把这几万个数字乘出来。

三是给估算结果标一个误差范围。Stirling 公式在很多教材里被当成「近似等于」直接抄,但它的相对误差是有具体数值的:n = 5 时低估 1.6507%,n = 20 时低估 0.4158%,n = 100 时低估 0.0833%。知道这个数量级,你才能判断某一次估算能不能当结论用。

四是把定义域边界讲清楚。0! 为什么等于 1、小数能不能算阶乘、(−1)! 为什么没有定义,这三个问题在查资料时经常被含糊带过。这个阶乘计算器把 0~10000 的整数、正负小数、负整数分别走不同分支,每一条分支的取与舍都写在页面上。

计算原理:BigInt 逐项相乘与 2^53−1 边界

双精度能表示到哪一步。JavaScript 的数字是 IEEE 754 定义的 binary64:1 位符号、11 位阶码、52 位尾数,再加上规格化数隐含的最高位,一共能精确表示 53 位二进制有效数字。由此得到所有绝对值不超过 253 = 9007199254740992 的整数都可以无损表示,其中最大的「安全整数」是 253 − 1 = 9007199254740991,也就是 Number.MAX_SAFE_INTEGER。

但「超过 2^53−1」并不等于「马上丢精度」。把一个阶乘写成 2a × 奇部,其中 a 是 n! 里因子 2 的个数 v2(n!) = ⌊n/2⌋ + ⌊n/4⌋ + ⌊n/8⌋ + …。尾数只需要存下那个奇部,剩下的 2a 交给阶码去表示,所以只要奇部本身不超过 53 位,这个整数就仍然可以被精确表示。逐档算下来是这样的:

所以网上流传的那句「Number 从 21! 起就不准了」是错的。21! 和 22! 实测都精确,真正的分水岭在 23!。判断依据是奇部位长,不是「数值超过了 253」这个笼统说法——把 253−1 当成一条非黑即白的线,恰恰是最常见的误用。

BigInt 路径。BigInt 是 ECMAScript 2020 引入的任意精度整数类型,后缀 n 标记字面量。本页的精确值走 let r = 1n; for (let i = 2n; i <= n; i++) r *= i,全程不经过浮点,所以位数再多也不会丢有效数字。n = 0 和 n = 1 时循环体一次都不执行,直接返回 1n,这与 0! = 1 的约定天然吻合。Number 路径的上溢则来得更早:Number 能算出的最大阶乘是 170! = 7.257415615307994×10306,再乘一个 171 就超过 Number.MAX_VALUE = 1.7976931348623157×10308,直接变成 Infinity。也就是说用 Number 算阶乘,从 23! 开始不准、到 171! 直接算不出来。

Stirling 近似。公式是 n! ≈ √(2πn)·(n/e)n,本页用它的对数形式 log10(n!) ≈ 0.5·log10(2πn) + n·(log10n − log10e) 先取对数再加回幂次,避免中间结果上溢。这个主项永远偏小,因为它省略了 1 + 1/(12n) + 1/(288n²) − … 这一串修正因子:n = 5 时低估 1.6507%,n = 20 时 0.4158%,n = 100 时 0.0833%,n = 170 时 0.0490%。误差随 n 单调减小,所以它是估算位数的好工具(100! 有 158 位,1000! 有 2568 位,10000! 有 35660 位),不是算精确值的工具。

0! = 1 与 Gamma 函数。0! = 1 是空积的约定,不是凑出来的数:n! 是「把 1 到 n 的所有整数乘起来」,n = 0 时一个数都不乘,而乘法的单位元是 1。这个约定必须成立,否则组合数 C(n,0) = n!/(0!·n!) 就等于不了 1。正整数阶乘可以用 Gamma 函数推广成 n! = Γ(n+1),其中 Γ(x) = ∫0∞ tx−1e−tdt,且 Γ(1) = 1,与 0! = 1 正好对上。小数就靠它:Γ(0.5) = √π ≈ 1.7724539,所以 (−0.5)! ≈ 1.7724539;Γ(1.5) = √π/2 ≈ 0.8862269,即 0.5! ≈ 0.8862269。唯一的例外是负整数——Γ 在 x = 0, −1, −2, … 处都是极点、函数值发散,所以 (−1)! 没有定义。本页对小数和负的非整数用 Lanczos 近似(g = 7,9 个系数)求 Γ,对负整数直接给出报错文案,而不是返回一个看起来很像的假数字。

完整示例:20!

示例条件与页面默认值完全一致:n = 20,开启 Stirling 对比。计算过程分三步。

再看两个对照档。n = 18 时,18! = 6402373705728000 落在 253−1 = 9007199254740991 之内,这是最后一个「安全」的阶乘;Stirling 相对误差 −0.461846%。n = 23 时,精确值是 25852016738884976640000,而 Number 算出来的是 2.585201673888498×1022,两者相差 +1572864——数字串看上去还是很正常,但已经错了,这就是必须用 BigInt 的原因。

输入范围与 n! 对照表

下表列出 n = 0 到 25 的每一档:精确值、位数、是否落在 253−1 之内、Number 能否精确表示、以及 Stirling 主项的相对误差。默认显示前 12 行,可展开或导出 CSV。

nn!(精确值)位数在 253−1 之内Number 精确?Stirling 相对误差
011 位是是—
111 位是是-7.786299 %
221 位是是-4.049782 %
361 位是是-2.729840 %
4242 位是是-2.057604 %
51203 位是是-1.650693 %
67203 位是是-1.378030 %
750404 位是是-1.182622 %
8403205 位是是-1.035726 %
93628806 位是是-0.921276 %
1036288007 位是是-0.829596 %
11399168008 位是是-0.754507 %
124790016009 位是是-0.691879 %
13622702080010 位是是-0.638850 %
148717829120011 位是是-0.593370 %
15130767436800013 位是是-0.553933 %
162092278988800014 位是是-0.519412 %
1735568742809600015 位是是-0.488940 %
18640237370572800016 位是是-0.461846 %
1912164510040883200018 位否是-0.437596 %
20243290200817664000019 位否是-0.415765 %
215109094217170944000020 位否是-0.396009 %
22112400072777760768000022 位否是-0.378045 %
232585201673888497664000023 位否否-0.361641 %
2462044840173323943936000024 位否否-0.346600 %
251551121004333098598400000026 位否否-0.332761 %

表里加底色的是三档关键值:19! 是第一个超出 253−1 的阶乘,20! 是常见示例同时也是「超出但不丢精度」的代表,23! 是第一个 Number 无法精确表示的阶乘。n = 0 一行的 Stirling 误差写作「—」,因为公式里的 (n/e)n 在 n = 0 处没有意义。

边界与精度

常见问题

为什么 20! 已经超过 2^53−1 了还是精确的?

因为「超过安全整数范围」和「丢精度」不是同一件事。双精度能精确表示的前提是:把整数写成 2a × 奇部之后,奇部的二进制位数不超过 53。20! 里因子 2 的个数 v2(20!) = 10 + 5 + 2 + 1 = 18,奇部只有 44 位,远小于 53,所以尾数装得下,218 那部分交给阶码表示就行。

那到底从几开始不准?

23!。逐个验证下来:19!(奇部 41 位)、20!(44 位)、21!(48 位)、22!(51 位)都还在 53 位以内,全部精确;23! 的奇部有 56 位,超出后 Number 与真值相差 +1572864,是第一个不精确的阶乘。很多资料写「21! 起就不准」,那个说法不对。

那我平时该用哪种方式算阶乘?

只要 n 超过 20,就用 BigInt:let r = 1n; for (let i = 2n; i <= n; i++) r *= i;。它没有位数上限,只受可用内存限制。只有需要「量级」或者「位数」时,才用 Stirling 的对数式去估,速度快得多而且不会上溢。

170! 之后为什么直接变成 Infinity?

因为 Number 的最大有限值是 Number.MAX_VALUE = 1.7976931348623157×10308,而 170! = 7.257415615307994×10306 已经是最后一个装得下的阶乘,再乘 171 就溢出,按 IEEE 754 的规则变成正无穷。要算 171! 及更大,必须换 BigInt。

Stirling 公式为什么不直接用?它明明误差很小。

误差小是相对的。n = 100 时相对误差 0.083298%,看着不到千分之一,但 100! 的前几位是 9332621544…,而 Stirling 主项算出来是 9324847625…——只有「93」这两位对得上,第三位就已经偏了,后面 156 位全不可信。补上 1/(12n) 修正项之后变成 9332618332…,前 5 位才对上。所以 Stirling 适合判断量级和位数(位数误差不影响结论),不适合给出可以照抄的数值;要精确值就用 BigInt 乘出来,本页两边都给你。

0! 为什么等于 1?

因为阶乘是「把 1 到 n 的整数全部相乘」,n = 0 时一个因子都没有,空积按定义为乘法的单位元,也就是 1。这个约定不是可选的:组合数 C(n,0) = n!/(0!·n!) 要等于 1、Gamma 函数要满足 Γ(1) = 1,都得靠它。本页的 BigInt 循环在 n = 0 和 n = 1 时一次都不执行,直接返回 1n,正是同一个道理。

小数和负数能算阶乘吗?

小数能,但算的是 Gamma 函数的推广值:n! = Γ(n+1)。0.5! = Γ(1.5) = √π/2 ≈ 0.8862269,(−0.5)! = Γ(0.5) = √π ≈ 1.7724539。负整数不能,因为 Γ 在 0 和所有负整数处都是极点,函数值发散;负的非整数则可以,会按反射公式 Γ(x)Γ(1−x) = π / sin(πx) 算出来,结果正负交替。

页面上的「位数」是怎么数出来的?

整数分支直接取 BigInt 字符串的长度,是精确值,不存在估算误差。比如 20! 转成字符串是 19 个字符,所以是 19 位;10000! 有 35660 位。Stirling 的对数式本身也能估位数(⌊log10(n!)⌋ + 1),但那是估计值,本页只在解释原理时用它做对照,表格里的位数一律来自字符串长度。Gamma 分支没有整数位数,统计条会改显示函数值本身。

相关工具