Ich bin eigentlich sehr überrascht, dass ich hier keine Antwort darauf finden konnte, obwohl ich vielleicht nur die falschen Suchbegriffe verwende oder so. Closest ich finden konnte , ist dies , aber sie fragen über einen bestimmten Bereich der Erzeugung von double
s mit einem bestimmten Schrittgröße, und die Antworten behandeln sie als solche. Ich brauche etwas, das die Zahlen mit beliebiger Start-, End- und Schrittgröße generiert.
Ich denke, irgendwo in einer Bibliothek muss es schon eine solche Methode geben, aber wenn ja, konnte ich sie nicht leicht finden (wieder verwende ich vielleicht nur die falschen Suchbegriffe oder so). Folgendes habe ich mir in den letzten Minuten selbst ausgedacht:
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);
}
}
Diese Methoden führen eine einfache Schleife aus, die das step
mit dem Sequenzindex multipliziert und zum start
Offset addiert . Dies verringert das Zusammensetzen von Gleitkommafehlern, die bei kontinuierlicher Inkrementierung auftreten würden (z. B. Hinzufügen der step
Variablen zu einer Variablen bei jeder Iteration).
Ich habe die generateSequenceRounded
Methode für Fälle hinzugefügt , in denen eine gebrochene Schrittgröße zu merklichen Gleitkommafehlern führen kann. Es erfordert etwas mehr Arithmetik, daher ist es in extrem leistungsempfindlichen Situationen wie unserer schön, die Option zu haben, die einfachere Methode zu verwenden, wenn die Rundung nicht erforderlich ist. Ich vermute, dass in den meisten allgemeinen Anwendungsfällen der Rundungsaufwand vernachlässigbar wäre.
Beachten Sie, dass ich ausgeschlossen Logik absichtlich für „abnormal“ Argumente wie Handhabung Infinity
, NaN
, start
> end
, oder eine negative step
Größe für Einfachheit und begehrt auf der Frage auf der Hand zu konzentrieren.
Hier einige Beispiele für die Verwendung und die entsprechende Ausgabe:
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]
Gibt es bereits eine Bibliothek, die diese Art von Funktionalität bietet?
Wenn nicht, gibt es Probleme mit meinem Ansatz?
Hat jemand einen besseren Ansatz dafür?
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
. Ähnliche Fehler fürIntStream.iterate
undStream.iterate
. Auchnon-static method doubleValue() cannot be referenced from a static context
.Ich persönlich würde ich die verkürzen DoubleSequenceGenerator Klasse bis etwas für andere Leckereien und verwenden nur eine Sequenzgenerator Methode, die die Option enthält zu verwenden , was auch immer gewünschte Präzision wollten oder keine Präzision überhaupt nutzen:
Wenn in der folgenden Generatormethode dem optionalen Parameter setPrecision nichts (oder ein Wert kleiner als 0) zugeführt wird, wird keine Rundung mit Dezimalgenauigkeit durchgeführt. Wenn für einen Genauigkeitswert 0 angegeben wird, werden die Zahlen auf die nächste ganze Zahl gerundet (dh: 89.674 wird auf 90,0 gerundet). Wenn ein bestimmter Genauigkeitswert größer als 0 angegeben wird, werden die Werte in diese Dezimalgenauigkeit konvertiert.
BigDecimal wird hier verwendet für ... na ja ... Präzision:
Und in main ():
Und die Konsole zeigt an:
quelle
val
jeder Iteration erhalten Sie einen additiven Präzisionsverlust. Bei sehr großen Sequenzen kann der Fehler bei den letzten Zahlen erheblich sein. 2. Wiederholte AnrufeBigDecimal.valueOf()
sind relativ teuer. Sie erhalten eine bessere Leistung (und Präzision), wenn Sie die Eingaben inBigDecimal
s konvertieren undBigDecimal
for verwendenval
. Wenn Sie eindouble
for verwendenval
, erhalten Sie keinen Präzisionsvorteil,BigDecimal
außer vielleicht bei der Rundung.Versuche dies.
Hier,
Gibt die Skalierung dieses BigDecimal zurück. Bei Null oder positiv ist die Skala die Anzahl der Stellen rechts vom Dezimalpunkt. Wenn negativ, wird der nicht skalierte Wert der Zahl mit zehn multipliziert mit der Potenz der Negation der Skala multipliziert. Zum Beispiel bedeutet eine Skala von -3, dass der nicht skalierte Wert mit 1000 multipliziert wird.
In main ()
Und Ausgabe:
quelle
Entschuldigung, ich weiß es nicht, aber nach anderen Antworten und ihrer relativen Einfachheit zu urteilen - nein, das gibt es nicht. Das ist nicht nötig. Naja fast...
Ja und nein. Sie haben mindestens einen Fehler und etwas Raum für Leistungssteigerungen, aber der Ansatz selbst ist korrekt.
while (mult*fraction < 1.0)
zuwhile (mult*fraction < 10.0)
und das sollte ihn beheben)end
... nun, vielleicht waren sie einfach nicht aufmerksam genug, um Kommentare in Ihrem Code zu lesenint < Double
auf ändern ,int < int
wird die Geschwindigkeit Ihres Codes spürbar erhöhtHmm ... auf welche Weise?
generateSequenceDoubleStream
von @Evgeniy Khyst sieht ganz einfach aus. Und sollte verwendet werden ... aber vielleicht nein, wegen der nächsten zwei PunktegenerateSequenceDoubleStream
ist nicht! Kann aber trotzdem mit dem Muster gespeichert werdenstart + step*i
. Und dasstart + step*i
Muster ist präzise. Nur eineBigDouble
Festkomma-Arithmetik kann es schlagen. AberBigDouble
s sind langsam und manuelle Festkomma-Arithmetik ist langwierig und möglicherweise für Ihre Daten ungeeignet. In Sachen Präzision können Sie sich übrigens damit unterhalten: https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.htmlGeschwindigkeit ... nun sind wir auf wackeligen Böden. Schauen Sie sich diese Replik an https://repl.it/repls/RespectfulSufficientWorker Ich habe momentan keinen anständigen Prüfstand, daher habe ich repl.it verwendet ... was für Leistungstests völlig unzureichend ist, aber es ist nicht der Hauptpunkt. Der Punkt ist - es gibt keine eindeutige Antwort. Abgesehen davon, dass Sie in Ihrem Fall, der aus Ihrer Frage nicht ganz klar hervorgeht, BigDecimal definitiv nicht verwenden sollten (lesen Sie weiter).
Ich habe versucht zu spielen und für große Eingaben zu optimieren. Und Ihr ursprünglicher Code mit einigen geringfügigen Änderungen - der schnellste. Aber vielleicht brauchen Sie enorme Mengen kleiner
List
s? Dann kann das eine ganz andere Geschichte sein.Dieser Code ist nach meinem Geschmack recht einfach und schnell genug:
Wenn Sie einen eleganteren Weg bevorzugen (oder wir sollten ihn als idiomatisch bezeichnen), würde ich persönlich vorschlagen:
Mögliche Leistungssteigerungen sind:
Double
zu wechselndouble
, und wenn Sie sie wirklich brauchen, können Sie wieder zurückschalten. Nach den Tests ist dies möglicherweise immer noch schneller. (Aber vertraue mir nicht, versuche es selbst mit deinen Daten in deiner Umgebung. Wie gesagt - repl.it ist scheiße für Benchmarks)Ein bisschen Magie: separate Schleife für
Math.round()
... vielleicht hat es etwas mit Datenlokalität zu tun. Ich empfehle dies nicht - das Ergebnis ist sehr instabil. Aber es macht Spaß.Sie sollten auf jeden Fall in Betracht ziehen, fauler zu sein und bei Bedarf Zahlen zu generieren, ohne diese dann in
List
s zu speichernWenn Sie etwas vermuten - testen Sie es :-) Meine Antwort lautet "Ja", aber noch einmal ... glauben Sie mir nicht. Probier es aus.
Zurück zur Hauptfrage: Gibt es einen besseren Weg?
Ja natürlich!
Aber es kommt darauf an.
Double
und darüber hinaus, verwenden Sie sie mit Zahlen von "enger" Größe - keine Notwendigkeit für sie! Überprüfen Sie die gleiche Antwort: https://repl.it/repls/RespectfulSufficientWorker - Der letzte Test zeigt, dass es keinen Unterschied in den Ergebnissen gibt , sondern einen Geschwindigkeitsverlust.Davon abgesehen geht es dir gut.
PS . Es gibt auch eine Implementierung der Kahan Summation Formula in der Antwort ... nur zum Spaß. https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html#1346 und es funktioniert - Sie können Summierungsfehler verringern
quelle