Czy istnieje metoda obliczania silni w języku Java?


105

Jeszcze go nie znalazłem. Przegapiłem coś? Wiem, że metoda silnia to typowy przykładowy program dla początkujących. Ale czy nie byłoby przydatne posiadanie standardowej implementacji do ponownego wykorzystania? Mógłbym użyć takiej metody ze standardowymi typami (np. Int, long ...), a także z BigInteger / BigDecimal.

Odpowiedzi:


26

Nie sądzę, żeby była przydatna funkcja biblioteki dla silni. Istnieje wiele badań dotyczących skutecznych wdrożeń czynnikowych. Oto kilka wdrożeń.


188
Dlaczego nie warto mieć funkcji bibliotecznej dla silni?
północy

17
Niewielu ludzi faktycznie potrzebuje silni w prawdziwym kodzie. Jeśli tak, prawdopodobnie wykonujesz zaawansowane obliczenia matematyczne lub statystyki, w którym to przypadku najprawdopodobniej będziesz już używać biblioteki matematycznej ze specjalistyczną implementacją silni.
mikera

3
Funkcja gamma jest bardzo przydatna, dlatego jest zawarta w standardowej bibliotece C ++.
Columbo

2
Wydaje mi się, że @KarlthePagan oznacza, że ​​nie warto mieć standardowej funkcji bibliotecznej dla silni - czy to prawda?
dantiston

więc na rozmowie kwalifikacyjnej pytaliby Cię, czy możesz to zrobić sam * moja sprawa
moldovean

59

Apache Commons Math ma kilka metod silni w klasie MathUtils .


1
Tak. Dobry towar. Istnieje implementacja silni dla liczb zmiennoprzecinkowych i niepłaskich (MathUtils.factorial (int) i MathUtils.factorialDouble (int)), a także użyteczny logarytm naturalny n! (MathUtils.factorialLog (int))
Tomasz Błachowicz

3
jest w tej chwili w ArithmeticUtils.
MarianP

6
ArithmeticUtils.factorial jest teraz najwyraźniej przestarzały, atm używa CombinatoricsUtils.factorial
Victor Häggqvist

Nie używaj linków! Zwykle znikają (jak WSZYSTKIE z nich).
SMBiggs

1
@ScottBiggs Oba linki w odpowiedzi działają poprawnie.
Bill the Lizard

40
public class UsefulMethods {
    public static long factorial(int number) {
        long result = 1;

        for (int factor = 2; factor <= number; factor++) {
            result *= factor;
        }

        return result;
    }
}

Wersja Big Numbers HoldOffHunger :

public static BigInteger factorial(BigInteger number) {
    BigInteger result = BigInteger.valueOf(1);

    for (long factor = 2; factor <= number.longValue(); factor++) {
        result = result.multiply(BigInteger.valueOf(factor));
    }

    return result;
}

Zakładasz również, że chcesz silnię liczby całkowitej
Andrew

To rozwiązanie powinno korzystać z klasy BigInteger.
Oleg Abrazhaev

2
Wersja Big Numbers: public static BigInteger silnia (BigInteger n) {BigInteger factorial = BigInteger.valueOf (1); for (int i = 1; i <= n.intValue (); i ++) {factorial = factorial.multiply (BigInteger.valueOf (i)); } powrót silnia; }
HoldOffHunger

23

Nagie silnie nagie są w praktyce rzadko potrzebne. Najczęściej będziesz potrzebować jednego z poniższych:

1) podzielić jedną silnię przez drugą, lub

2) przybliżona odpowiedź zmiennoprzecinkowa.

W obu przypadkach lepiej byłoby zastosować proste, niestandardowe rozwiązania.

W przypadku (1) powiedzmy, że x = 90! / 85!, Wtedy obliczysz wynik jako x = 86 * 87 * 88 * 89 * 90, bez konieczności trzymania 90! w pamięci :)

W przypadku (2) wyszukaj w Google „przybliżenie Stirlinga”.


3
Kontrprzykład: Obliczanie liczby permutacji z N elementami wymaga samej silni i jest potrzebne, jeśli chcesz przydzielić strukturę do przechowywania permutacji.
Mark Jeronimus

Słuszna uwaga! Moje pierwsze pytanie brzmiało, czy 90! / 85! uproszczony ze względu na wspólny mianownik 5, ale w rzeczywistości był to wspólny mianownik 85 !. 90! / 85! = 90 * 89 * 88 * 87 * 86 * 85! / 85 !. Widać to wyraźniej w tej równości.
HoldOffHunger


6

Chociaż silnia jest przyjemnym ćwiczeniem dla początkującego programisty, w większości przypadków nie jest zbyt przydatna i każdy wie, jak napisać funkcję silnię, więc zazwyczaj nie ma ich w przeciętnej bibliotece.


6
Zgadzam się z tobą, są ważniejsze funkcje matematyczne. Ale moim zdaniem ta metoda powinna być standardowa, aby ludzie mogli ją ponownie wykorzystać. Nie ma potrzeby wielokrotnego wdrażania go przez wiele osób. Może to zrobić w celach edukacyjnych. Ale do codziennej pracy jest przestarzały. To jest moja opinia. W każdym razie dziękuję za odpowiedź. Zrobię to sam - innym razem.

Jakie są korzyści z proponowanej przez Pana standaryzacji? Dodawanie metod do standardowej biblioteki nie jest pozbawione kosztów. Jak zauważyli inni, nie ma jednego najlepszego rozwiązania. Który z nich proponujesz wbudować w język? Posiadanie metody w bibliotece standardowej nie zaoszczędzi czasu na zrozumienie problemu, a kiedy już to zrobisz, równie dobrze możesz wybrać implementację, która najlepiej nadaje się do tego zadania.
Matt G

2
„... i każdy wie, jak napisać funkcję silni” chaosinmotion.com/blog/?p=622
James P.

4
Nie zgadzać się. Silniki są wymagane w przypadku kombinatoryki , co jest potrzebne w wielu obszarach projektowania oprogramowania. Argument brak silni we wbudowanej bibliotece matematycznej jest tym samym argumentem, co brak wbudowanej biblioteki matematycznej.
LateralFractal

Cóż za wybitna logika. Absolutnie gwiezdny. Szkoda, że ​​projektanci klas java.lang.Math nie zdawali sobie z tego sprawy, dołączając metody abs () do tej biblioteki.
Igor Soudakevitch

6

uważam, że byłby to najszybszy sposób, przy użyciu tabeli odnośników:

private static final long[] FACTORIAL_TABLE = initFactorialTable();
private static long[] initFactorialTable() {
    final long[] factorialTable = new long[21];
    factorialTable[0] = 1;
    for (int i=1; i<factorialTable.length; i++)
        factorialTable[i] = factorialTable[i-1] * i;
    return factorialTable;
}
/**
 * Actually, even for {@code long}, it works only until 20 inclusively.
 */
public static long factorial(final int n) {
    if ((n < 0) || (n > 20))
        throw new OutOfRangeException("n", 0, 20);
    return FACTORIAL_TABLE[n];
}

W przypadku typu natywnego long(8 bajtów) może pomieścić tylko do20!

20! = 2432902008176640000(10) = 0x 21C3 677C 82B4 0000

Oczywiście 21!spowoduje przepełnienie.

Dlatego w przypadku typu natywnego dozwolona jest longtylko maksymalna liczba 20!, znacząca i poprawna.


1
Całkiem niezły pomysł. biorąc pod uwagę, że silnia pierwszych 20 jest prawdopodobnie wystarczająca, dodałbym statyczne stałe (nie ma potrzeby obliczania ich za każdym razem, gdy aplikacja jest uruchamiana) do klasy Math z podanymi danymi. Fakt, że niewielu ludzi potrzebuje silni w swoim kodzie, jest kiepską wymówką, aby nie wspierać jej na zajęciach z matematyki.
TG

6

Ponieważ silnia rośnie tak szybko, przepełnienie stosu nie stanowi problemu, jeśli używasz rekursji. W rzeczywistości wartość 20! jest największym, jaki można przedstawić w języku Java. Zatem poniższa metoda albo obliczy silnię (n), albo zgłosi wyjątek IllegalArgumentException, jeśli n jest za duże.

public long factorial(int n) {
    if (n > 20) throw new IllegalArgumentException(n + " is out of range");
    return (1 > n) ? 1 : n * factorial(n - 1);
}

Innym (fajniejszym) sposobem na zrobienie tego samego jest użycie biblioteki strumieniowej Java 8 w następujący sposób:

public long factorial(int n) {
    if (n > 20) throw new IllegalArgumentException(n + " is out of range");        
    return LongStream.rangeClosed(1, n).reduce(1, (a, b) -> a * b);
}

Przeczytaj więcej na temat silni przy użyciu strumieni Java 8



6

Krótka odpowiedź brzmi: użyj rekurencji.

Możesz utworzyć jedną metodę i wywołać ją rekurencyjnie bezpośrednio w tej samej metodzie:

public class factorial {

    public static void main(String[] args) {
        System.out.println(calc(10));
    }

    public static long calc(long n) {
        if (n <= 1)
            return 1;
        else
            return n * calc(n - 1);
    }
}

5
funkcje rekurencyjne są fajne, ale jeśli ktoś spróbuje policzyć naprawdę duże fatiorial, skończy ze StackOverflowException;) + Nie jestem pewien, ale myślę, że rekurencja jest wolniejsza niż stara dobra metoda pętli;)
TG

skąd możesz wiedzieć, że skończy się na wyjątku stackoverflow? @TG
gumuruh

2
To jest proste. każdy rekurencyjnie umieszcza bieżące miejsce na stosie, więc program będzie miał 'pamięć' miejsca, do którego ma wrócić po zakończeniu wywołania metody. Stos ma swoje ograniczenia. aby spróbować samemu, wypróbuj powyższy kod, zmień się System.out.println(calc(10));na System.out.println(calc(Long.MAX_VALUE));dość długi wyścig :)
TG

@TG Żeby było jasne, wypróbowałem moją metodę rekurencyjną, z którą działa BigInteger. Próbowałem obliczyć silnię liczby 8020, która dała mi wynik 613578884952214809325384..., który ma 27831miejsca po przecinku. Więc nawet podczas pracy z tak dużymi liczbami nie Stackoverflowzostanie wyrzucone. Oczywiście youre prawo, ale wątpię, że istnieją duże numery z faktycznie z niego korzystać :-)
Moritz Schmidt

3

Spróbuj tego

public static BigInteger factorial(int value){
    if(value < 0){
        throw new IllegalArgumentException("Value must be positive");
    }

    BigInteger result = BigInteger.ONE;
    for (int i = 2; i <= value; i++) {
        result = result.multiply(BigInteger.valueOf(i));
    }

    return result;
}

3
Uważam, że w pętli for jest błąd: powinien i <= value. Pętla for może zostać nieco zoptymalizowana do (int i = 2; i <= value; i++).
Chris

2

Znalazłem niesamowitą sztuczkę, aby znaleźć silnie w zaledwie połowie rzeczywistych mnożeń.

Prosimy o cierpliwość, ponieważ jest to trochę długi post.

Dla liczb parzystych: Aby zmniejszyć o połowę mnożenie z liczbami parzystymi, otrzymasz n / 2 współczynniki. Pierwszym czynnikiem będzie liczba, dla której bierzesz silnię, a następnym będzie ta liczba plus ta liczba minus dwa. Następna liczba będzie poprzednią liczbą plus ostatnio dodaną liczbę minus dwa. Skończysz, gdy ostatnią dodaną liczbą było dwa (tj. 2) . To prawdopodobnie nie miało większego sensu, więc pozwólcie, że dam wam przykład.

8! = 8 * (8 + 6 = 14) * (14 + 4 = 18) * (18 + 2 = 20)

8! = 8 * 14 * 18 * 20 which is **40320** 

Zauważ, że zacząłem od 8, potem pierwsza dodana liczba to 6, potem 4, potem 2, każda dodana liczba była o dwa mniej niż liczba dodana przed nią. Ta metoda jest równoważna pomnożeniu najmniejszych liczb przez największe liczby, tylko z mniejszym mnożeniem, na przykład:

8! = 1 * 2 * 3 * 4 * 5 * 6 * 7 * 
8! = (1 * 8) * (2 * 7) * (3 * 6) * (4 * 5)
8! = 8 * 14 * 18 * 20

Czy to nie proste :)

Teraz dla liczb nieparzystych: Jeśli liczba jest nieparzysta, dodawanie jest takie samo, jak w przypadku każdego odejmowania dwóch, ale zatrzymuje się na trzech. Jednak liczba czynników się zmienia. Jeśli podzielisz liczbę przez dwa, otrzymasz pewną liczbę kończącą się na .5. Powodem jest to, że jeśli pomnożymy razem końce, pozostanie nam środkowa liczba. Zasadniczo to wszystko można rozwiązać, rozwiązując kilka czynników równych liczbie podzielonej przez dwa, zaokrąglając w górę. To prawdopodobnie nie miało większego sensu również dla umysłów bez matematycznego przygotowania, więc pozwól mi zrobić przykład:

9! = 9 * (9 + 7 = 16) * (16 + 5 = 21) * (21 + 3 = 24) * (roundUp(9/2) = 5)

9! = 9 * 16 * 21 * 24 * 5 = **362880**

Uwaga: Jeśli nie podoba ci się ta metoda, możesz po prostu wziąć silnię liczby parzystej przed nieparzystą (w tym przypadku osiem) i pomnożyć ją przez liczbę nieparzystą (tj. 9! = 8! * 9).

Teraz zaimplementujmy to w Javie:

public static int getFactorial(int num)
{
    int factorial=1;
    int diffrennceFromActualNum=0;
    int previousSum=num;

    if(num==0) //Returning  1 as factorial if number is 0 
        return 1;
    if(num%2==0)//  Checking if Number is odd or even
    { 
        while(num-diffrennceFromActualNum>=2)
        {
            if(!isFirst)
            {
                previousSum=previousSum+(num-diffrennceFromActualNum);  
            }
            isFirst=false;
            factorial*=previousSum;
            diffrennceFromActualNum+=2;
        }
    }
    else // In Odd Case (Number * getFactorial(Number-1))
    {
        factorial=num*getFactorial(num-1);
    }
    return factorial;
}

isFirstjest zmienną logiczną zadeklarowaną jako statyczna; jest używany w pierwszym przypadku, w którym nie chcemy zmieniać poprzedniej sumy.

Spróbuj z parzystymi i nieparzystymi liczbami.


2

Możesz użyć rekursji.

public static int factorial(int n){    
      if (n == 0)    
        return 1;    
      else    
        return(n * factorial(n-1));    
     }

a następnie po utworzeniu metody (funkcji) powyżej:

System.out.println(factorial(number of your choice));  
    //direct example
    System.out.println(factorial(3));

1

Jedynym zastosowaniem biznesowym silni, o jakim przychodzi mi do głowy, są formuły Erlang B i Erlang C, a nie każdy pracuje w call center lub w firmie telefonicznej. Wydaje się, że użyteczność funkcji dla biznesu często decyduje o tym, co pojawia się w języku - spójrz na wszystkie operacje przetwarzania danych, XML i funkcje sieciowe w głównych językach.

Łatwo jest zachować silnię lub funkcję biblioteczną dla czegoś takiego w pobliżu.


1

Bardzo prosta metoda obliczania silni:

private double FACT(double n) {
    double num = n;
    double total = 1;
    if(num != 0 | num != 1){
        total = num;
    }else if(num == 1 | num == 0){
        total = 1;
    }
    double num2;
    while(num > 1){
        num2 = num - 1;
        total = total * num2;
        num = num - 1;
    }
    return total;
}

Użyłem double, ponieważ mogą pomieścić ogromne liczby, ale możesz użyć dowolnego innego typu, takiego jak int, long, float itp.

PS To może nie być najlepsze rozwiązanie, ale jestem nowy w kodowaniu i zajęło mi wieki znalezienie prostego kodu, który mógłby obliczyć silnię, więc musiałem sam napisać tę metodę, ale umieszczam to tutaj, aby pomóc innym ludziom takim jak ja .


1

Możesz również użyć wersji rekurencyjnej.

static int myFactorial(int i) {
    if(i == 1)
        return;
    else
        System.out.prinln(i * (myFactorial(--i)));
}

Rekursja jest zwykle mniej wydajna z powodu konieczności wypychania i przerywania rekurencji, więc iteracja jest szybsza. Z drugiej strony, wersje rekurencyjne wykorzystują mniej zmiennych lokalnych lub nie używają ich wcale, co jest zaletą.


1

Silnia jest silnie zwiększającą się funkcją dyskretną, więc myślę, że użycie BigInteger jest lepsze niż użycie int. Zaimplementowałem poniższy kod do obliczania silni nieujemnych liczb całkowitych, zamiast pętli zastosowałem rekurencję.

public  BigInteger factorial(BigInteger x){     
    if(x.compareTo(new BigInteger("1"))==0||x.compareTo(new BigInteger("0"))==0)
        return new BigInteger("1");
    else return x.multiply(factorial(x.subtract(new BigInteger("1")))); 
}

Oto zakres dużej liczby całkowitej

-2^Integer.MAX_VALUE (exclusive) to +2^Integer.MAX_VALUE,
where Integer.MAX_VALUE=2^31.

Jednak zakres metody silni podanej powyżej można rozszerzyć maksymalnie dwukrotnie, używając bez znaku BigInteger.


1

Mamy jedną linię do obliczenia:

Long factorialNumber = LongStream.rangeClosed(2, N).reduce(1, Math::multiplyExact);


1
    /**
import java liberary class

*/
import java.util.Scanner;

/* class to find factorial of a number
*/

public class factorial
{
public static void main(String[] args)
{

// scanner method for read keayboard values

    Scanner factor= new Scanner(System.in);

    int n;
    double total = 1;
    double sum= 1;

    System.out.println("\nPlease enter an integer: ");
    n = factor.nextInt();

// evaluvate the integer is greater than zero and calculate factorial

if(n==0)

{
    System.out.println(" Factorial of 0 is 1");
}
else if (n>0)
{
    System.out.println("\nThe factorial of " + n + " is " );

    System.out.print(n);

    for(int i=1;i<n;i++)
    {
        do // do while loop for display each integer in the factorial
              {
                System.out.print("*"+(n-i) );
              }

        while ( n == 1);

      total = total * i;

    }

// calculate factorial
sum= total * n;


// display sum of factorial

    System.out.println("\n\nThe "+ n +" Factorial is : "+" "+ sum);
}

// display invalid entry, if enter a value less than zero

else

{
    System.out.println("\nInvalid entry!!");

}System.exit(0);
}
}

0
public static int fact(int i){
    if(i==0)
       return 0;
    if(i>1){
       i = i * fact(--i);
    }

   return i;
}

1
Myślę, że OP pyta, czy w API jest funkcja, a nie jak ją napisać. Ponadto 0! = 1 - możesz chcieć zaktualizować swój kod, aby uwzględnić ten przypadek.
SL Barth - Przywróć Monikę

zawsze da wynik 0
Tom Brito

0

Musimy wdrażać iteracyjnie. Jeśli implementujemy rekurencyjnie, spowoduje to StackOverflow, jeśli dane wejściowe staną się bardzo duże (tj. 2 miliardy). I musimy użyć niezwiązanej liczby rozmiaru, takiej jak BigInteger, aby uniknąć przepełnienia arytmatycznego, gdy liczba silnia staje się większa niż maksymalna liczba danego typu (tj. 2 miliardy dla int). Możesz użyć int dla maksymalnie 14 silni i long dla maksymalnie 20 silni przed przepełnieniem.

public BigInteger getFactorialIteratively(BigInteger input) {
    if (input.compareTo(BigInteger.ZERO) <= 0) {
        throw new IllegalArgumentException("zero or negatives are not allowed");
    }

    BigInteger result = BigInteger.ONE;
    for (BigInteger i = BigInteger.ONE; i.compareTo(input) <= 0; i = i.add(BigInteger.ONE)) {
        result = result.multiply(i);
    }
    return result;
}

Jeśli nie możesz użyć BigInteger, dodaj sprawdzanie błędów.

public long getFactorialIteratively(long input) {
    if (input <= 0) {
        throw new IllegalArgumentException("zero or negatives are not allowed");
    } else if (input == 1) {
        return 1;
    }

    long prev = 1;
    long result = 0;
    for (long i = 2; i <= input; i++) {
        result = prev * i;
        if (result / prev != i) { // check if result holds the definition of factorial
            // arithmatic overflow, error out
            throw new RuntimeException("value "+i+" is too big to calculate a factorial, prev:"+prev+", current:"+result);
        }
        prev = result;
    }
    return result;
}

0
public int factorial(int num) {
        if (num == 1) return 1;
        return num * factorial(num - 1);
}

Powinien używać long lub BigInteger;)
AxelH

0

pętla while (dla małych liczb)

public class factorial {

public static void main(String[] args) {
    int counter=1, sum=1;

    while (counter<=10) {
        sum=sum*counter;
        counter++;
   }

    System.out.println("Factorial of 10 is " +sum);
   }
}

0

Mam to z EDX, użyj go! nazywa się to rekurencją

   public static int factorial(int n) {
    if (n == 1) {
        return 1;
    } else {
        return n * factorial(n-1);
    }
}

0

z rekurencją:

public static int factorial(int n)
{
    if(n == 1)
    {
        return 1;
    }               
    return n * factorial(n-1);
}

z pętlą while:

public static int factorial1(int n)
{
    int fact=1;
    while(n>=1)
    {
        fact=fact*n;
        n--;
    }
    return fact;
}

0

UŻYWANIE PROGRAMOWANIA DYNAMICZNEGO JEST WYDAJNE

jeśli chcesz go używać do wielokrotnych obliczeń (jak buforowanie)

Kod Java:

int fact[]=new int[n+1]; //n is the required number you want to find factorial for.
int factorial(int num)
 {
    if(num==0){
     fact[num]=1;
     return fact[num];
       }
     else
       fact[num]=(num)*factorial(num-1);

     return fact[num];
 }

0

użycie rekurencji jest najprostszą metodą. jeśli chcemy znaleźć silnię N, musimy rozważyć dwa przypadki, w których N = 1 i N> 1, ponieważ w silni mnożymy N, N-1, N-2 ,,,,, aż do 1. jeśli przejdź do N = 0 otrzymamy 0 za odpowiedź. aby zatrzymać silnię sięgającą zera, stosuje się następującą metodę rekurencyjną. Wewnątrz funkcji silni, gdy N> 1, zwracana wartość jest mnożona przez inną inicjację funkcji silni. spowoduje to, że kod będzie rekurencyjnie wywoływał silnię (), dopóki nie osiągnie N = 1. dla przypadku N = 1, zwraca ona samą N (= 1), a wszystkie poprzednio utworzone wyniki pomnożonego zwrotu N s zostaną pomnożone przez N = 1. W ten sposób daje wynik silni.

static int factorial(int N) {
    if(N > 1) { 
    return n * factorial(N - 1);
    }
    // Base Case N = 1
    else { 
    return N;
    }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.