De hecho, estoy muy sorprendido de que no haya podido encontrar la respuesta a esto aquí, aunque tal vez solo estoy usando los términos de búsqueda incorrectos o algo así. Lo más cercano que pude encontrar es esto , pero preguntan sobre la generación de un rango específico de double
s con un tamaño de paso específico, y las respuestas lo tratan como tal. Necesito algo que genere los números con inicio arbitrario, final 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 solo 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 ejecutan un bucle simple multiplicando el step
por el índice de secuencia y agregando al start
desplazamiento. Esto mitiga los errores de coma flotante compuestos que ocurrirían con un incremento continuo (como agregar el step
a una variable en cada iteración).
Agregué el generateSequenceRounded
método para aquellos casos en que un tamaño de paso fraccional puede causar errores notables de punto flotante. Requiere un poco más de aritmética, por lo que en situaciones extremadamente sensibles al rendimiento como la nuestra, es bueno tener la opción de usar el método más simple cuando el redondeo es innecesario. Sospecho que en la mayoría de los casos de uso general, la sobrecarga de 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.
Aquí hay un ejemplo de uso y 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]
¿Existe ya una biblioteca que brinde este tipo de funcionalidad?
Si no, ¿hay algún problema con mi enfoque?
¿Alguien tiene un mejor enfoque para esto?
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
. Errores similares paraIntStream.iterate
yStream.iterate
. Además,non-static method doubleValue() cannot be referenced from a static context
.Yo personalmente, acortaría un poco la clase DoubleSequenceGenerator para otras cosas y usaría solo un método generador de secuencia que contiene la opción de utilizar la precisión deseada o no utilizar ninguna precisión:
En el siguiente método del generador, si no se proporciona nada (o cualquier valor menor que 0) al parámetro opcional setPrecision , entonces no se lleva a cabo el redondeo de precisión decimal. Si se proporciona 0 para un valor de precisión, los números se redondean a su número entero más cercano (es decir: 89.674 se redondea a 90.0). Si se proporciona un valor de precisión específico mayor que 0 , los valores se convierten a esa precisión decimal.
BigDecimal se usa aquí para ... bueno ... precisión:
Y en main ():
Y la consola muestra:
fuente
val
cada iteración, se obtiene una pérdida de precisión aditiva. Para secuencias muy grandes, el error en los últimos números podría ser significativo. 2. Las llamadas repetidas aBigDecimal.valueOf()
son relativamente caras. Obtendrá un mejor rendimiento (y precisión) al convertir las entradas aBigDecimal
sy usarlasBigDecimal
paraval
. De hecho, al usar undouble
forval
, realmente no obtienes ningún beneficio de precisión al usarlo,BigDecimal
excepto quizás con el redondeo.Prueba esto.
Aquí,
Devuelve la escala de este BigDecimal. Si es cero o positivo, la escala es el número de dígitos a la derecha del punto decimal. Si es negativo, el valor sin escala del número se multiplica por diez a la potencia de la negación de la escala. Por ejemplo, una escala de -3 significa que el valor sin escala se multiplica por 1000.
En main ()
Y salida:
fuente
Lo siento, no lo sé, pero a juzgar por otras respuestas y su relativa simplicidad, no, no la hay. No hay necesidad. Bueno, casi...
Si y no. Tiene al menos un error y algo de espacio para aumentar el rendimiento, pero el enfoque en sí es correcto.
while (mult*fraction < 1.0)
awhile (mult*fraction < 10.0)
y que debe solucionarlo)end
... bueno, tal vez simplemente no fueron lo suficientemente observadores para leer los comentarios en su códigoint < Double
aint < int
aumentará notablemente la velocidad de su códigoHmm ... ¿De qué manera?
generateSequenceDoubleStream
de @Evgeniy Khyst parece bastante simple. Y debería usarse ... pero tal vez no, debido a los siguientes dos puntosgenerateSequenceDoubleStream
¡no es! Pero aún se puede guardar con el patrónstart + step*i
. Y elstart + step*i
patrón es preciso. SoloBigDouble
y la aritmética de punto fijo puede vencerlo. Pero losBigDouble
s son lentos, y la aritmética manual de punto fijo es tediosa y puede ser inapropiada para sus datos. Por cierto, en materia de precisión, puede entretenerse con esto: https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.htmlVelocidad ... bueno, ahora estamos en terrenos inestables. Echa un vistazo a esta respuesta https://repl.it/repls/RespectfulSufficientWorker No tengo un banco de pruebas decente en este momento, así que utilicé repl.it ... que es totalmente inadecuado para las pruebas de rendimiento, pero no es el punto principal. El punto es que no hay una respuesta definitiva. Excepto que tal vez en su caso, que no está totalmente claro en su pregunta, definitivamente no debe usar BigDecimal (lea más).
He intentado jugar y optimizar para grandes entradas. Y su código original, con algunos cambios menores, el más rápido. ¿Pero tal vez necesitas enormes cantidades de pequeños
List
s? Entonces esa puede ser una historia totalmente diferente.Este código es bastante simple para mi gusto y lo suficientemente rápido:
Si prefiere una forma más elegante (o deberíamos llamarlo idiomático), personalmente, sugeriría:
De todos modos, los posibles aumentos de rendimiento son:
Double
adouble
, y si realmente los necesita, puede volver a cambiar, a juzgar por las pruebas, aún puede ser más rápido. (Pero no confíes en mi, pruébalo tú mismo con tus datos en tu entorno. Como dije, repl.it apesta a los puntos de referencia)Un poco de magia: bucle separado para
Math.round()
... tal vez tenga algo que ver con la localidad de datos. No lo recomiendo, el resultado es muy inestable. Pero es divertido.Definitivamente debería considerar ser más vago y generar números a pedido sin almacenarlos en
List
sSi sospecha algo, pruébelo :-) Mi respuesta es "Sí", pero de nuevo ... no me crea. Pruébalo.
Entonces, volviendo a la pregunta principal: ¿hay una mejor manera?
¡Sí, por supuesto!
Pero eso depende.
Double
, y más que eso, úselo con números de magnitud "cercana", ¡no los necesita! Consulte la misma respuesta: https://repl.it/repls/RespectfulSufficientWorker : la última prueba muestra que no habrá diferencias en los resultados , sino una pérdida de velocidad en la excavación.Aparte de eso, estás bien.
PS . También hay una implementación de Kahan Summation Formula en la respuesta ... solo por diversión. https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html#1346 y funciona: puede mitigar los errores de suma
fuente