Jestem naprawdę zaskoczony, że nie mogłem znaleźć odpowiedzi na to pytanie, chociaż może po prostu używam niewłaściwych wyszukiwanych haseł lub czegoś takiego. Najbliżej udało mi się znaleźć jest to , ale proszą o generowanie szeregu specyficzną double
S o rozmiarze określonego etapu, a odpowiedzi traktować go jako taki. Potrzebuję czegoś, co wygeneruje liczby o dowolnym rozmiarze początku, końca i kroku.
Wydaje mi się, że musi być gdzieś taka metoda w bibliotece, ale jeśli tak, to nie mogłem jej łatwo znaleźć (ponownie, może po prostu używam złych wyszukiwanych haseł lub czegoś takiego). Oto, co sam przygotowałem w ciągu ostatnich kilku minut, aby to zrobić:
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);
}
}
Te metody uruchamiają prostą pętlę, mnożąc wartość step
indeksu sekwencji i dodając do start
przesunięcia. Ogranicza to łączenie błędów zmiennoprzecinkowych, które występowałyby przy ciągłej inkrementacji (np. Dodawanie step
zmiennej do każdej iteracji).
Dodałem generateSequenceRounded
metodę dla tych przypadków, w których ułamek wielkości kroku może powodować zauważalne błędy zmiennoprzecinkowe. Wymaga to nieco więcej arytmetyki, więc w sytuacjach szczególnie wrażliwych na wydajność, takich jak nasza, miło jest mieć możliwość użycia prostszej metody, gdy zaokrąglanie nie jest konieczne. Podejrzewam, że w większości ogólnych przypadków użycia zaokrąglenie narzutu byłoby nieznaczne.
Zauważ, że celowo wyłączone logiki do obsługi „nienormalne” argumenty takie jak Infinity
, NaN
, start
> end
lub ujemną step
wielkość dla prostoty i pragną skoncentrować się na kwestii pod ręką.
Oto kilka przykładów użycia i odpowiadających danych wyjściowych:
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]
Czy istnieje już biblioteka, która już zapewnia taką funkcjonalność?
Jeśli nie, czy są jakieś problemy z moim podejściem?
Czy ktoś ma lepsze podejście do tego?
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
. Podobne błędy dlaIntStream.iterate
iStream.iterate
. Równieżnon-static method doubleValue() cannot be referenced from a static context
.Ja osobiście skróciłbym nieco klasę DoubleSequenceGenerator dla innych gadżetów i używałbym tylko jednej metody generatora sekwencji , która zawiera opcję wykorzystania dowolnej pożądanej precyzji lub wcale nie precyzji:
W poniższej metodzie generatora, jeśli do opcjonalnego parametru setPrecision nie zostanie dostarczone nic (lub jakakolwiek wartość mniejsza niż 0), wówczas nie jest wykonywane zaokrąglanie z dokładnością dziesiętną. Jeśli podano wartość 0 dla wartości dokładności, liczby są zaokrąglane do najbliższej liczby całkowitej (tj. 89,674 jest zaokrąglane do 90,0). Jeśli podana jest konkretna wartość dokładności większa niż 0, wówczas wartości są konwertowane na tę dokładność dziesiętną.
BigDecimal jest tutaj używany do ... no cóż ... precyzji:
I w main ():
A konsola wyświetla:
źródło
val
każdej iteracji, otrzymujesz addytywną utratę precyzji. W przypadku bardzo dużych sekwencji błąd ostatnich kilku cyfr może być znaczący. 2. Powtarzające się połączeniaBigDecimal.valueOf()
są stosunkowo drogie. Uzyskasz lepszą wydajność (i precyzję) poprzez konwersję danych wejściowych naBigDecimal
s i użycieBigDecimal
dlaval
. W rzeczywistości, używającdouble
forval
, tak naprawdę nie zyskujesz na precyzji, zBigDecimal
wyjątkiem zaokrąglania.Spróbuj tego.
Tutaj,
Zwraca skalę tego BigDecimal. Jeśli zero lub dodatnia, skala jest liczbą cyfr po prawej stronie przecinka dziesiętnego. Jeśli ujemna, nieskalowana wartość liczby jest mnożona przez dziesięć do potęgi negacji skali. Na przykład skala -3 oznacza, że nieskalowana wartość jest mnożona przez 1000.
W main ()
I wyjście:
źródło
Przepraszam, nie wiem, ale sądząc po innych odpowiedziach i ich względnej prostocie - nie, nie ma. Nie ma potrzeby. Cóż prawie...
Tak i nie. Masz co najmniej jeden błąd i trochę miejsca na zwiększenie wydajności, ale samo podejście jest prawidłowe.
while (mult*fraction < 1.0)
nawhile (mult*fraction < 10.0)
i powinien to naprawić)end
... cóż, może po prostu nie były wystarczająco spostrzegawcze, aby przeczytać komentarze w twoim kodzieint < Double
naint < int
zauważalnie zwiększy szybkość twojego koduHmm ... W jaki sposób?
generateSequenceDoubleStream
@Evgeniy Khyst wygląda dość prosto. I powinien być użyty ... ale może nie, z powodu kolejnych dwóch punktówgenerateSequenceDoubleStream
nie jest! Ale nadal można go zapisać za pomocą wzorustart + step*i
. Astart + step*i
wzór jest precyzyjny. TylkoBigDouble
arytmetyka stałoprzecinkowa może ją pokonać. AleBigDouble
są powolne, a ręczna arytmetyka stałoprzecinkowa jest uciążliwa i może być nieodpowiednia dla twoich danych. Nawiasem mówiąc, w kwestiach dotyczących precyzji możesz się bawić: https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.htmlSzybkość ... cóż, teraz jesteśmy na niepewnym gruncie. Sprawdź tę replikę https://repl.it/repls/RespectfulSufficientWorker Nie mam teraz przyzwoitego stanowiska testowego, więc użyłem repl.it ... co jest całkowicie nieodpowiednie do testowania wydajności, ale nie jest to najważniejsze. Chodzi o to - nie ma jednoznacznej odpowiedzi. Tyle że w twoim przypadku, co nie jest całkowicie jasne z twojego pytania, zdecydowanie nie powinieneś używać BigDecimal (czytaj dalej).
Próbowałem grać i optymalizować pod kątem dużych nakładów. I twój oryginalny kod, z pewnymi drobnymi zmianami - najszybszy. Ale może potrzebujesz ogromnych ilości małych
List
s? To może być zupełnie inna historia.Ten kod jest dość prosty i dość szybki:
Jeśli wolisz bardziej elegancki sposób (lub powinniśmy to nazwać idiomatycznym), osobiście sugerowałbym:
W każdym razie możliwe zwiększenie wydajności to:
Double
nadouble
, a jeśli naprawdę ich potrzebujesz, możesz przełączyć się ponownie, sądząc po testach, wciąż może być szybszy. (Ale nie ufaj mojemu, spróbuj sam z danymi w swoim środowisku. Jak powiedziałem - repl.it jest do bani dla testów porównawczych)Trochę magii: osobna pętla dla
Math.round()
... może ma to coś wspólnego z lokalizacją danych. Nie polecam tego - wynik jest bardzo niestabilny. Ale jest fajnie.Zdecydowanie powinieneś uważać się za bardziej leniwego i generować liczby na żądanie bez przechowywania ich w
List
sJeśli coś podejrzewasz - przetestuj :-) Moja odpowiedź brzmi „Tak”, ale znowu… nie wierz mi. Sprawdź to.
Wróćmy więc do głównego pytania: czy istnieje lepszy sposób?
Tak oczywiście!
Ale to zależy.
Double
, i co więcej, użyj go z liczbą „bliskich” wielkości - nie ma takiej potrzeby! Kasa tego samego replika: https://repl.it/repls/RespectfulSufficientWorker - ostatni test pokazuje, że nie będzie żadnej różnicy w wynikach , ale utrata prędkości kopania.Poza tym wszystko w porządku.
PS . W replice jest także implementacja formuły sumowania Kahan ... dla zabawy. https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html#1346 i działa - można złagodzić błędy sumowania
źródło