How that "0 to 1,000 cannot overflow" line made me suspicious
The copy on this page reads "based on BigInt, it computes factorials from 0 to 1,000 and never overflows". The other half of the same sentence is hiding right next to it: Number tops out near 1.7×10^308 (170!), and only larger values need BigInt at all. So I re-checked 23! with the most ordinary code — var r = 1; for (var i = 2; i <= 23; i++) r *= i; — and it returns 25852016738884978212864, while the true value is 25852016738884976640000, a gap of 1572864; the first wrong digit sits at position 17 of what should be a 23-digit number. 23 is inside the range the page advertises, 147 steps earlier than "only at 170 does it break".
Setting the verdict aside for a moment: the page itself does not make this mistake, because it has no Number multiplication path. What does go wrong is the step where we re-check it in Excel or in a script we dash off.
The template has one multiply loop, and from line 111 it is BigInt
factorialBig (lines 108-116) contains a single loop: line 111 is var result = 1n;, line 112 is for (var i = 2n; i <= BigInt(n); i++), line 113 is result *= i;. Across the whole file there is one BigInt seed and zero Number seeds; even 5! is computed as BigInt. The copy's remark that larger values "need BigInt" describes a switch that never takes place.
Overflow begins at 171, exactness ends at 23: the two boundaries are not the same place
| Measuring stick | Where it breaks | What that step really holds |
|---|---|---|
| Number exactness | 23! | sits 1572864 above the true value |
| Number finiteness | 171! | 170! is still 7.257415615307994e+306, from 171! it is Infinity |
| Page BigInt | no boundary | 170! gives 307 digits, 171! gives 310, 1000! gives 2568 |
One more detail worth holding on to: 170! and 171! both end in 41 zeros (171 contributes no factor 5), yet the digit count climbs from 307 to 310. "It does not overflow" and "the digits are right" are two separate promises; the first one is kept 147 steps longer, and the second is already broken after 22!.
Fig. 1: n runs along the axis, and each bar is the stretch over which that measuring stick can be trusted.
1,000 is an input gate, not a limit of the algorithm
Line 142 reads if (n > 1000). It uses "greater than", so 1,000 itself passes: the raw value of 1,000! is 2,568 digits, it displays as 3,423 characters once the thousands separators are inserted, and the extra 855 commas are formatting only, because the "result digits" field on line 169 reports the length of the raw value. Typing 1,001 is a hard error, the toast reads n 不能超过 1,000, and nothing is truncated or swallowed quietly. Bypass the gate and call the function directly and 1,001! returns 2,571 digits while 2,000! returns 5,736 — you cannot type those into the page, but they are enough to show that 1,000 is only a gate.
249 trailing zeros: no such field exists on the page, so this is a script-side count
Let me fix the scope first: the statistics block on lines 168, 169 and 170 has only three fields, n, result digits, and whether the result is overlong, and grepping the template for 尾随 or 零个数 returns no match. So this number is one I counted myself. I took the zeros at the tail of the BigInt value and tested them against Legendre's formula floor(n/5)+floor(n/25)+floor(n/125)+floor(n/625) for every n from 0 to 1,000, with zero disagreements: 1,000! = 200+40+8+1 = 249 zeros, 500! has 124, 100! has 24, 25! has 6.
The staircase itself has holes. Of the values from 1 to 249 only 201 are reachable and 49 are missing, and the smallest hole is 5: 24! still has four zeros, 25! jumps straight to six, because 25 contributes two 5s at once and 125 contributes three.
Fig. 2: how the count of trailing zeros climbs as n grows (script-side count).
The most hidden failure path: parseInt stops at the first letter
Line 137 is parseInt(nStr, 10). It halts at the first character that is not a digit and raises no error, so the largest-looking input can produce the smallest result:
1e21parses as 1, the page quietly shows 1! = 1, and the gate on line 142 never fires.1,000and1e5both give 1 as well;0x10stops at the x and gives 0.- Decimals truncate toward zero:
5.5and5.9both read as 5 and return 120, with no hint. - An empty string,
abc, and full-width20all fall into theisNaNbranch on line 140, which only hides the result area. - Typing
1001is oddly the cleanest case: one clear sentence, n 不能超过 1,000.
Range list: the copy says 100 entries, the code lets 101 through
The hint on line 63 says the list shows at most 100 entries, but the guardrail on line 194 is if (n - m > 100): it compares the difference, not the number of entries. So m=0, n=100 is let through, line 213 prints its own tally as 项数 101, and the list HTML is 18,133 characters; m=1, n=101 and m=50, n=150 are also 101 entries. Turn it around and m=0, n=1000 is refused with the message 范围过大,最多支持 100 entries, so the operation of listing every value from 0! to 1,000! does not exist on the page.
Every timing below is script-side (Node v24.19.0, and the page has no timing field at all): calcSingle(1000) runs end to end in 1.020 ms, and the most expensive step is 900 to 1,000 at 102.485 ms, writing 334,650 characters into the DOM, because line 202 recomputes each i from scratch. Calling the function directly on 1,000, 5,000 and 10,000 takes 0.446, 6.644 and 20.721 ms, so n grows 10 times and the time grows 46 times.
The 5,898 characters of the process display, and not one line cut
Lines 159 to 162 are an unconditional parts.join(' × '), and the only slice in the whole file sits on line 123 stripping a minus sign: the process line for 1,000! is 5,898 characters, the " × " separators number exactly 999, and the innerHTML of the block is 9,460 characters, with hasEllipsis=false at every step. If you want to find fault, look in the copy instead: the process block for 5! is two separate divs in the DOM, line 162 ends at " × 1" and = 120 lives in the div on line 163, so that continuous form never exists on screen.
Q: So where exactly does it go wrong between 0 and 1,000?
Not on the page. It has one BigInt multiplication path, and for every n from 0 to 1,000 it agrees with the formula with zero disagreements. What goes wrong is the re-check: Number stops being exact from 23! and turns into plain Infinity at 171!.
Q: Can I see the number of trailing zeros on the page?
You cannot. The statistics block carries only n, result digits, and whether the result is overlong, three fields (lines 168 to 170), and the overlong threshold is strictly greater than 20 digits: 21! still shows 否 and only 22! shows 是. The 249 was counted by a script.
Q: What if I want the whole column from 0! to 1,000! in one go?
Range mode cannot do it: line 194 refuses on the difference, and the widest span that gets through is 101 entries. If you really want the long list, take it step by step in the single-value box of the factorial calculator, where the largest step runs to 2,568 digits.
If you are collecting factorials on the way to permutations and combinations, read the article on permutations versus combinations first, because computing a raw value before cancelling numerator against denominator is a familiar way to fall over; the same multiply-before-dividing trap shows up in the least common multiple article, where the intermediate value overflows before the result does.