Je suis en fait très surpris de ne pas avoir pu trouver la réponse à cela ici, bien que j'utilise peut-être simplement les mauvais termes de recherche ou quelque chose. Le plus proche que j'ai pu trouver est celui-ci , mais ils demandent de générer une plage spécifique de double
s avec une taille de pas spécifique, et les réponses le traitent comme tel. J'ai besoin de quelque chose qui générera les nombres avec une taille de début, de fin et de pas arbitraire.
Je pense qu'il doit déjà y avoir une méthode comme celle-ci dans une bibliothèque, mais si c'est le cas, je n'ai pas pu la trouver facilement (encore une fois, j'utilise peut-être les mauvais termes de recherche ou quelque chose). Voici donc ce que j'ai préparé par moi-même au cours des dernières minutes pour le faire:
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);
}
}
Ces méthodes exécutent une boucle simple multipliant le step
par l'index de séquence et ajoutant au start
décalage. Cela atténue les erreurs de virgule flottante qui se produiraient avec une incrémentation continue (comme l'ajout de la step
à une variable à chaque itération).
J'ai ajouté la generateSequenceRounded
méthode pour les cas où une taille de pas fractionnaire peut provoquer des erreurs notables en virgule flottante. Cela nécessite un peu plus d'arithmétique, donc dans des situations extrêmement sensibles aux performances comme la nôtre, il est agréable d'avoir la possibilité d'utiliser la méthode plus simple lorsque l'arrondi n'est pas nécessaire. Je soupçonne que dans la plupart des cas d'utilisation générale, les frais généraux d'arrondi seraient négligeables.
Notez que je la logique intentionnellement exclue pour le traitement des arguments « anormaux » tels que Infinity
, NaN
, start
> end
ou négatif step
taille pour la simplicité et le désir de se concentrer sur la question à portée de main.
Voici un exemple d'utilisation et la sortie correspondante:
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]
Existe-t-il une bibliothèque existante qui fournit déjà ce type de fonctionnalité?
Sinon, y a-t-il des problèmes avec mon approche?
Quelqu'un at-il une meilleure approche à ce sujet?
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
. Erreurs similaires pourIntStream.iterate
etStream.iterate
. Aussinon-static method doubleValue() cannot be referenced from a static context
.Personnellement , je raccourcirais un peu la classe DoubleSequenceGenerator pour d'autres goodies et n'utiliserais qu'une seule méthode de générateur de séquence qui contient l'option d'utiliser la précision souhaitée souhaitée ou de n'utiliser aucune précision du tout:
Dans la méthode de générateur ci-dessous, si rien (ou toute valeur inférieure à 0) n'est fourni au paramètre optionnel setPrecision , aucun arrondi de précision décimale n'est effectué. Si 0 est fourni pour une valeur de précision, les nombres sont arrondis à leur nombre entier le plus proche (par exemple: 89,674 est arrondi à 90,0). Si une valeur de précision spécifique supérieure à 0 est fournie, les valeurs sont converties en cette précision décimale.
BigDecimal est utilisé ici pour ... eh bien ... précision:
Et dans main ():
Et la console affiche:
la source
val
chaque itération, vous obtenez une perte de précision additive. Pour de très grandes séquences, l'erreur sur les derniers nombres pourrait être significative. 2. Les appels répétés versBigDecimal.valueOf()
sont relativement coûteux. Vous obtiendrez de meilleures performances (et précision) en convertissant les entrées enBigDecimal
s et en utilisantBigDecimal
forval
. En fait, en utilisant undouble
forval
, vous n'obtenez vraiment aucun avantage de précision,BigDecimal
sauf peut-être avec l'arrondi.Essaye ça.
Ici,
Renvoie l'échelle de ce BigDecimal. Si zéro ou positif, l'échelle est le nombre de chiffres à droite de la virgule décimale. S'il est négatif, la valeur non mise à l'échelle du nombre est multipliée par dix à la puissance de la négation de l'échelle. Par exemple, une échelle de -3 signifie que la valeur non mise à l'échelle est multipliée par 1000.
En principal ()
Et sortie:
la source
Désolé, je ne sais pas, mais à en juger par les autres réponses et leur relative simplicité - non, il n'y en a pas. Ce n'est pas nécessaire. Enfin, presque ...
Oui et non. Vous avez au moins un bogue et une certaine marge d'amélioration des performances, mais l'approche elle-même est correcte.
while (mult*fraction < 1.0)
àwhile (mult*fraction < 10.0)
et cela devrait le corriger)end
... eh bien, peut-être qu'ils n'étaient tout simplement pas assez observateurs pour lire les commentaires dans votre codeint < Double
àint < int
augmentera sensiblement la vitesse de votre codeHmm ... De quelle manière?
generateSequenceDoubleStream
de @Evgeniy Khyst semble assez simple. Et devrait être utilisé ... mais peut-être pas, à cause des deux points suivantsgenerateSequenceDoubleStream
n'est pas! Mais peut encore être enregistré avec le motifstart + step*i
. Et lestart + step*i
motif est précis. SeuleBigDouble
et l'arithmétique à virgule fixe peut le battre. MaisBigDouble
les opérations sont lentes et l'arithmétique manuelle à virgule fixe est fastidieuse et peut être inappropriée pour vos données. Soit dit en passant, sur les questions de précision, vous pouvez vous divertir avec ceci: https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.htmlLa vitesse ... eh bien maintenant nous sommes sur un terrain fragile. Découvrez cette repl https://repl.it/repls/RespectfulSufficientWorker Je n'ai pas de banc d'essai décent en ce moment, j'ai donc utilisé repl.it ... qui est totalement inadéquat pour les tests de performances, mais ce n'est pas le point principal. Le fait est - il n'y a pas de réponse définitive. Sauf que peut-être dans votre cas, ce qui ne ressort pas totalement de votre question, vous ne devriez certainement pas utiliser BigDecimal (lire plus loin).
J'ai essayé de jouer et d'optimiser pour les grosses entrées. Et votre code d'origine, avec quelques modifications mineures - le plus rapide. Mais peut-être avez-vous besoin d'énormes quantités de petits
List
s? Cela peut alors être une histoire totalement différente.Ce code est assez simple à mon goût, et assez rapide:
Si vous préférez une manière plus élégante (ou nous devrions l'appeler idiomatique), je suggère personnellement:
Quoi qu'il en soit, les améliorations possibles des performances sont les suivantes:
Double
àdouble
, et si vous en avez vraiment besoin, vous pouvez revenir en arrière, à en juger par les tests, cela peut encore être plus rapide. (Mais ne vous fiez pas à moi, essayez-le vous-même avec vos données dans votre environnement. Comme je l'ai dit - repl.it craint pour les repères)Un peu de magie: boucle séparée pour
Math.round()
... peut-être que cela a quelque chose à voir avec la localisation des données. Je ne le recommande pas - le résultat est très instable. Mais c'est amusant.Vous devriez certainement considérer être plus paresseux et générer des nombres à la demande sans les stocker ensuite dans
List
sSi vous suspectez quelque chose - testez-le :-) Ma réponse est "Oui", mais encore une fois ... ne me croyez pas. Essaye-le.
Donc, revenons à la question principale: existe-t-il une meilleure façon?
Oui bien sûr!
Mais ça dépend.
Double
, et plus encore, utilisez-les avec des nombres de magnitude "proche" - pas besoin d'eux! Vérifiez la même réponse: https://repl.it/repls/RespectfulSufficientWorker - le dernier test montre qu'il n'y aura pas de différence de résultats , mais une perte de vitesse.A part ça, tu vas bien.
PS . Il y a aussi une implémentation Kahan Summation Formula dans la repl ... juste pour le plaisir. https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html#1346 et cela fonctionne - vous pouvez atténuer les erreurs de sommation
la source