Hàm giai thừa nhanh nhất trong JavaScript là gì? [đóng cửa]


94

Tìm kiếm cách triển khai thực sự nhanh chóng của hàm giai thừa trong JavaScript. Bất kỳ gợi ý?


8
Phạm vi đối số có thể là gì?
Nikita Rybak

5
Bạn đã xem xét tính toán trước các thừa số và lưu trữ các giá trị trong bảng tra cứu chưa?
Waleed Amjad

2
Ứng dụng của một chức năng như vậy là gì? Nói cách khác, bạn sẽ sử dụng nó để làm gì?
chóp

@Nikita Rybak, chỉ có 1 agrument (n). If (n> 170) e = Infinity
Ken

@ Pointy, một dịch vụ máy tính toán học khác.
Ken

Câu trả lời:


110

Bạn có thể tìm kiếm (1 ... 100)! trên Wolfram | Alpha để tính toán trước chuỗi giai thừa.

100 số đầu tiên là:

1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000, 20922789888000, 355687428096000, 6402373705728000, 121645100408832000, 2432902008176640000, 51090942171709440000, 1124000727777607680000, 25852016738884976640000, 620448401733239439360000, 15511210043330985984000000, 403291461126605635584000000, 10888869450418352160768000000, 304888344611713860501504000000, 8841761993739701954543616000000, 265252859812191058636308480000000, 8222838654177922817725562880000000, 263130836933693530167218012160000000, 8683317618811886495518194401280000000, 295232799039604140847618609643520000000, 10333147966386144929666651337523200000000, 371993326789901217467999448150835200000000, 13763753091226345046315979581580902400000000, 523022617466601111760007224100074291200000000, 20397882081197443358640281739902897356800000000, 815915283247897734345611269596115894272000000000, 33452526613163807108170062053440751665152000000000, 1405006117752879898543142606244511569936384000000000, 60415263063373835637355132068513997507264512000000000, 2658271574788448768043625811014615890319638528000000000, 119622220865480194561963161495657715064383733760000000000, 5502622159812088949850305428800254892961651752960000000000, 258623241511168180642964355153611979969197632389120000000000, 12413915592536072670862289047373375038521486354677760000000000, 608281864034267560872252163321295376887552831379210240000000000, 30414093201713378043612608166064768844377641568960512000000000000, 1551118753287382280224243016469303211063259720016986112000000000000, 80658175170943878571660636856403766975289505440883277824000000000000, 4274883284060025564298013753389399649690343788366813724672000000000000, 230843697339241380472092742683027581083278564571807941132288000000000000, 12696403353658275925965100847566516959580321051449436762275840000000000000, 710998587804863451854045647463724949736497978881168458687447040000000000000, 40526919504877216755680601905432322134980384796226602145184481280000000000000, 2350561331282878571829474910515074683828862318181142924420699914240000000000000, 138683118545689835737939019720389406345902876772687432540821294940160000000000000, 8320987112741390144276341183223364380754172606361245952449277696409600000000000000, 507580213877224798800856812176625227226004528988036003099405939480985600000000000000, 31469973260387937525653122354950764088012280797258232192163168247821107200000000000000, 1982608315404440064116146708361898137544773690227268628106279599612729753600000000000000, 126886932185884164103433389335161480802865516174545192198801894375214704230400000000000000, 8247650592082470666723170306785496252186258551345437492922123134388955774976000000000000000, 544344939077443064003729240247842752644293064388798874532860126869671081148416000000000000000, 36471110918188685288249859096605464427167635314049524593701628500267962436943872000000000000000, 2480035542436830599600990418569171581047399201355367672371710738018221445712183296000000000000000, 171122452428141311372468338881272839092270544893520369393648040923257279754140647424000000000000000, 11978571669969891796072783721689098736458938142546425857555362864628009582789845319680000000000000000, 850478588567862317521167644239926010288584608120796235886430763388588680378079017697280000000000000000, 61234458376886086861524070385274672740778091784697328983823014963978384987221689274204160000000000000000, 4470115461512684340891257138125051110076800700282905015819080092370422104067183317016903680000000000000000, 330788544151938641225953028221253782145683251820934971170611926835411235700971565459250872320000000000000000, 24809140811395398091946477116594033660926243886570122837795894512655842677572867409443815424000000000000000000, 1885494701666050254987932260861146558230394535379329335672487982961844043495537923117729972224000000000000000000, 145183092028285869634070784086308284983740379224208358846781574688061991349156420080065207861248000000000000000000, 11324281178206297831457521158732046228731749579488251990048962825668835325234200766245086213177344000000000000000000, 894618213078297528685144171539831652069808216779571907213868063227837990693501860533361810841010176000000000000000000, 71569457046263802294811533723186532165584657342365752577109445058227039255480148842668944867280814080000000000000000000, 5797126020747367985879734231578109105412357244731625958745865049716390179693892056256184534249745940480000000000000000000, 475364333701284174842138206989404946643813294067993328617160934076743994734899148613007131808479167119360000000000000000000, 39455239697206586511897471180120610571436503407643446275224357528369751562996629334879591940103770870906880000000000000000000, 3314240134565353266999387579130131288000666286242049487118846032383059131291716864129885722968716753156177920000000000000000000, 281710411438055027694947944226061159480056634330574206405101912752560026159795933451040286452340924018275123200000000000000000000, 24227095383672732381765523203441259715284870552429381750838764496720162249742450276789464634901319465571660595200000000000000000000, 2107757298379527717213600518699389595229783738061356212322972511214654115727593174080683423236414793504734471782400000000000000000000, 185482642257398439114796845645546284380220968949399346684421580986889562184028199319100141244804501828416633516851200000000000000000000, 16507955160908461081216919262453619309839666236496541854913520707833171034378509739399912570787600662729080382999756800000000000000000000, 1485715964481761497309522733620825737885569961284688766942216863704985393094065876545992131370884059645617234469978112000000000000000000000, 135200152767840296255166568759495142147586866476906677791741734597153670771559994765685283954750449427751168336768008192000000000000000000000, 12438414054641307255475324325873553077577991715875414356840239582938137710983519518443046123837041347353107486982656753664000000000000000000000, 1156772507081641574759205162306240436214753229576413535186142281213246807121467315215203289516844845303838996289387078090752000000000000000000000, 108736615665674308027365285256786601004186803580182872307497374434045199869417927630229109214583415458560865651202385340530688000000000000000000000, 10329978488239059262599702099394727095397746340117372869212250571234293987594703124871765375385424468563282236864226607350415360000000000000000000000, 991677934870949689209571401541893801158183648651267795444376054838492222809091499987689476037000748982075094738965754305639874560000000000000000000000, 96192759682482119853328425949563698712343813919172976158104477319333745612481875498805879175589072651261284189679678167647067832320000000000000000000000, 9426890448883247745626185743057242473809693764078951663494238777294707070023223798882976159207729119823605850588608460429412647567360000000000000000000000, 933262154439441526816992388562667004907159682643816214685929638952175999932299156089414639761565182862536979208272237582511852109168640000000000000000000000, 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

Nếu bạn vẫn muốn tự mình tính toán các giá trị, bạn có thể sử dụng memoization :

var f = [];
function factorial (n) {
  if (n == 0 || n == 1)
    return 1;
  if (f[n] > 0)
    return f[n];
  return f[n] = factorial(n-1) * n;
}

Chỉnh sửa: 21.08.2014

Giải pháp 2

Tôi nghĩ sẽ hữu ích nếu thêm một ví dụ hoạt động của hàm giai thừa lặp đi lặp lại lười biếng sử dụng các số lớn để có kết quả chính xác với ghi nhớbộ nhớ cache để so sánh

var f = [new BigNumber("1"), new BigNumber("1")];
var i = 2;
function factorial(n)
{
  if (typeof f[n] != 'undefined')
    return f[n];
  var result = f[i-1];
  for (; i <= n; i++)
      f[i] = result = result.multiply(i.toString());
  return result;
}
var cache = 100;
// Due to memoization, following line will cache first 100 elements.
factorial(cache);

Tôi giả sử bạn sẽ sử dụng một số loại bao đóng để hạn chế khả năng hiển thị tên biến.

Ref : BigNumber Sandbox : JsFiddle


Các giá trị quá 6402373705728000 sẽ bị cắt bớt, vì vậy nếu bạn định sử dụng phương pháp này, hãy đảm bảo chuyển đổi thành cấp số nhân trước khi sử dụng bảng nói trên.
David Scott Kirby

1
@DavidScottKirby Javascript tự động chuyển đổi những con số này thành biểu diễn float 64-bit gần nhất của chúng. Lợi ích thực sự của việc không có số chính xác đầy đủ trong mã là kích thước tệp giảm.
le_m

Giải pháp thứ hai của bạn có thể được đơn giản hóa để function factorial (n) { for (var i = f.length; i <= n; i++) f.push(f[i - 1].multiply(i.toString())); return f[n]; }Cũng xem câu trả lời của tôi sử dụng nội trang mới BigInthơn thay vì thư viện của bên thứ ba.
Patrick Roberts

97

Bạn nên sử dụng một vòng lặp.

Đây là hai phiên bản được chuẩn hóa bằng cách tính giai thừa của 100 cho 10.000 lần.

Đệ quy

function rFact(num)
{
    if (num === 0)
      { return 1; }
    else
      { return num * rFact( num - 1 ); }
}

Lặp đi lặp lại

function sFact(num)
{
    var rval=1;
    for (var i = 2; i <= num; i++)
        rval = rval * i;
    return rval;
}

Trực tiếp tại: http://jsfiddle.net/xMpTv/

Kết quả của tôi cho thấy:
- Đệ quy ~ 150 mili giây
- Lặp lại ~ 5 mili giây ..


+1 Câu trả lời tuyệt vời! Mặc dù sự ghi nhớ có thể hợp lý khi có nhiều lệnh gọi để tính giai thừa cho các số lớn hơn.
Tadeck

@Tadeck, cảm ơn. Thật vậy memoization là rất hữu ích trong trường hợp này và đó là lý do tại sao Margus câu trả lời được chọn là đúng :)
Gabriele Petrioli

Phiên bản 1 dòng của hàm đệ quy: function Giai thừa (num) {return (num == 1)? num: num * objects.callee (num-1); }
jbyrd

2
@HWTech, bạn không bao giờ gọi các phương thức. Thử nghiệm của bạn so sánh tốc độ của việc xác định hai phương pháp .. không phải là thời gian họ làm để thực hiện .. Đây là một tốt hơn kiểm tra (cố gắng chỉ giai thừa của 15)
Gabriele Petrioli

3
Thay vì rval = rval * i;bạn có thể viếtrval *= i;
Ryan

29

Tôi vẫn nghĩ câu trả lời của Margus là câu trả lời hay nhất. Tuy nhiên, nếu bạn muốn tính theo giai thừa của các số trong phạm vi từ 0 đến 1 (tức là hàm gamma), thì bạn không thể sử dụng cách tiếp cận đó vì bảng tra cứu sẽ phải chứa các giá trị vô hạn.

Tuy nhiên, bạn có thể tính gần đúng giá trị của các giai thừa và nó khá nhanh, nhanh hơn so với gọi đệ quy chính nó hoặc lặp lại nó ít nhất (đặc biệt là khi các giá trị bắt đầu lớn hơn).

Một phương pháp xấp xỉ tốt là một của Lanczos

Đây là một triển khai trong JavaScript (được chuyển từ máy tính tôi đã viết vài tháng trước):

function factorial(op) {
 // Lanczos Approximation of the Gamma Function
 // As described in Numerical Recipes in C (2nd ed. Cambridge University Press, 1992)
 var z = op + 1;
 var p = [1.000000000190015, 76.18009172947146, -86.50532032941677, 24.01409824083091, -1.231739572450155, 1.208650973866179E-3, -5.395239384953E-6];

 var d1 = Math.sqrt(2 * Math.PI) / z;
 var d2 = p[0];

 for (var i = 1; i <= 6; ++i)
  d2 += p[i] / (z + i);

 var d3 = Math.pow((z + 5.5), (z + 0.5));
 var d4 = Math.exp(-(z + 5.5));

 d = d1 * d2 * d3 * d4;

 return d;
}

Giờ đây, bạn có thể làm những thứ thú vị như factorial(0.41), v.v. tuy nhiên, độ chính xác có thể hơi sai lệch, xét cho cùng, đó là kết quả gần đúng.


cách tiếp cận khá thú vị, cảm ơn.
Ken

Vừa tiết kiệm cho tôi rất nhiều thời gian, cảm ơn rất nhiều :)
nicolaskruchten

Tôi khuyên bạn nên thay đổi phần bên dưới vòng lặp for thành var d3d4 = Math.exp((z + 0.5) * Math.log(z + 5.5) - z - 5.5); return d1 * d2 * d3d4;. Điều này cho phép bạn tính các giai thừa lên đến 169! thay vì hiện tại chỉ có 140 !. Đây là khá gần với giai thừa có thể biểu diễn tối đa bằng cách sử dụng Numberkiểu dữ liệu, là 170 !.
le_m

18

Bảng tra cứu là cách rõ ràng để thực hiện, nếu bạn đang làm việc với các số tự nhiên. Để tính toán bất kỳ giai thừa nào trong thời gian thực, bạn có thể tăng tốc độ bằng bộ nhớ cache, lưu các số bạn đã tính toán trước đó. Cái gì đó như:

factorial = (function() {
    var cache = {},
        fn = function(n) {
            if (n === 0) {
                return 1;
            } else if (cache[n]) {
                return cache[n];
            }
            return cache[n] = n * fn(n -1);
        };
    return fn;
})();

Bạn có thể tính toán trước một số giá trị để tăng tốc hơn nữa.


3
Tôi đã tạo một trình ghi nhớ tự động cho bất kỳ chức năng nhất định nào dựa trên câu trả lời này (cũng nhanh hơn một chút :)), cũng bao gồm giới hạn về kích thước bộ nhớ cache. stackoverflow.com/a/10031674/36537
Phil H

16

Đây là giải pháp của tôi:

function fac(n){
    return(n<2)?1:fac(n-1)*n;
}

Đó là cách đơn giản nhất (ít ký tự / dòng) mà tôi đã tìm thấy, chỉ một hàm với một dòng mã.


Chỉnh sửa:
Nếu bạn thực sự muốn lưu một số ký tự, bạn có thể sử dụng Hàm mũi tên (21 byte) :

f=n=>(n<2)?1:f(n-1)*n

7
Lưu thậm chí nhiều hơn với f=n=>n?f(n-1)*n:1...
le_m

Thật không may, ngay cả khi nó đẹp và hình thức ngắn, đây là cách làm chậm nhất .
Zibri

11

Chỉ một dòng với ES6

const factorial = n => !(n > 1) ? 1 : factorial(n - 1) * n;


factorial = n => n <= 1 ? 1 : factorial(n - 1) * n
Naramsim

10

hàm đệ quy ngắn và dễ dàng (bạn cũng có thể làm điều đó với một vòng lặp, nhưng tôi không nghĩ rằng điều đó sẽ tạo ra bất kỳ sự khác biệt nào về hiệu suất):

function factorial (n){
  if (n==0 || n==1){
    return 1;
  }
  return factorial(n-1)*n;
} 

đối với một n rất lớn, bạn có thể sử dụng xấp xỉ stirlings - nhưng điều đó sẽ chỉ cung cấp cho bạn một giá trị gần đúng.

BIÊN TẬP: một nhận xét về lý do tại sao tôi nhận được một phản đối cho điều này sẽ rất tuyệt ...

EDIT2: đây sẽ là kỹ thuật sử dụng một vòng lặp (sẽ là lựa chọn tốt hơn):

function factorial (n){
  j = 1;
  for(i=1;i<=n;i++){
    j = j*i;
  }
  return j;
}

Tôi nghĩ rằng giải pháp tốt nhất là nên sử dụng các giá trị lưu trữ, như Margus đề cập và sử dụng Stirlings xấp xỉ cho các giá trị lớn hơn (giả định bạn phải thực sự nhanh chóng và không phải chính xác về số lượng lớn như vậy).


3
Trong các ngôn ngữ không có tối ưu hóa cuộc gọi đuôi (tức là hầu hết các ngôn ngữ được sử dụng rộng rãi), tốt hơn là sử dụng triển khai không đệ quy để dễ dàng thực hiện, mặc dù có nhiều cách giải quyết nó: paulbarry.com/articles/2009/08/30 / tail-call-Optimization
Daniel Earwicker

điều đó thực sự chắc chắn không phải là nhanh nhất, vì nó thậm chí sẽ không sử dụng TCO, nếu nó được triển khai. Nhưng nó đơn giản và tôi sẽ không từ chối nó. Nó không phải là nhanh nhất chắc chắn.
haylem,

Tối ưu hóa cuộc gọi đuôi thậm chí không thể thực hiện cho hàm này, vì lệnh gọi đệ quy không ở vị trí đuôi.
Fred Foo

3
@Josh, ( không phải là downvoter ) nhanh nhất là vòng lặp bằng lãi khá ..
Gabriele Petrioli

7

Kìa, trình ghi nhớ, nhận bất kỳ hàm đối số đơn nào và ghi nhớ nó. Hóa ra là nhanh hơn một chút so với giải pháp của @ xPheRe , bao gồm cả giới hạn về kích thước bộ nhớ cache và kiểm tra liên quan, vì tôi sử dụng shortcuiting, v.v.

function memoize(func, max) {
    max = max || 5000;
    return (function() {
        var cache = {};
        var remaining = max;
        function fn(n) {
            return (cache[n] || (remaining-- >0 ? (cache[n]=func(n)) : func(n)));
        }
        return fn;
    }());
}

function fact(n) {
    return n<2 ? 1: n*fact(n-1);
}

// construct memoized version
var memfact = memoize(fact,170);

// xPheRe's solution
var factorial = (function() {
    var cache = {},
        fn = function(n) {
            if (n === 0) {
                return 1;
            } else if (cache[n]) {
                return cache[n];
            }
            return cache[n] = n * fn(n -1);
        };
    return fn;
}());

Trên máy của tôi trong Chrome nhanh hơn khoảng 25 lần so với phiên bản đệ quy và nhanh hơn 10% so với xPheRe.


6

Hàm giai thừa nhanh nhất

Tôi nghĩ rằng phiên bản dựa trên vòng lặp này có thể là hàm giai thừa nhanh nhất.

function factorial(n, r = 1) {
  while (n > 0) r *= n--;
  return r;
}

// Default parameters `r = 1`,
//   was introduced in ES6

Và đây là lý do của tôi:

  • Các hàm đệ quy, ngay cả với ghi nhớ, có chi phí của một cuộc gọi hàm (về cơ bản là đẩy các hàm lên ngăn xếp), điều này kém hiệu quả hơn so với sử dụng vòng lặp
  • Trong khi forcác vòng lặp và whilevòng lặp có hiệu suất tương tự nhau, một forvòng lặp không có biểu thức khởi tạo và biểu thức cuối cùng trông kỳ lạ; có lẽ tốt hơn nên viết for(; n > 0;)nhưwhile(n > 0)
  • Chỉ có hai tham số nrđược sử dụng, vì vậy về lý thuyết ít tham số hơn có nghĩa là dành ít thời gian hơn cho việc phân bổ bộ nhớ
  • Sử dụng một vòng lặp giảm dần để kiểm tra xem ncó bằng không - tôi đã nghe lý thuyết rằng máy tính kiểm tra các số nhị phân (0 và 1) tốt hơn chúng kiểm tra các số nguyên khác

5

Tôi đã xem qua bài đăng này. Lấy cảm hứng từ tất cả các đóng góp ở đây, tôi đã nghĩ ra phiên bản của riêng mình, có hai tính năng mà tôi chưa từng thảo luận trước đây: 1) Kiểm tra để đảm bảo đối số là số nguyên không âm 2) Tạo một đơn vị khỏi bộ nhớ cache và chức năng biến nó thành một bit mã độc lập. Để cho vui, tôi đã cố gắng làm cho nó nhỏ gọn nhất có thể. Một số có thể thấy rằng nó thanh lịch, những người khác có thể nghĩ rằng nó tối nghĩa. Dù sao, đây là:

var fact;
(fact = function(n){
    if ((n = parseInt(n)) < 0 || isNaN(n)) throw "Must be non-negative number";
    var cache = fact.cache, i = cache.length - 1;
    while (i < n) cache.push(cache[i++] * i);
    return cache[n];
}).cache = [1];

Bạn có thể điền trước bộ nhớ cache hoặc cho phép nó được lấp đầy khi các cuộc gọi diễn ra. Nhưng phần tử ban đầu (thực tế là (0) phải có mặt nếu không nó sẽ bị hỏng.

Thưởng thức :)


4

Nó rất đơn giản bằng cách sử dụng ES6

const factorial = n => n ? (n * factorial(n-1)) : 1;

Xem ví dụ ở đây


4

Đây là một giải pháp:

function factorial(number) {
  total = 1
  while (number > 0) {
    total *= number
    number = number - 1
  }
  return total
}

4

Sử dụng ES6, bạn có thể đạt được nó cả nhanh và ngắn:

const factorial = n => [...Array(n + 1).keys()].slice(1).reduce((acc, cur) => acc * cur, 1)

3

Mã để tính giai thừa phụ thuộc vào yêu cầu của bạn.

  1. Bạn có lo lắng về tràn?
  2. Bạn sẽ có phạm vi đầu vào nào?
  3. Điều quan trọng hơn đối với bạn là giảm thiểu kích thước hoặc thời gian?
  4. Bạn sẽ làm gì với giai thừa?

Về điểm 1 và 4, thường hữu ích hơn nếu có một chức năng đánh giá nhật ký của giai thừa trực tiếp hơn là có một chức năng để đánh giá chính giai thừa.

Đây là một bài đăng trên blog thảo luận về những vấn đề này. Đây là một số mã C # để tính toán giai thừa nhật ký mà sẽ rất nhỏ để chuyển sang JavaScript. Nhưng nó có thể không tốt nhất cho nhu cầu của bạn tùy thuộc vào câu trả lời của bạn cho các câu hỏi trên.


Danh sách được đánh số có lẽ nên ở trong phần bình luận. Tất cả những gì còn lại là hai liên kết và các câu trả lời chỉ có liên kết không được khuyến khích.
Barett

3

Đây là phiên bản dựa trên vòng lặp nhỏ gọn

function factorial( _n )
{
    var _p = 1 ;
    while( _n > 0 ) { _p *= _n-- ; }
    return _p ;
}

Hoặc bạn có thể ghi đè đối tượng Math (phiên bản đệ quy):

Math.factorial = function( _x )  { return _x <= 1 ? 1 : _x * Math.factorial( --_x ) ; }

Hoặc tham gia cả hai cách tiếp cận ...


1
Tôi đã sửa nó bên trong đoạn mã trên. Cảm ơn bạn!
Sandro Rosa

3

Khai thác thực tế rằng Number.MAX_VALUE < 171!, chúng ta có thể chỉ cần sử dụng một bảng tra cứu hoàn chỉnh chỉ bao gồm 171 phần tử mảng nhỏ gọn chiếm ít hơn 1,4 kilobyte bộ nhớ.

Một hàm tra cứu nhanh với độ phức tạp thời gian chạy O (1) và tổng chi phí truy cập mảng tối thiểu khi đó sẽ trông như sau:

// Lookup table for n! for 0 <= n <= 170:
const factorials = [1,1,2,6,24,120,720,5040,40320,362880,3628800,39916800,479001600,6227020800,87178291200,1307674368e3,20922789888e3,355687428096e3,6402373705728e3,121645100408832e3,243290200817664e4,5109094217170944e4,1.1240007277776077e21,2.585201673888498e22,6.204484017332394e23,1.5511210043330986e25,4.0329146112660565e26,1.0888869450418352e28,3.0488834461171387e29,8.841761993739702e30,2.6525285981219107e32,8.222838654177922e33,2.631308369336935e35,8.683317618811886e36,2.9523279903960416e38,1.0333147966386145e40,3.7199332678990125e41,1.3763753091226346e43,5.230226174666011e44,2.0397882081197444e46,8.159152832478977e47,3.345252661316381e49,1.40500611775288e51,6.041526306337383e52,2.658271574788449e54,1.1962222086548019e56,5.502622159812089e57,2.5862324151116818e59,1.2413915592536073e61,6.082818640342675e62,3.0414093201713376e64,1.5511187532873822e66,8.065817517094388e67,4.2748832840600255e69,2.308436973392414e71,1.2696403353658276e73,7.109985878048635e74,4.0526919504877214e76,2.3505613312828785e78,1.3868311854568984e80,8.32098711274139e81,5.075802138772248e83,3.146997326038794e85,1.98260831540444e87,1.2688693218588417e89,8.247650592082472e90,5.443449390774431e92,3.647111091818868e94,2.4800355424368305e96,1.711224524281413e98,1.1978571669969892e100,8.504785885678623e101,6.1234458376886085e103,4.4701154615126844e105,3.307885441519386e107,2.48091408113954e109,1.8854947016660504e111,1.4518309202828587e113,1.1324281178206297e115,8.946182130782976e116,7.156945704626381e118,5.797126020747368e120,4.753643337012842e122,3.945523969720659e124,3.314240134565353e126,2.81710411438055e128,2.4227095383672734e130,2.107757298379528e132,1.8548264225739844e134,1.650795516090846e136,1.4857159644817615e138,1.352001527678403e140,1.2438414054641308e142,1.1567725070816416e144,1.087366156656743e146,1.032997848823906e148,9.916779348709496e149,9.619275968248212e151,9.426890448883248e153,9.332621544394415e155,9.332621544394415e157,9.42594775983836e159,9.614466715035127e161,9.90290071648618e163,1.0299016745145628e166,1.081396758240291e168,1.1462805637347084e170,1.226520203196138e172,1.324641819451829e174,1.4438595832024937e176,1.588245541522743e178,1.7629525510902446e180,1.974506857221074e182,2.2311927486598138e184,2.5435597334721877e186,2.925093693493016e188,3.393108684451898e190,3.969937160808721e192,4.684525849754291e194,5.574585761207606e196,6.689502913449127e198,8.094298525273444e200,9.875044200833601e202,1.214630436702533e205,1.506141741511141e207,1.882677176888926e209,2.372173242880047e211,3.0126600184576594e213,3.856204823625804e215,4.974504222477287e217,6.466855489220474e219,8.47158069087882e221,1.1182486511960043e224,1.4872707060906857e226,1.9929427461615188e228,2.6904727073180504e230,3.659042881952549e232,5.012888748274992e234,6.917786472619489e236,9.615723196941089e238,1.3462012475717526e241,1.898143759076171e243,2.695364137888163e245,3.854370717180073e247,5.5502938327393044e249,8.047926057471992e251,1.1749972043909107e254,1.727245890454639e256,2.5563239178728654e258,3.80892263763057e260,5.713383956445855e262,8.62720977423324e264,1.3113358856834524e267,2.0063439050956823e269,3.0897696138473508e271,4.789142901463394e273,7.471062926282894e275,1.1729568794264145e278,1.853271869493735e280,2.9467022724950384e282,4.7147236359920616e284,7.590705053947219e286,1.2296942187394494e289,2.0044015765453026e291,3.287218585534296e293,5.423910666131589e295,9.003691705778438e297,1.503616514864999e300,2.5260757449731984e302,4.269068009004705e304,7.257415615307999e306];

// Lookup function:
function factorial(n) {
  return factorials[n] || (n > 170 ? Infinity : NaN);
}

// Test cases:
console.log(factorial(NaN));       // NaN
console.log(factorial(-Infinity)); // NaN
console.log(factorial(-1));        // NaN
console.log(factorial(0));         // 1
console.log(factorial(170));       // 7.257415615307999e+306 < Number.MAX_VALUE
console.log(factorial(171));       // Infinity > Number.MAX_VALUE
console.log(factorial(Infinity));  // Infinity

Điều này chính xác và nhanh nhất khi sử dụng Number kiểu dữ liệu. Tính toán bảng tra cứu trong Javascript - như một số câu trả lời khác đề xuất - sẽ làm giảm độ chính xác khi n! > Number.MAX_SAFE_INTEGER.

Nén bảng thời gian chạy qua gzip làm giảm kích thước của nó trên đĩa từ khoảng 3,6 xuống 1,8 kilobyte.


3

Câu trả lời một dòng:

const factorial = (num, accumulator) => num <= 1 ? accumulator || 1 : factorial(--num, num * (accumulator || num + 1));

factorial(5); // 120
factorial(10); // 3628800
factorial(3); // 6
factorial(7); // 5040
// et cetera


3

Giai thừa lặp lại với BigIntđể an toàn

Giải pháp sử dụng BigInt, một tính năng của ES 2018 + / 2019.

Đây là ví dụ hoạt động được sử dụng BigInt, vì nhiều câu trả lời ở đây đều thoát khỏi ranh giới an toàn của Number(MDN) gần như ngay lập tức. Nó không phải là nhanh nhất nhưng nó đơn giản và do đó rõ ràng hơn để điều chỉnh các tối ưu hóa khác (như bộ nhớ cache của 100 số đầu tiên).

function factorial(nat) {
   let p = BigInt(1)
   let i = BigInt(nat)

   while (1 < i--) p *= i

   return p
}

Ví dụ sử dụng

// 9.332621544394415e+157
Number(factorial(100))

// "933262154439441526816992388562667004907159682643816214685929638952175999
//  932299156089414639761565182862536979208272237582511852109168640000000000
//  00000000000000"
String(factorial(100))

// 9332621544394415268169923885626670049071596826438162146859296389521759999
// 3229915608941463976156518286253697920827223758251185210916864000000000000
// 000000000000n
factorial(100)
  • ncuối một ký tự số như 1303ncho biết đó là một BigIntloại.
  • Hãy nhớ rằng bạn không nên kết hợp BigIntvới Numbertrừ khi bạn ép buộc chúng một cách rõ ràng và làm như vậy có thể làm mất độ chính xác.

3

Sử dụng các tính năng của ES6, có thể viết mã trên MỘT dòng & không cần đệ quy :

var factorial=(n)=>Array.from({length: n},(v, k) => k+1).reduce((a, b) => a*b, 1)


2

Chỉ để hoàn thiện, đây là một phiên bản đệ quy sẽ cho phép tối ưu hóa cuộc gọi đuôi. Tuy nhiên, tôi không chắc liệu tối ưu hóa cuộc gọi đuôi có được thực hiện trong JavaScript hay không ..

function rFact(n, acc)
{
    if (n == 0 || n == 1) return acc; 
    else return rFact(n-1, acc*n); 
}

Để gọi nó:

rFact(x, 1);

ES6 hỗ trợ TCO, nhưng có thể tính năng này không hoạt động theo mặc định trong bất kỳ công cụ chính nào
le_m

2

Đây là một giải pháp lặp lại sử dụng ít không gian ngăn xếp hơn và lưu các giá trị đã tính toán trước đó theo cách tự ghi nhớ:

Math.factorial = function(n){
    if(this.factorials[n]){ // memoized
        return this.factorials[n];
    }
    var total=1;
    for(var i=n; i>0; i--){
        total*=i;
    }
    this.factorials[n] = total; // save
    return total;
};
Math.factorials={}; // store

Cũng lưu ý rằng tôi đang thêm cái này vào đối tượng Math, nó là một đối tượng theo nghĩa đen nên không có nguyên mẫu. Thay vì chỉ ràng buộc chúng với hàm một cách trực tiếp.


Điều này không thực sự tận dụng hết lợi thế của việc ghi nhớ cho các bài toán con - ví dụ, Math.factorial(100); Math.factorial(500);sẽ tính toán 1..100 phép nhân hai lần.
Barett

2

Tôi tin rằng phần sau là đoạn mã bền vững và hiệu quả nhất từ ​​các nhận xét ở trên. Bạn có thể sử dụng nó trong kiến ​​trúc js ứng dụng toàn cầu của mình ... và đừng lo lắng về việc viết nó trong nhiều không gian tên (vì nó là một nhiệm vụ có thể không cần bổ sung nhiều). Tôi đã bao gồm 2 tên phương thức (dựa trên tùy chọn) nhưng cả hai đều có thể được sử dụng vì chúng chỉ là tham chiếu.

Math.factorial = Math.fact = function(n) {
    if (isNaN(n)||n<0) return undefined;
    var f = 1; while (n > 1) {
        f *= n--;
    } return f;
};

Bằng cách bắt đầu nhân của bạn với n * (n-1) * (n-2) * ... * 1thay cho chiều ngược lại, bạn mất lên đến 4 chữ số trong độ chính xác cho n >> 20.
le_m

2
// if you don't want to update the Math object, use `var factorial = ...`
Math.factorial = (function() {
    var f = function(n) {
        if (n < 1) {return 1;}  // no real error checking, could add type-check
        return (f[n] > 0) ? f[n] : f[n] = n * f(n -1);
    }
    for (i = 0; i < 101; i++) {f(i);} // precalculate some values
    return f;
}());

factorial(6); // 720, initially cached
factorial[6]; // 720, same thing, slightly faster access, 
              // but fails above current cache limit of 100
factorial(100); // 9.33262154439441e+157, called, but pulled from cache
factorial(142); // 2.6953641378881614e+245, called
factorial[141]; // 1.89814375907617e+243, now cached

Điều này thực hiện lưu trữ nhanh 100 giá trị đầu tiên và không đưa một biến bên ngoài vào phạm vi cho bộ nhớ cache, lưu trữ các giá trị dưới dạng thuộc tính của chính đối tượng hàm, có nghĩa là nếu bạn biết factorial(n)đã được tính toán, bạn có thể chỉ đơn giản là gọi nó là factorial[n], hiệu quả hơn một chút. Việc chạy 100 giá trị đầu tiên này sẽ mất thời gian dưới mili giây trong các trình duyệt hiện đại.


Tôi đã tìm ra điều đó sau 21! những con số không đáng tin cậy.
AutoSponge

@AutoSponge Đó là bởi vì 21! > Number.MAX_SAFE_INTEGER, do đó, không thể được biểu diễn một cách an toàn dưới dạng một phao 64-bit.
le_m Ngày


2

Đây là số do tôi tự làm, không sử dụng các số trên 170 hoặc dưới 2.

function factorial(x){
 if((!(isNaN(Number(x)))) && (Number(x)<=170) && (Number(x)>=2)){
  x=Number(x);for(i=x-(1);i>=1;--i){
   x*=i;
  }
 }return x;
}

Bằng cách bắt đầu phép nhân của bạn với n * (n-1) * (n-2) * ... * 1 thay vì làm theo cách khác, bạn mất tới 4 chữ số chính xác cho n >> 20. Ngoài ra, tạo ra một biến toàn cục ivà thực hiện quá nhiều Numberchuyển đổi và đưa ra kết quả không chính xác cho 0! (như bạn đã nêu, nhưng tại sao?).
le_m

2

Đây là mã của tôi

function factorial(num){
    var result = num;
    for(i=num;i>=2;i--){
        result = result * (i-1);
    }
    return result;
}

1
Nếu (n> 170) e = Vô cực. Và mã của bạn sẽ tạo ra một số lượng lớn. sẽ không có bất kỳ tràn?
chính

Kết quả không chính xác cho factorial(0). Ngoài ra, bằng cách bắt đầu phép nhân của bạn với n * (n-1) * (n-2) * ... * 1 thay vì làm theo cách khác, bạn có độ chính xác lên đến 4 chữ số cho n >> 20. @prime: 170! > Number.MAX_VALUEvà được thể hiện tốt nhất với Infinity.
le_m

2

Vòng lặp được lưu trong bộ nhớ cache phải nhanh nhất (ít nhất khi được gọi nhiều lần)

var factorial = (function() {
  var x =[];

  return function (num) {
    if (x[num] >0) return x[num];
    var rval=1;
    for (var i = 2; i <= num; i++) {
        rval = rval * i;
        x[i] = rval;
    }
    return rval;
  }
})();

2
function isNumeric(n) {
    return !isNaN(parseFloat(n)) && isFinite(n)
}

Được cung cấp bởi http://javascript.info/tutorial/number-math như một cách đơn giản để đánh giá xem một đối tượng có phải là một số nguyên thích hợp để tính toán hay không.

var factorials=[[1,2,6],3];

Một tập hợp các thừa số được ghi nhớ đơn giản yêu cầu các phép tính dư thừa, có thể được xử lý bằng "nhân với 1" hoặc là một chữ số là một phương trình đơn giản không đáng để xử lý trực tiếp.

var factorial = (function(memo,n) {
    this.memomize = (function(n) {
        var ni=n-1;
        if(factorials[1]<n) {
            factorials[0][ni]=0;
            for(var factorial_index=factorials[1]-1;factorials[1]<n;factorial_index++) {
                factorials[0][factorials[1]]=factorials[0][factorial_index]*(factorials[1]+1);
                factorials[1]++;
            }
        }
    });
    this.factorialize = (function(n) {
        return (n<3)?n:(factorialize(n-1)*n);
    });
    if(isNumeric(n)) {
        if(memo===true) {
            this.memomize(n);
            return factorials[0][n-1];
        }
        return this.factorialize(n);
    }
    return factorials;
});

Sau khi xem xét đầu vào từ các thành viên khác (không bao gồm lời khuyên Log, mặc dù tôi có thể thực hiện điều đó sau này), tôi đã tiếp tục và tập hợp một tập lệnh khá đơn giản. Tôi bắt đầu với một ví dụ OOP JavaScript đơn giản chưa được đào tạo và xây dựng một lớp nhỏ để xử lý các giai thừa. Sau đó, tôi đã triển khai phiên bản Ghi nhớ được đề xuất ở trên. Tôi cũng đã triển khai Giai thừa viết tắt tuy nhiên tôi đã thực hiện một điều chỉnh lỗi nhỏ; Tôi đã thay đổi "n <2" thành "n <3". "n <2" sẽ vẫn xử lý n = 2, điều này sẽ lãng phí, bởi vì bạn sẽ lặp lại cho một 2 * 1 = 2; đây là một sự lãng phí theo quan điểm của tôi. Tôi đã thay đổi nó thành "n <3"; bởi vì nếu n là 1 hoặc 2 nó sẽ trả về n đơn giản, nếu nó là 3 hoặc nhiều hơn nó sẽ đánh giá bình thường. Tất nhiên khi các quy tắc áp dụng, tôi đã đặt các hàm của mình theo thứ tự giảm dần của việc thực thi giả định. Tôi đã thêm vào tùy chọn bool (true | false) để cho phép thay đổi nhanh chóng giữa thực thi đã ghi nhớ và thực thi bình thường (Bạn không bao giờ biết khi nào bạn muốn hoán đổi trên trang của mình mà không cần thay đổi "kiểu") Như tôi đã nói trước đây biến thừa số được ghi nhớ được đặt với 3 vị trí bắt đầu, lấy 4 ký tự và giảm thiểu các phép tính lãng phí. Mọi thứ sau lần lặp thứ ba, bạn đang xử lý phép toán cộng hai chữ số. Tôi nghĩ nếu bạn có đủ một stickler về nó, bạn sẽ chạy trên một bảng giai thừa (như được triển khai). lấy 4 ký tự và giảm thiểu các phép tính lãng phí. Mọi thứ sau lần lặp thứ ba, bạn đang xử lý phép toán cộng hai chữ số. Tôi nghĩ nếu bạn có đủ một stickler về nó, bạn sẽ chạy trên một bảng giai thừa (như được triển khai). lấy 4 ký tự và giảm thiểu các phép tính lãng phí. Mọi thứ sau lần lặp thứ ba, bạn đang xử lý phép toán cộng hai chữ số. Tôi nghĩ nếu bạn có đủ một stickler về nó, bạn sẽ chạy trên một bảng giai thừa (như được triển khai).

Tôi đã lên kế hoạch gì sau đây? lưu trữ phiên cục bộ & | để cho phép lưu trữ từng trường hợp trong bộ nhớ cache của các lần lặp cần thiết, về cơ bản xử lý vấn đề "bảng" đã nói ở trên. Điều này cũng sẽ tiết kiệm hàng loạt cơ sở dữ liệu và không gian phía máy chủ. Tuy nhiên, nếu bạn sử dụng localStorage, về cơ bản, bạn sẽ hút không gian trên máy tính của người dùng chỉ để lưu trữ danh sách các số và làm cho màn hình của họ XEM nhanh hơn, tuy nhiên trong một khoảng thời gian dài với nhu cầu lớn thì điều này sẽ chậm lại. Tôi đang nghĩ rằng sessionStorage (xóa sau khi Tab rời đi) sẽ là một lộ trình tốt hơn nhiều. Có thể kết hợp điều này với một máy chủ tự cân bằng / bộ nhớ cache phụ thuộc cục bộ không? Người dùng A cần X lần lặp. Người dùng B cần lặp lại Y. X + Y / 2 = Số lượng cần thiết được lưu trong bộ nhớ cache cục bộ. Sau đó, chỉ cần phát hiện và xử lý các điểm chuẩn thời gian tải và thời gian thực thi trực tiếp cho mọi người dùng cho đến khi nó tự điều chỉnh để tối ưu hóa cho chính trang web. Cảm ơn!

Chỉnh sửa 3:

var f=[1,2,6];
var fc=3;
var factorial = (function(memo) {
    this.memomize = (function(n) {
        var ni=n-1;
        if(fc<n) {
            for(var fi=fc-1;fc<n;fi++) {
                f[fc]=f[fi]*(fc+1);
                fc++;
            }
        }
        return f[ni];
    });

    this.factorialize = (function(n) {
        return (n<3)?n:(factorialize(n-1)*n);
    });

    this.fractal = (function (functio) {
        return function(n) {
            if(isNumeric(n)) {
                return functio(n);
            }
            return NaN;
        }
    });

    if(memo===true) {
        return this.fractal(memomize);
    }
    return this.fractal(factorialize);
});

Chỉnh sửa này thực hiện một đề xuất Stack khác và cho phép tôi gọi hàm là giai thừa (true) (5), là một trong những mục tiêu của tôi đã đề ra. : 3 Tôi cũng đã loại bỏ một số chỉ định không cần thiết và viết tắt một số tên biến không công khai.


Trả về undefinedcho 0 !. ES6 cho phép thay thế isNumericbằng Number.isInteger. Những dòng như thế factorials[0][factorials[1]]=factorials[0][factorial_index]*(factorials[1]+1);là hoàn toàn không thể đọc được.
le_m Ngày

2

Đây là một trong những sử dụng các hàm javascript mới hơn điền , ánh xạ , giảm và hàm tạo (và cú pháp mũi tên béo):

Math.factorial = n => n === 0 ? 1 : Array(n).fill(null).map((e,i)=>i+1).reduce((p,c)=>p*c)

Chỉnh sửa: cập nhật để xử lý n === 0


2
Đó là một dòng mã không thể đọc được.
jungledev

1
Đó là một ý tưởng gọn gàng. Thay vì duyệt chiều dài hai lần, tại sao không chuyển đổi tất cả logic thành hàm giảm và sử dụng giá trị ban đầu của nó để xử lý trường hợp cạnh n === 0? Math.factorial = n => Array.from({ length: n }).reduce((product, _, i) => product * (i + 1), 1)
AlexSashaRegan

2
function computeFactorialOfN(n) {
  var output=1;
  for(i=1; i<=n; i++){
    output*=i;
  } return output;
}
computeFactorialOfN(5);

2
Chào mừng bạn đến với StackOverflow và cảm ơn sự giúp đỡ của bạn. Bạn có thể muốn làm cho câu trả lời của mình tốt hơn nữa bằng cách thêm một số giải thích.
Elias MP,
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.