
Java: Jak implementować Callable?
Problem
Silnia
silnia = factorial (ang.).
n! (czytaj: 'n silnia’) jest iloczynem liczb od 1 do n



Dla wielkich liczb możemy obliczyć wartość n! z wzoru przybliżonego:

gdzie
e – jest podstawą logarytmu naturalnego
Już przy n = 20 wzór wykazuje mniej niż 0.5% błędu.
Algorytmy
Prosta metoda dla niewielkich liczb
Metoda została dodana do klasy Factorial.
public static long factorial(int n) {
if(n == 0){
return 1;
}
long sum = 1;
for(int i = 1; i < n + 1; i++){
sum = Math.multiplyExact(sum, i);
}
return sum;
}
Metoda wyrzuca wyjątek jeśli zostanie przekroczony zakres typu long.
Prosta metoda dla średnich liczb
Metoda została dodana do klasy Factorial.
public static BigInteger factorial(long n) {
if(n == 0){
return BigInteger.ONE;
}
BigInteger sum = new BigInteger("1");
for(long i = 1; i < n + 1; i++){
sum = sum.multiply(BigInteger.valueOf(i));
}
return sum;
}
Klasa Factorial
Wykonuje obliczenia dla dowolnych liczb. Ograniczeniem są możliwości komputera.
package callableimpl;
import java.math.*;
import java.util.concurrent.*;
/**
* Klasa do obliczeń n! w wątku
*
*/
public class Factorial implements Callable<BigInteger>{
private final BigInteger n;
public Factorial(BigInteger n){
this.n = n;
}
@Override
public BigInteger call() {
return factorial(this.n);
}
/**
* Oblicza silnię n! dla podanej liczby całkowitej
* @param n - liczba całkowita
* @return - zwraca n!
*/
public static BigInteger factorial(BigInteger n) {
if(rowny(n, BigInteger.ZERO)){
return BigInteger.ONE;
}
BigInteger sum = new BigInteger("1");
BigInteger i = new BigInteger("1");
while(mniejszy(i, n.add(BigInteger.ONE))){
sum = sum.multiply(i);
i = i.add(BigInteger.ONE);
}
return sum;
}
private static boolean rowny(BigInteger f, BigInteger s) {
return (f.compareTo(s) == 0);
}
private static boolean mniejszy(BigInteger f, BigInteger s) {
return (f.compareTo(s) < 0);
}
... Pominięto dwie wyżej listowane metody.
}
Użycie klas i metod
FactorialMain1
W klasie listujemy n! dla n = 1 do n = 100. Używamy metody Factorial.factorial(long n).
package callableimpl;
import java.time.*;
public class FactorialMain1 {
public static void main(String[] args) {
Instant startTime = Instant.now();
for (long i = 1; i < 101; i++) {
System.out.println(i + ": " + Factorial.factorial(i));
}
Instant endTime = Instant.now();
long time = Duration.between(startTime, endTime).toMillis();
System.out.println("Czas wykonania: " + time + (" ms"));
}
}
W wyniku na konsoli otrzymujemy:
1: 1 2: 2 3: 6 ... 100: 9332621544394415268169923885626670049 071596826438162146859296389521759999322991 560894146397615651828625369792082722375825 1185210916864000000000000000000000000 Czas wykonania: 40 ms
Klasa FactorialMain2
Chcemy obliczyć n! dla n = 200.
package callableimpl;
import java.math.*;
import java.time.*;
import java.util.concurrent.*;
public class FactorialMain2{
public static void main(String[] args) {
BigInteger n = new BigInteger("200");
ExecutorService es = Executors.newFixedThreadPool(1);
Future<BigInteger> f = null;
System.out.println("Start");
Instant startTime = Instant.now();
f = es.submit(new Factorial(n));
Instant endTime = Instant.now();
try{
System.out.println(f.get());
} catch (InterruptedException | ExecutionException e){
e.printStackTrace();
}
es.shutdown();
long time = Duration.between(startTime, endTime).toMillis();
System.out.println("Czas wykonania: " + time + (" ms"));
System.out.println("Koniec");
}
}
A oto nasz wynik:
Start 788657867364790503552363213932185062295135977 687173263294742533244359449963403342920304284 011984623904177212138919638830257642790242637 105061926624952829931113462857270763317237396 988943922445621451664240254033291864131227428 294853277524242407573903240321257405579568660 226031904170324062351700858796178922222789623 703897374720000000000000000000000000000000000 000000000000000 Czas wykonania: 0 ms Koniec
Zwróćmy uwagę, że czas wykonania jest mniejszy niż 1 ms. Powinniśmy czas mierzyć w nanosekundach.
Liczba ma 355 cyfr.
