Saya mencoba bekerja dengan pecahan di Jawa.
Saya ingin menerapkan fungsi aritmatika. Untuk ini, pertama-tama saya akan membutuhkan cara untuk menormalkan fungsi. Saya tahu saya tidak dapat menambahkan 1/6 dan 1/2 sampai saya memiliki penyebut yang sama. Saya harus menambahkan 1/6 dan 3/6. Pendekatan yang naif akan meminta saya menambahkan 2/12 dan 6/12 lalu mengurangi. Bagaimana saya bisa mencapai penyebut umum dengan penalti performa paling kecil? Algoritma apa yang terbaik untuk ini?
Versi 8 (terima kasih kepada hstoerr ):
Perbaikan meliputi:
- metode equals () sekarang konsisten dengan metode bandingkanTo ()
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;
}
}
Saya telah menghapus semua versi sebelumnya. Terima kasih saya kepada:
Jawaban:
Kebetulan saya menulis kelas BigFraction belum lama ini, untuk masalah Project Euler . Itu menyimpan pembilang dan penyebut BigInteger, sehingga tidak akan pernah meluap. Tapi itu akan menjadi anak laki-laki lambat untuk banyak operasi yang Anda tahu tidak akan pernah meluap .. bagaimanapun, gunakan jika Anda menginginkannya. Aku sangat ingin memamerkan ini. :)
Sunting : Versi terbaru dan terhebat dari kode ini, termasuk pengujian unit sekarang dihosting di GitHub dan juga tersedia melalui Maven Central . Saya meninggalkan kode asli saya di sini sehingga jawaban ini bukan hanya tautan ...
sumber
BigInteger
untuk menyimpan nilai dengan presisi yang sewenang-wenang. Jika tidaklong
, maka implementasi yang lebih mudah;Number
;Comparable<T>
;equals()
danhashCode()
;String
;toString()
; danSerializable
.Sebenarnya, coba ini untuk ukuran. Ini berjalan tetapi mungkin memiliki beberapa masalah:
Outputnya adalah:
sumber
Apache Commons Math telah memiliki kelas Pecahan cukup lama. Sering kali jawaban untuk, "Wah, saya berharap Java memiliki sesuatu seperti X di pustaka inti!" dapat ditemukan di bawah payung pustaka Apache Commons .
sumber
Tolong jadikan itu tipe yang tidak bisa diubah! Nilai pecahan tidak berubah - setengah tidak menjadi sepertiga, misalnya. Alih-alih setDenominator, Anda bisa memiliki withDenominator yang mengembalikan nilai baru pecahan yang memiliki pembilang yang sama tetapi penyebut yang ditentukan.
Hidup itu jauh lebih mudah dengan tipe yang tidak bisa diubah.
Mengganti sama dengan dan kode hash akan masuk akal juga, sehingga bisa digunakan di peta dan set. Poin Outlaw Programmer tentang operator aritmatika dan pemformatan string juga bagus.
Sebagai panduan umum, lihat BigInteger dan BigDecimal. Mereka tidak melakukan hal yang sama, tetapi mereka cukup mirip untuk memberi Anda ide-ide bagus.
sumber
Yah, untuk satu, aku akan menyingkirkan penyetel dan membuat Fraksi tidak berubah.
Anda mungkin juga menginginkan metode untuk menambah, mengurangi, dll., Dan mungkin beberapa cara untuk mendapatkan representasi dalam berbagai format String.
EDIT: Saya mungkin akan menandai bidang sebagai 'final' untuk menandakan niat saya, tetapi saya rasa itu bukan masalah besar ...
sumber
sumber
Tidak terlalu penting. (Faktanya jika Anda ingin menangani persamaan dengan benar, jangan mengandalkan double untuk bekerja dengan benar.) Jika b * d positif, a / b <c / d if ad <bc. Jika ada bilangan bulat negatif yang terlibat, itu dapat ditangani dengan tepat ...
Saya mungkin menulis ulang sebagai:
Penggunaan di
long
sini adalah untuk memastikan tidak ada luapan jika Anda mengalikan dua besarint
s . handle Jika Anda dapat menjamin bahwa penyebut selalu non-negatif (jika negatif, negasikan saja pembilang dan penyebutnya), maka Anda dapat menyingkirkan keharusan untuk memeriksa apakah b * d positif dan menyimpan beberapa langkah. Saya tidak yakin perilaku apa yang Anda cari dengan penyebut nol.Tidak yakin bagaimana kinerja dibandingkan dengan penggunaan ganda untuk membandingkan. (Yaitu, jika Anda sangat peduli dengan kinerja) Berikut adalah metode pengujian yang saya gunakan untuk memeriksa. (Tampaknya berfungsi dengan baik.)
(ps Anda mungkin mempertimbangkan restrukturisasi untuk diimplementasikan
Comparable
atauComparator
untuk kelas Anda.)sumber
Satu peningkatan yang sangat kecil berpotensi untuk menyimpan nilai ganda yang Anda hitung sehingga Anda hanya menghitungnya pada akses pertama. Ini tidak akan menjadi kemenangan besar kecuali Anda sering mengakses nomor ini, tetapi itu juga tidak terlalu sulit untuk dilakukan.
Satu poin tambahan mungkin adalah pengecekan kesalahan yang Anda lakukan di penyebut ... Anda secara otomatis mengubah 0 menjadi 1. Tidak yakin apakah ini benar untuk aplikasi khusus Anda, tetapi secara umum jika seseorang mencoba membagi dengan 0, ada sesuatu yang sangat salah . Saya akan membiarkan ini melempar pengecualian (pengecualian khusus jika Anda merasa itu diperlukan) daripada mengubah nilai dengan cara yang tampaknya sewenang-wenang yang tidak diketahui pengguna.
Berkebalikan dengan beberapa komentar lain, tentang menambahkan metode untuk menambah pengurangan, dll ... karena Anda tidak menyebutkan membutuhkannya, saya berasumsi Anda tidak membutuhkannya. Dan kecuali Anda sedang membangun perpustakaan yang benar-benar akan digunakan di banyak tempat atau oleh orang lain, gunakan YAGNI (Anda tidak akan membutuhkannya, jadi seharusnya tidak ada di sana.)
sumber
Ada beberapa cara untuk meningkatkan ini atau jenis nilai apa pun:
Pada dasarnya, lihat API untuk kelas nilai lainnya seperti Double , Integer dan lakukan apa yang mereka lakukan :)
sumber
Jika Anda mengalikan pembilang dan penyebut dari satu pecahan dengan penyebut yang lain dan sebaliknya, Anda akan mendapatkan dua pecahan (yang nilainya masih sama) dengan penyebut yang sama dan Anda dapat membandingkan pembilangnya secara langsung. Oleh karena itu, Anda tidak perlu menghitung nilai ganda:
sumber
bagaimana saya akan meningkatkan kode itu:
sumber
Anda sudah memiliki fungsi CompareTo ... Saya akan mengimplementasikan antarmuka Comparable.
Mungkin tidak terlalu penting untuk apa pun yang akan Anda lakukan dengannya.
sumber
Jika Anda suka berpetualang, lihat JScience . Ini memiliki
Rational
kelas yang mewakili pecahan.sumber
Saya akan mengatakan melempar ArithmeticException untuk membagi dengan nol, karena itulah yang sebenarnya terjadi:
Alih-alih "Bagi dengan nol", Anda mungkin ingin membuat pesan yang mengatakan "Bagi dengan nol: Penyebut untuk Pecahan adalah nol."
sumber
Setelah Anda membuat objek pecahan, mengapa Anda ingin mengizinkan objek lain menyetel pembilang atau penyebutnya? Saya pikir ini harus dibaca saja. Itu membuat objek itu tidak berubah ...
Juga ... mengatur penyebut ke nol harus membuang pengecualian argumen yang tidak valid (saya tidak tahu apa itu di Jawa)
sumber
Timothy Budd memiliki implementasi kelas Rasional yang bagus dalam "Struktur Data di C ++". Bahasa berbeda, tentu saja, tetapi port ke Java dengan sangat baik.
Saya akan merekomendasikan lebih banyak konstruktor. Konstruktor default akan memiliki pembilang 0, penyebut 1. Konstruktor arg tunggal akan mengasumsikan penyebut 1. Pikirkan bagaimana pengguna Anda dapat menggunakan kelas ini.
Tidak ada cek untuk penyebut nol? Pemrograman berdasarkan kontrak akan membuat Anda menambahkannya.
sumber
Saya akan merekomendasikan ketiga atau kelima atau apapun untuk membuat pecahan Anda tidak berubah. Saya juga merekomendasikan agar Anda memperpanjang kelas Number . Saya mungkin akan melihat kelas Double , karena Anda mungkin ingin menerapkan banyak metode yang sama.
Anda mungkin juga harus mengimplementasikan Comparable dan Serializable karena perilaku ini mungkin diharapkan. Jadi, Anda perlu mengimplementasikan bandingkanTo (). Anda juga perlu mengganti equals () dan saya tidak bisa cukup menekankan bahwa Anda juga mengganti hashCode (). Ini mungkin salah satu dari sedikit kasus meskipun Anda tidak ingin bandingkanTo () dan sama dengan () menjadi konsisten karena pecahan dapat direduksi satu sama lain belum tentu sama.
sumber
Praktik pembersihan yang saya suka adalah hanya memiliki satu pengembalian.
sumber
Gunakan kelas Rasional dari perpustakaan JScience . Itu hal terbaik untuk aritmatika pecahan yang saya lihat di Jawa.
sumber
Saya membersihkan jawaban cletus :
valueOf(String)
denganBigInteger(String)
yang lebih fleksibel dan lebih cepat.sumber
Komentar awal:
Jangan pernah menulis ini:
Ini jauh lebih baik
Ciptakan saja untuk menciptakan kebiasaan yang baik.
Dengan membuat kelas tidak dapat diubah seperti yang disarankan, Anda juga dapat memanfaatkan fungsi ganda untuk melakukan operasi equals dan hashCode serta bandingkanTo
Ini versi cepat kotor saya:
Tentang metode pabrik statis, mungkin berguna nanti, jika Anda membuat subkelas Fraksi untuk menangani hal-hal yang lebih kompleks, atau jika Anda memutuskan untuk menggunakan kumpulan untuk objek yang paling sering digunakan.
Mungkin tidak demikian, saya hanya ingin menunjukkannya. :)
Lihat item pertama Java Efektif .
sumber
Mungkin berguna untuk menambahkan hal-hal sederhana seperti membalas, mendapatkan sisa, dan utuh.
sumber
Meskipun Anda memiliki metode bandingkanTo (), jika Anda ingin menggunakan utilitas seperti Collections.sort (), Anda juga harus mengimplementasikan Comparable.
Selain itu, untuk tampilan yang cantik, saya sarankan untuk mengganti toString ()
Dan akhirnya, saya akan membuat kelas menjadi publik sehingga Anda dapat menggunakannya dari paket yang berbeda.
sumber
Fungsi ini menyederhanakan dengan menggunakan algoritma eucledian cukup berguna saat mendefinisikan pecahan
sumber
Untuk implementasi Fraksi / Rasional kelas industri, saya akan menerapkannya sehingga dapat mewakili NaN, infinity positif, infinity negatif, dan nol negatif opsional dengan semantik operasional persis sama dengan standar IEEE 754 untuk aritmatika floating point (ini juga memudahkan konversi ke / dari nilai floating point). Plus, karena perbandingan dengan nol, satu, dan nilai khusus di atas hanya perlu sederhana, tetapi perbandingan gabungan pembilang dan penyebut terhadap 0 dan 1 - saya akan menambahkan beberapa metode isXXX dan bandingkanToXXX untuk kemudahan penggunaan (mis. Eq0 () akan gunakan pembilang == 0 && penyebut! = 0 di belakang layar alih-alih membiarkan klien membandingkan dengan contoh bernilai nol). Beberapa nilai yang telah ditentukan secara statis (ZERO, ONE, TWO, TEN, ONE_TENTH, NAN, etc.) juga berguna, karena mereka muncul di beberapa tempat sebagai nilai konstan. Ini adalah IMHO cara terbaik.
sumber
Fraksi Kelas:
Program utama:
sumber