En realidad estoy muy sorprendido de que yo era incapaz de encontrar la respuesta a esta aquí, aunque tal vez sólo estoy usando mal los términos de búsqueda o algo así. Más cercano que pude encontrar es esta , sino que se preguntan sobre la generación de un rango específico de double
s con un tamaño de paso específica, y las respuestas tratan como tal. Necesito algo que generará los números con inicio arbitrario, extremo y tamaño de paso.
Calculo que hay tiene que haber algún método como este en una biblioteca en algún lugar ya, pero si es así yo no era capaz de encontrar fácilmente (de nuevo, tal vez sólo estoy usando los términos de búsqueda mal o algo). Así que esto es lo que he cocinado en mi propia en los últimos minutos para hacer esto:
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);
}
}
Estos métodos se ejecutan un bucle sencillo multiplicando el step
por el índice de secuencia y añadiendo a la start
offset. Este capitalización errores de punto flotante mitiga que ocurrirían con incrementación continua (tales como la adición de la step
a una variable en cada iteración).
He añadido el generateSequenceRounded
método para aquellos casos en que un tamaño de paso fraccional puede causar errores de punto flotante notables. Se requiere un poco más de la aritmética, por lo que en situaciones extremadamente sensibles rendimiento como el nuestro, es bueno tener la opción de utilizar el método más sencillo cuando el redondeo es innecesaria. Sospecho que en la mayoría de los casos de uso general de la sobrecarga redondeo sería insignificante.
Tenga en cuenta que Excluí intencionadamente lógica para el manejo de argumentos "anormales", tales como Infinity
, NaN
, start
> end
, o un negativo step
tamaño de la simplicidad y el deseo de centrarse en la cuestión que nos ocupa.
He aquí algunos ejemplo de uso y la salida correspondiente:
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]
¿Hay una biblioteca existente que ofrece este tipo de funcionalidad ya?
Si no es así, ¿hay algún problema con mi enfoque?
¿Alguien tiene una mejor aproximación a esto?
Las secuencias se pueden generar fácilmente utilizando Java API 11 Stream.
El enfoque sencillo es utilizar DoubleStream
:
public static List<Double> generateSequenceDoubleStream(double start, double end, double step) {
return DoubleStream.iterate(start, d -> d <= end, d -> d + step)
.boxed()
.collect(toList());
}
En rangos con un gran número de iteraciones, double
error de precisión podría acumular resultante en el error más grande más próximo al extremo de la gama. El error puede ser minimizado por el cambio a IntStream
y el uso de números enteros y solo doble multiplicador:
public static List<Double> generateSequenceIntStream(int start, int end, int step, double multiplier) {
return IntStream.iterate(start, i -> i <= end, i -> i + step)
.mapToDouble(i -> i * multiplier)
.boxed()
.collect(toList());
}
Para deshacerse de un double
error de precisión en absoluto, BigDecimal
se puede utilizar:
public static List<Double> generateSequenceBigDecimal(BigDecimal start, BigDecimal end, BigDecimal step) {
return Stream.iterate(start, d -> d.compareTo(end) <= 0, d -> d.add(step))
.mapToDouble(BigDecimal::doubleValue)
.boxed()
.collect(toList());
}
Ejemplos:
public static void main(String[] args) {
System.out.println(generateSequenceDoubleStream(0.0, 2.0, 0.2));
//[0.0, 0.2, 0.4, 0.6000000000000001, 0.8, 1.0, 1.2, 1.4, 1.5999999999999999, 1.7999999999999998, 1.9999999999999998]
System.out.println(generateSequenceIntStream(0, 20, 2, 0.1));
//[0.0, 0.2, 0.4, 0.6000000000000001, 0.8, 1.0, 1.2000000000000002, 1.4000000000000001, 1.6, 1.8, 2.0]
System.out.println(generateSequenceBigDecimal(new BigDecimal("0"), new BigDecimal("2"), new BigDecimal("0.2")));
//[0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0]
}
Método iterate con esta firma (3 parámetros) se añadió en Java 9. Por lo tanto, para Java 8 las miradas de código como
DoubleStream.iterate(start, d -> d + step)
.limit((int) (1 + (end - start) / step))