Saya sebenarnya sangat terkejut saya tidak dapat menemukan jawaban untuk ini di sini, meskipun mungkin saya hanya menggunakan istilah pencarian yang salah atau sesuatu. Yang paling dekat yang bisa saya temukan adalah ini , tetapi mereka bertanya tentang menghasilkan kisaran double
s tertentu dengan ukuran langkah spesifik, dan jawaban memperlakukannya seperti itu. Saya membutuhkan sesuatu yang akan menghasilkan angka dengan ukuran awal, akhir dan langkah yang sewenang-wenang.
Saya pikir ada memiliki menjadi beberapa metode seperti ini di perpustakaan di suatu tempat sudah, tapi kalau jadi saya tidak dapat menemukannya dengan mudah (sekali lagi, mungkin aku hanya menggunakan istilah pencarian yang salah atau sesuatu). Jadi inilah yang saya lakukan sendiri dalam beberapa menit terakhir untuk melakukan ini:
import java.lang.Math;
import java.util.List;
import java.util.ArrayList;
public class DoubleSequenceGenerator {
/**
* Generates a List of Double values beginning with `start` and ending with
* the last step from `start` which includes the provided `end` value.
**/
public static List<Double> generateSequence(double start, double end, double step) {
Double numValues = (end-start)/step + 1.0;
List<Double> sequence = new ArrayList<Double>(numValues.intValue());
sequence.add(start);
for (int i=1; i < numValues; i++) {
sequence.add(start + step*i);
}
return sequence;
}
/**
* Generates a List of Double values beginning with `start` and ending with
* the last step from `start` which includes the provided `end` value.
*
* Each number in the sequence is rounded to the precision of the `step`
* value. For instance, if step=0.025, values will round to the nearest
* thousandth value (0.001).
**/
public static List<Double> generateSequenceRounded(double start, double end, double step) {
if (step != Math.floor(step)) {
Double numValues = (end-start)/step + 1.0;
List<Double> sequence = new ArrayList<Double>(numValues.intValue());
double fraction = step - Math.floor(step);
double mult = 10;
while (mult*fraction < 1.0) {
mult *= 10;
}
sequence.add(start);
for (int i=1; i < numValues; i++) {
sequence.add(Math.round(mult*(start + step*i))/mult);
}
return sequence;
}
return generateSequence(start, end, step);
}
}
Metode-metode ini menjalankan loop sederhana mengalikan step
dengan indeks urutan dan menambahkan ke start
offset. Ini mengurangi kesalahan floating-point majemuk yang akan terjadi dengan peningkatan berkelanjutan (seperti menambahkan step
variabel ke setiap iterasi).
Saya menambahkan generateSequenceRounded
metode untuk kasus-kasus di mana ukuran langkah fraksional dapat menyebabkan kesalahan floating-point. Memang diperlukan aritmatika sedikit lebih, sehingga dalam situasi yang sangat sensitif seperti kita, bagus untuk memiliki pilihan menggunakan metode yang lebih sederhana ketika pembulatan tidak diperlukan. Saya menduga bahwa dalam sebagian besar kasus penggunaan umum, overhead pembulatan akan diabaikan.
Perhatikan bahwa saya sengaja dikeluarkan logika untuk menangani "abnormal" argumen seperti Infinity
, NaN
, start
> end
, atau negatif step
ukuran untuk kesederhanaan dan ingin fokus pada pertanyaan di tangan.
Berikut beberapa contoh penggunaan dan output yang sesuai:
System.out.println(DoubleSequenceGenerator.generateSequence(0.0, 2.0, 0.2))
System.out.println(DoubleSequenceGenerator.generateSequenceRounded(0.0, 2.0, 0.2));
System.out.println(DoubleSequenceGenerator.generateSequence(0.0, 102.0, 10.2));
System.out.println(DoubleSequenceGenerator.generateSequenceRounded(0.0, 102.0, 10.2));
[0.0, 0.2, 0.4, 0.6000000000000001, 0.8, 1.0, 1.2000000000000002, 1.4000000000000001, 1.6, 1.8, 2.0]
[0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0]
[0.0, 10.2, 20.4, 30.599999999999998, 40.8, 51.0, 61.199999999999996, 71.39999999999999, 81.6, 91.8, 102.0]
[0.0, 10.2, 20.4, 30.6, 40.8, 51.0, 61.2, 71.4, 81.6, 91.8, 102.0]
Apakah sudah ada perpustakaan yang menyediakan fungsionalitas semacam ini?
Jika tidak, apakah ada masalah dengan pendekatan saya?
Apakah ada yang punya pendekatan yang lebih baik untuk ini?
error: method iterate in interface DoubleStream cannot be applied to given types; return DoubleStream.iterate(start, d -> d <= end, d -> d + step) required: double,DoubleUnaryOperator. found: double,(d)->d <= end,(d)->d + step. reason: actual and formal argument lists differ in length
. Kesalahan serupa untukIntStream.iterate
danStream.iterate
. Juganon-static method doubleValue() cannot be referenced from a static context
,.Saya pribadi, saya akan mempersingkat kelas DoubleSequenceGenerator sedikit untuk barang lainnya dan hanya menggunakan satu metode generator urutan yang berisi opsi untuk memanfaatkan apa pun yang diinginkan presisi yang diinginkan atau tidak menggunakan presisi sama sekali:
Dalam metode generator di bawah ini, jika tidak ada (atau nilai apa pun yang kurang dari 0) dipasok ke parameter setPrecision opsional maka tidak ada pembulatan presisi desimal yang dilakukan. Jika 0 diberikan untuk nilai presisi maka angka dibulatkan ke terdekat mereka seluruh nomor (yaitu: 89,674 dibulatkan ke 90.0). Jika nilai presisi spesifik lebih besar dari 0 diberikan maka nilai dikonversi ke presisi desimal itu.
BigDecimal digunakan di sini untuk ... yah .... presisi:
Dan di main ():
Dan konsol menampilkan:
sumber
val
pada setiap iterasi, Anda mendapatkan kerugian presisi aditif. Untuk urutan yang sangat besar, kesalahan pada beberapa angka terakhir bisa jadi signifikan. 2. Panggilan berulang keBigDecimal.valueOf()
relatif mahal. Anda akan mendapatkan kinerja (dan ketepatan) yang lebih baik dengan mengonversi input menjadiBigDecimal
, dan menggunakannyaBigDecimal
untukval
. Bahkan, dengan menggunakandouble
forval
, Anda tidak benar-benar mendapatkan manfaat presisi dari menggunakanBigDecimal
kecuali mungkin dengan pembulatan.Coba ini.
Sini,
Mengembalikan skala BigDecimal ini. Jika nol atau positif, skala adalah jumlah digit di sebelah kanan titik desimal. Jika negatif, nilai unscaled dari angka dikalikan sepuluh dengan kekuatan negasi skala. Misalnya, skala -3 berarti nilai unscaled dikalikan dengan 1000.
Di main ()
Dan Output:
sumber
Maaf, saya tidak tahu, tetapi menilai dari jawaban lain, dan kesederhanaannya - tidak, tidak ada. Tidak dibutuhkan. Hampir saja...
Iya dan tidak. Anda memiliki setidaknya satu bug, dan beberapa ruang untuk meningkatkan kinerja, tetapi pendekatan itu sendiri sudah benar.
while (mult*fraction < 1.0)
kewhile (mult*fraction < 10.0)
dan yang seharusnya memperbaikinya)end
... well, mungkin mereka hanya tidak cukup jeli untuk membaca komentar dalam kode Andaint < Double
menjadiint < int
terasa akan meningkatkan kecepatan kode AndaHmm ... Dalam hal apa?
generateSequenceDoubleStream
dari @Evgeniy Khyst terlihat cukup sederhana. Dan harus digunakan ... tapi mungkin tidak, karena dua poin berikutnyagenerateSequenceDoubleStream
tidak! Tapi tetap bisa diselamatkan dengan polanyastart + step*i
. Danstart + step*i
polanya tepat. HanyaBigDouble
dan aritmatika titik tetap dapat mengalahkannya. TapiBigDouble
s lambat, dan aritmatika titik tetap manual membosankan dan mungkin tidak sesuai untuk data Anda. Ngomong-ngomong, dalam hal ketepatan, Anda dapat menghibur diri dengan ini: https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.htmlKecepatan ... yah sekarang kita berada di tanah yang goyah. Lihat replika ini https://repl.it/repls/RespectfulSufficientWorker Saya tidak memiliki stan tes yang layak sekarang, jadi saya menggunakan repl.it ... yang sama sekali tidak memadai untuk pengujian kinerja, tetapi itu bukan poin utama. Intinya adalah - tidak ada jawaban yang pasti. Kecuali bahwa mungkin dalam kasus Anda, yang tidak sepenuhnya jelas dari pertanyaan Anda, Anda pasti tidak boleh menggunakan BigDecimal (baca lebih lanjut).
Saya sudah mencoba bermain dan mengoptimalkan input besar. Dan kode asli Anda, dengan beberapa perubahan kecil - tercepat. Tapi mungkin Anda membutuhkan sejumlah kecil
List
? Maka itu bisa menjadi cerita yang sama sekali berbeda.Kode ini cukup sederhana untuk seleraku, dan cukup cepat:
Jika Anda lebih suka cara yang lebih elegan (atau kami menyebutnya idiomatis), saya pribadi menyarankan:
Bagaimanapun, peningkatan kinerja yang mungkin adalah:
Double
kedouble
, dan jika Anda benar-benar membutuhkannya, Anda dapat beralih kembali, menilai dari tes, masih mungkin lebih cepat. (Tapi jangan percaya, coba sendiri dengan data Anda di lingkungan Anda. Seperti yang saya katakan - repl.it menyebalkan untuk tolok ukur)Sihir kecil: loop terpisah untuk
Math.round()
... mungkin ada hubungannya dengan lokalitas data. Saya tidak merekomendasikan ini - hasilnya sangat tidak stabil. Tapi itu menyenangkan.Anda harus mempertimbangkan untuk menjadi lebih malas dan menghasilkan angka sesuai permintaan tanpa menyimpan kemudian di
List
sJika Anda mencurigai sesuatu - ujilah :-) Jawaban saya adalah "Ya", tapi sekali lagi ... jangan percaya padaku. Menguji.
Jadi, kembali ke pertanyaan utama: Apakah ada cara yang lebih baik?
Ya tentu saja!
Tapi itu tergantung.
Double
, dan lebih dari itu, gunakan dengan jumlah "dekat" - tidak perlu bagi mereka! Periksa balasan yang sama: https://repl.it/repls/RespectfulSufficientWorker - tes terakhir menunjukkan bahwa tidak akan ada perbedaan dalam hasil , tetapi penggalian kehilangan dalam kecepatan.Selain itu, Anda baik-baik saja.
PS . Ada juga implementasi Formula Kahan Summation di repl ... hanya untuk bersenang-senang. https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html#1346 dan berhasil - Anda dapat mengurangi kesalahan penjumlahan
sumber