Estou realmente muito surpreso por não ter encontrado a resposta para isso aqui, embora talvez esteja apenas usando os termos de pesquisa incorretos ou algo assim. O mais próximo que pude encontrar é esse , mas eles perguntam sobre a geração de um intervalo específico de double
s com um tamanho de etapa específico e as respostas o tratam como tal. Preciso de algo que gere os números com tamanho arbitrário de início, fim e etapa.
Eu acho que já deve haver algum método como esse em uma biblioteca em algum lugar, mas se assim não for possível encontrá-lo facilmente (novamente, talvez eu esteja apenas usando os termos de pesquisa incorretos ou algo assim). Então, aqui está o que eu cozinhei por conta própria nos últimos minutos para fazer isso:
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);
}
}
Esses métodos executam um loop simples multiplicando step
pelo índice de sequência e adicionando ao start
deslocamento. Isso reduz os erros de composição de ponto flutuante que ocorreriam com incrementos contínuos (como adicionar a step
uma variável em cada iteração).
Adicionei o generateSequenceRounded
método para os casos em que um tamanho de etapa fracionário pode causar erros perceptíveis de ponto flutuante. Requer um pouco mais de aritmética, portanto, em situações extremamente sensíveis ao desempenho como a nossa, é bom ter a opção de usar o método mais simples quando o arredondamento é desnecessário. Eu suspeito que, na maioria dos casos de uso geral, a sobrecarga de arredondamento seria insignificante.
Nota que eu intencionalmente excluídos lógica para lidar com argumentos "anormais", como Infinity
, NaN
, start
> end
, ou um negativo step
tamanho para simplicidade e desejam focar a questão em apreço.
Aqui estão alguns exemplos de uso e saída correspondente:
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 uma biblioteca existente que já fornece esse tipo de funcionalidade?
Caso contrário, há algum problema com minha abordagem?
Alguém tem uma abordagem melhor para isso?
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
. Erros semelhantes paraIntStream.iterate
eStream.iterate
. Além dissonon-static method doubleValue() cannot be referenced from a static context
,.Pessoalmente, reduziria um pouco a classe DoubleSequenceGenerator para outros itens e usaria apenas um método gerador de sequência que contém a opção de utilizar a precisão desejada ou não usar precisão:
No método do gerador abaixo, se nada (ou qualquer valor menor que 0) for fornecido ao parâmetro opcional setPrecision , nenhum arredondamento de precisão decimal será realizado. Se 0 for fornecido para um valor de precisão, os números serão arredondados para o número inteiro mais próximo (ou seja: 89,674 é arredondado para 90,0). Se um valor específico de precisão maior que 0 for fornecido, os valores serão convertidos para essa precisão decimal.
BigDecimal é usado aqui para ... bem ... precisão:
E em main ():
E o console exibe:
fonte
val
cada iteração, você obtém perda de precisão aditiva. Para seqüências muito grandes, o erro nos últimos números pode ser significativo. 2. Chamadas repetidas paraBigDecimal.valueOf()
são relativamente caras. Você obterá melhor desempenho (e precisão) convertendo as entradas emBigDecimal
s e usandoBigDecimal
forval
. De fato, usando adouble
paraval
, você não está obtendo nenhum benefício de precisão,BigDecimal
exceto talvez com o arredondamento.Tente isso.
Aqui,
Retorna a escala deste BigDecimal. Se zero ou positivo, a escala é o número de dígitos à direita do ponto decimal. Se negativo, o valor não escalado do número é multiplicado por dez à potência da negação da escala. Por exemplo, uma escala de -3 significa que o valor não dimensionado é multiplicado por 1000.
Principal ()
E saída:
fonte
Desculpe, eu não sei, mas a julgar por outras respostas, e sua relativa simplicidade - não, não há. Não há necessidade. Bem, quase...
Sim e não. Você tem pelo menos um bug e espaço para aumentar o desempenho, mas a abordagem em si está correta.
while (mult*fraction < 1.0)
parawhile (mult*fraction < 10.0)
e isso deve corrigi-lo)end
... bem, talvez eles simplesmente não estivessem atentos o suficiente para ler comentários em seu códigoint < Double
paraint < int
aumentará notavelmente a velocidade do seu códigoHmm ... de que maneira?
generateSequenceDoubleStream
de @Evgeniy Khyst parece bastante simples. E deve ser usado ... mas talvez não, por causa dos próximos dois pontosgenerateSequenceDoubleStream
não é! Mas ainda pode ser salvo com o padrãostart + step*i
. E ostart + step*i
padrão é preciso. Somente aBigDouble
aritmética de ponto fixo pode vencê-lo. MasBigDouble
s são lentos e a aritmética manual de ponto fixo é entediante e pode ser inadequada para seus dados. A propósito, sobre questões de precisão, você pode se divertir com isso: https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.htmlVelocidade ... bem, agora estamos em terreno instável. Confira esta repl https://repl.it/repls/RespectfulSufficientWorker Não tenho uma bancada de teste decente agora, então usei repl.it ... que é totalmente inadequado para testes de desempenho, mas não é o ponto principal. O ponto é - não há resposta definitiva. Exceto que, talvez no seu caso, o que não está totalmente claro em sua pergunta, você definitivamente não deve usar o BigDecimal (leia mais).
Eu tentei jogar e otimizar para grandes entradas. E seu código original, com algumas pequenas alterações - a mais rápida. Mas talvez você precise de enormes quantidades de pequenos
List
s? Então isso pode ser uma história totalmente diferente.Este código é bastante simples para o meu gosto e rápido o suficiente:
Se você preferir uma maneira mais elegante (ou deveríamos chamá-la de idiomática), eu pessoalmente sugeriria:
De qualquer forma, os possíveis aumentos de desempenho são:
Double
paradouble
e, se você realmente precisar deles, pode voltar novamente, a julgar pelos testes, ainda pode ser mais rápido. (Mas não confie no meu, tente você mesmo com seus dados em seu ambiente. Como eu disse - repl.it é péssimo para benchmarks)Um pouco de mágica: loop separado para
Math.round()
... talvez tenha algo a ver com a localidade dos dados. Eu não recomendo isso - o resultado é muito instável. Mas é divertido.Você definitivamente deve considerar ser mais preguiçoso e gerar números sob demanda sem armazenar em seguida em
List
sSe você suspeitar de algo - teste :-) Minha resposta é "Sim", mas novamente ... não acredite em mim. Teste-o.
Então, voltando à questão principal: existe uma maneira melhor?
Sim, claro!
Mas isso depende.
Double
e mais do que isso, use-o com números de magnitude "próxima" - sem necessidade deles! Faça o checkout da mesma repl: https://repl.it/repls/RespectfulSufficientWorker - o último teste mostra que não haverá diferença nos resultados , mas uma perda de velocidade na escavação.Fora isso, você está bem.
PS . Há também uma implementação da Kahan Summation Formula no repl ... apenas por diversão. https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html#1346 e funciona - você pode reduzir os erros de soma
fonte