Próbuję pracować z ułamkami w Javie.
Chcę zaimplementować funkcje arytmetyczne. W tym celu najpierw potrzebuję sposobu na znormalizowanie funkcji. Wiem, że nie mogę dodać 1/6 i 1/2, dopóki nie mam wspólnego mianownika. Będę musiał dodać 1/6 i 3/6. Naiwne podejście wymagałoby dodania 2/12 i 6/12, a następnie zmniejszenia. Jak osiągnąć wspólny mianownik przy jak najmniejszym spadku wydajności? Jaki algorytm jest do tego najlepszy?
Wersja 8 (dzięki hstoerr ):
Ulepszenia obejmują:
- metoda equals () jest teraz spójna z metodą compareTo ()
final class Fraction extends Number {
private int numerator;
private int denominator;
public Fraction(int numerator, int denominator) {
if(denominator == 0) {
throw new IllegalArgumentException("denominator is zero");
}
if(denominator < 0) {
numerator *= -1;
denominator *= -1;
}
this.numerator = numerator;
this.denominator = denominator;
}
public Fraction(int numerator) {
this.numerator = numerator;
this.denominator = 1;
}
public int getNumerator() {
return this.numerator;
}
public int getDenominator() {
return this.denominator;
}
public byte byteValue() {
return (byte) this.doubleValue();
}
public double doubleValue() {
return ((double) numerator)/((double) denominator);
}
public float floatValue() {
return (float) this.doubleValue();
}
public int intValue() {
return (int) this.doubleValue();
}
public long longValue() {
return (long) this.doubleValue();
}
public short shortValue() {
return (short) this.doubleValue();
}
public boolean equals(Fraction frac) {
return this.compareTo(frac) == 0;
}
public int compareTo(Fraction frac) {
long t = this.getNumerator() * frac.getDenominator();
long f = frac.getNumerator() * this.getDenominator();
int result = 0;
if(t>f) {
result = 1;
}
else if(f>t) {
result = -1;
}
return result;
}
}
Usunąłem wszystkie poprzednie wersje. Moje podziękowania dla:
Odpowiedzi:
Tak się składa, że niedawno napisałem klasę BigFraction, dotyczącą problemów z Projektem Euler . Zachowuje licznik i mianownik BigInteger, więc nigdy się nie przepełni. Ale będzie to trochę powolne w przypadku wielu operacji, o których wiesz, że nigdy się nie przepełnią… w każdym razie użyj go, jeśli chcesz. Nie mogłem się jakoś pochwalić, żeby to pokazać. :)
Edycja : najnowsza i najlepsza wersja tego kodu, w tym testy jednostkowe, jest teraz hostowana na GitHub, a także dostępna za pośrednictwem Maven Central . Zostawiam tutaj mój oryginalny kod, aby ta odpowiedź nie była tylko linkiem ...
źródło
BigInteger
do przechowywania dowolnie precyzyjnych wartości. Jeśli nie to, tolong
, to ma łatwiejszą implementację;Number
;Comparable<T>
;equals()
ihashCode()
;String
;toString()
; iSerializable
.W rzeczywistości spróbuj tego dla rozmiaru. Działa, ale może mieć pewne problemy:
Wynik to:
źródło
Apache Commons Math ma klasę Fraction od dłuższego czasu. W większości przypadków odpowiedź brzmi: „Chłopcze, chciałbym, żeby Java miała coś takiego jak X w podstawowej bibliotece!” można znaleźć pod parasolem biblioteki Apache Commons .
źródło
Proszę, uczyń go niezmiennym typem! Wartość ułamka się nie zmienia - na przykład połowa nie staje się trzecią. Zamiast setDenominator, możesz mieć withDenominator, który zwraca nowy ułamek, który ma ten sam licznik, ale określony mianownik.
Życie jest dużo łatwiejsze z niezmiennymi typami.
Zastąpienie równości i hashcode również byłoby rozsądne, więc można go używać w mapach i zestawach. Uwagi programisty dotyczące operatorów arytmetycznych i formatowania łańcuchów są również dobre.
Jako ogólny przewodnik, spójrz na BigInteger i BigDecimal. Nie robią tego samego, ale są na tyle podobne, że dają dobre pomysły.
źródło
Cóż, po pierwsze, pozbyłbym się seterów i uczyniłbym frakcje niezmiennymi.
Prawdopodobnie będziesz także potrzebować metod dodawania, odejmowania itp., A może jakiegoś sposobu na uzyskanie reprezentacji w różnych formatach String.
EDYCJA: Prawdopodobnie oznaczyłbym pola jako `` ostateczne '', aby zasygnalizować mój zamiar, ale myślę, że to nic wielkiego ...
źródło
źródło
Nie jest to bezwzględnie konieczne. (W rzeczywistości, jeśli chcesz poprawnie obsługiwać równość, nie polegaj na double, aby działać poprawnie.) Jeśli b * d jest dodatnie, a / b <c / d if ad <bc. Jeśli w grę wchodzą ujemne liczby całkowite, można to odpowiednio obsłużyć ...
Mógłbym przepisać jako:
Zastosowanie
long
tutaj ma na celu zapewnienie, że nie ma przepełnienia, jeśli pomnożymy dwa dużeint
s. uchwyt Jeśli możesz zagwarantować, że mianownik jest zawsze nieujemny (jeśli jest ujemny, po prostu zaneguj zarówno licznik, jak i mianownik), możesz pozbyć się konieczności sprawdzania, czy b * d jest dodatnie, i zapisać kilka kroków. Nie jestem pewien, jakiego zachowania szukasz przy zerowym mianowniku.Nie wiem, jak wypada wydajność w porównaniu z używaniem podwójnych do porównania. (to znaczy, jeśli tak bardzo zależy ci na wydajności) Oto metoda testowa, którą sprawdzałem. (Wydaje się, że działa poprawnie.)
(ps możesz rozważyć restrukturyzację do wdrożenia
Comparable
lubComparator
dla swojej klasy).źródło
Jednym z bardzo drobnych ulepszeń może być potencjalnie zapisanie podwójnej wartości, którą obliczasz, tak aby była obliczana tylko przy pierwszym dostępie. Nie będzie to duża wygrana, chyba że często uzyskujesz dostęp do tej liczby, ale nie jest to też zbyt trudne.
Dodatkową kwestią może być błąd sprawdzania, który wykonujesz w mianowniku ... automatycznie zmieniasz 0 na 1. Nie jestem pewien, czy to jest poprawne dla twojej konkretnej aplikacji, ale ogólnie jeśli ktoś próbuje podzielić przez 0, coś jest bardzo nie tak . Pozwoliłbym temu zgłosić wyjątek (wyspecjalizowany wyjątek, jeśli uważasz, że jest potrzebny), zamiast zmieniać wartość w pozornie arbitralny sposób, który nie jest znany użytkownikowi.
W przeciwieństwie do kilku innych komentarzy, o dodawaniu metod dodawania odejmowania itp. ... ponieważ nie wspomniałeś o ich potrzebie, zakładam, że nie. I jeśli nie budujesz biblioteki, która naprawdę będzie używana w wielu miejscach lub przez innych ludzi, idź z YAGNI (nie będziesz jej potrzebować, więc nie powinno jej tam być).
źródło
Istnieje kilka sposobów na ulepszenie tego lub dowolnego typu wartości:
Zasadniczo spójrz na API dla innych klas wartości, takich jak Double , Integer i rób to, co robią :)
źródło
Jeśli pomnożymy licznik i mianownik jednej frakcji przez mianownik drugiej i na odwrót, otrzymamy dwa ułamki (które nadal są tymi samymi wartościami) o tym samym mianowniku i można bezpośrednio porównać liczniki. Dlatego nie musisz obliczać podwójnej wartości:
źródło
jak ulepszyłbym ten kod:
źródło
Masz już funkcję compareTo ... Zaimplementowałbym interfejs Comparable.
Może nie mieć jednak znaczenia, niezależnie od tego, co z nim zrobisz.
źródło
Jeśli masz ochotę na przygodę, spójrz na JScience . Ma
Rational
klasę reprezentującą ułamki.źródło
Powiedziałbym rzucić wyjątek ArithmeticException do dzielenia przez zero, ponieważ tak naprawdę się dzieje:
Zamiast „Podziel przez zero”, możesz chcieć, aby komunikat brzmiał „Podziel przez zero: mianownikiem ułamka jest zero”.
źródło
Po utworzeniu obiektu ułamkowego, dlaczego miałbyś chcieć pozwolić innym obiektom ustawić licznik lub mianownik? Myślę, że powinny one być tylko do odczytu. Czyni obiekt niezmiennym ...
Poza tym ... ustawienie mianownika na zero powinno spowodować zgłoszenie wyjątku nieprawidłowego argumentu (nie wiem, co to jest w Javie)
źródło
Timothy Budd ma świetną implementację klasy Rational w swoich „Strukturach danych w C ++”. Oczywiście inny język, ale bardzo ładnie przenosi się na Javę.
Poleciłbym więcej konstruktorów. Domyślny konstruktor miałby licznik 0, mianownik 1. Konstruktor z pojedynczym argumentem zakładałby mianownik równy 1. Pomyśl, jak użytkownicy mogliby używać tej klasy.
Brak czeku na zerowy mianownik? Programowanie na podstawie umowy wymagałoby dodania go.
źródło
Podam trzecią, piątą lub jakąkolwiek rekomendację, aby uczynić twoją część niezmienną. Poleciłbym również, abyś rozszerzył klasę Number . Prawdopodobnie spojrzałbym na Double klasę , ponieważ prawdopodobnie będziesz chciał zaimplementować wiele takich samych metod.
Prawdopodobnie powinieneś również zaimplementować Comparable i Serializable, ponieważ takie zachowanie będzie prawdopodobnie oczekiwane. Dlatego będziesz musiał zaimplementować funkcję compareTo (). Będziesz także musiał zastąpić equals () i nie mogę wystarczająco mocno podkreślić, że możesz również zastąpić hashCode (). Może to być jednak jeden z nielicznych przypadków, w których nie chcesz, aby metody compareTo () i equals () były spójne, ponieważ ułamki, które można redukować, niekoniecznie są równe.
źródło
Praktyką sprzątania, którą lubię, jest tylko jeden powrót.
źródło
Użyj klasy Rational z biblioteki JScience . To najlepsza rzecz do arytmetyki ułamkowej, jaką widziałem w Javie.
źródło
Oczyściłem odpowiedź Cletusa :
valueOf(String)
zBigInteger(String)
którego jest jednocześnie bardziej elastyczne i szybciej.źródło
Uwaga wstępna:
Nigdy tego nie pisz:
To jest dużo lepsze
Po prostu stwórz, aby stworzyć dobry nawyk.
Uczyniając klasę niezmienną zgodnie z sugestią, możesz również skorzystać z double do wykonywania operacji equals, hashCode i compareTo
Oto moja szybka, brudna wersja:
Jeśli chodzi o metodę fabryki statycznej, może być przydatna później, jeśli podklasujesz ułamek w celu obsługi bardziej złożonych rzeczy lub jeśli zdecydujesz się użyć puli dla najczęściej używanych obiektów.
Może tak nie jest, po prostu chciałem to podkreślić. :)
Zobacz Efektywna pierwsza pozycja w Javie .
źródło
Może przydać się dodanie prostych rzeczy, takich jak odwzajemnienie, uzyskanie reszty i uzyskanie całości.
źródło
Nawet jeśli masz metody compareTo (), jeśli chcesz korzystać z narzędzi takich jak Collections.sort (), powinieneś również zaimplementować Comparable.
Ponadto, aby uzyskać ładny wyświetlacz, zalecam zastąpienie toString ()
Na koniec upubliczniłbym klasę, abyś mógł jej używać z różnych pakietów.
źródło
Ta funkcja upraszcza korzystanie z algorytmu eukledowskiego, jest bardzo przydatna podczas definiowania ułamków
źródło
W przypadku implementacji frakcji / racjonalnej klasy przemysłowej zaimplementowałbym ją tak, aby mogła reprezentować NaN, dodatnią nieskończoność, ujemną nieskończoność i opcjonalnie ujemne zero z semantyką operacyjną dokładnie taką samą, jak standardowe stany IEEE 754 dla arytmetyki zmiennoprzecinkowej (ułatwia to również konwersja na / z wartości zmiennoprzecinkowych). Ponadto, ponieważ porównanie do zera, jedynki i powyższych wartości specjalnych wymaga tylko prostego, ale połączonego porównania licznika i mianownika z 0 i 1 - dodałbym kilka metod isXXX i porównajToXXX dla łatwości użycia (np. Eq0 () użyj licznika == 0 && denominator! = 0 za kulisami, zamiast pozwalać klientowi na porównanie z instancją o wartości zerowej). Przydatne są również niektóre predefiniowane wartości statyczne (ZERO, ONE, TWO, TEN, ONE_TENTH, NAN itp.), ponieważ pojawiają się w kilku miejscach jako wartości stałe. To najlepszy sposób IMHO.
źródło
Frakcja klasowa:
Program główny:
źródło