实际上,我很惊讶我无法在这里找到答案,尽管也许我只是使用了错误的搜索词或其他内容。我能找到的最接近的是this,但是他们询问要生成具有特定步长的double
特定范围,答案也是如此。我需要一些可以生成具有任意开始,结束和步长数字的数字。
我认为已经在某个地方的某个库中已经有类似的方法,但是如果是这样,我将无法轻松找到它(再次,也许我只是使用了错误的搜索词或其他内容) 。因此,这是我在过去几分钟内自行完成的操作:
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);
}
}
这些方法运行一个简单的循环,将step
与序列索引相乘,然后加上start
偏移量。这样可以减轻由于连续递增而产生的复合浮点错误(例如,在每次迭代中将step
添加到变量)。
我为小数步长会导致明显的浮点错误的情况添加了generateSequenceRounded
方法。它确实需要更多的算术运算,因此在对性能非常敏感的情况下(例如我们的情况),当不需要舍入时,可以选择使用更简单的方法。我怀疑在大多数通用情况下,舍入开销可以忽略不计。
[请注意,为简化起见并希望专注于当前问题,我故意排除了用于处理“ Infinity
,NaN
,start
,[C0
end
”或负step
大小的“异常”逻辑的逻辑。这里有一些用法示例和相应的输出:
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]
是否已经存在提供这种功能的现有库?
如果没有,我的方法是否有问题?
有人对此有更好的方法吗?
可以使用Java 11 Stream API轻松生成序列。
直接的方法是使用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()); }
在具有大量迭代的范围上,
double
精度误差可能会累积,导致靠近范围终点的误差更大。可以通过切换到IntStream
并使用整数和单双倍乘数来最小化错误:
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()); }
要完全消除
double
精度误差,可以使用BigDecimal
:
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()); }
示例:
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] }
具有此签名(3个参数)的方法iterate已在Java 9中添加。因此,对于Java 8,代码如下所示
DoubleStream.iterate(start, d -> d + step)
.limit((int) (1 + (end - start) / step))
我个人,我会把DoubleSequenceGenerator类缩短一些其他的东西,并且只使用一个sequence generator
方法,该方法包含使用想要的任何所需精度或不使用精度的选项完全:在下面的生成器方法中,如果没有为可选的setPrecision参数提供任何值(或任何值[[小于 0),则不进行十进制精度舍入。如果为精确度值提供了[[0,那么数字将四舍五入为最接近的whole数字(即:89.674被四舍五入为90.0)。如果提供了特定的精度值大于0
,则值将转换为该十进制精度。BigDecimal在这里用于...很好....精度:import java.util.List;
import java.util.ArrayList;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class DoubleSequenceGenerator {
public static List<Double> generateSequence(double start, double end,
double step, int... setPrecision) {
int precision = -1;
if (setPrecision.length > 0) {
precision = setPrecision[0];
}
List<Double> sequence = new ArrayList<>();
for (double val = start; val < end; val+= step) {
if (precision > -1) {
sequence.add(BigDecimal.valueOf(val).setScale(precision, RoundingMode.HALF_UP).doubleValue());
}
else {
sequence.add(BigDecimal.valueOf(val).doubleValue());
}
}
if (sequence.get(sequence.size() - 1) < end) {
sequence.add(end);
}
return sequence;
}
// Other class goodies here ....
}
并且在main()中:
System.out.println(generateSequence(0.0, 2.0, 0.2)); System.out.println(generateSequence(0.0, 2.0, 0.2, 0)); System.out.println(generateSequence(0.0, 2.0, 0.2, 1)); System.out.println(); System.out.println(generateSequence(0.0, 102.0, 10.2, 0)); System.out.println(generateSequence(0.0, 102.0, 10.2, 0)); System.out.println(generateSequence(0.0, 102.0, 10.2, 1));
控制台显示:
[0.0, 0.2, 0.4, 0.6000000000000001, 0.8, 1.0, 1.2, 1.4, 1.5999999999999999, 1.7999999999999998, 1.9999999999999998, 2.0]
[0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 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.2, 71.4, 81.60000000000001, 91.80000000000001, 102.0]
[0.0, 10.0, 20.0, 31.0, 41.0, 51.0, 61.0, 71.0, 82.0, 92.0, 102.0]
[0.0, 10.2, 20.4, 30.6, 40.8, 51.0, 61.2, 71.4, 81.6, 91.8, 102.0]
public static List<Double> generateSequenceRounded(double start, double end, double step) {
long mult = (long) Math.pow(10, BigDecimal.valueOf(step).scale());
return DoubleStream.iterate(start, d -> (double) Math.round(mult * (d + step)) / mult)
.limit((long) (1 + (end - start) / step)).boxed().collect(Collectors.toList());
}
这里,
int java.math.BigDecimal.scale()
返回此BigDecimal的小数位数。如果是零或正数,则小数位数是小数点右边的位数。如果为负,则数字的未标度值乘以十,即标度取反的幂。例如,小数位数为-3表示未缩放的值乘以1000。
在main()
System.out.println(generateSequenceRounded(0.0, 102.0, 10.2)); System.out.println(generateSequenceRounded(0.0, 102.0, 10.24367));
和输出:
[0.0, 10.2, 20.4, 30.6, 40.8, 51.0, 61.2, 71.4, 81.6, 91.8, 102.0]
[0.0, 10.24367, 20.48734, 30.73101, 40.97468, 51.21835, 61.46202, 71.70569, 81.94936, 92.19303]
抱歉,我不知道,但是根据其他答案及其相对简单性来判断-不,没有。没必要。好吧,几乎...
是,不是。您至少有一个bug,还有一些提升性能的空间,但是这种方法本身是正确的。
您的错误:舍入错误(只需将while (mult*fraction < 1.0)
更改为while (mult*fraction < 10.0)
,这应该可以解决)
end
...好吧,也许他们只是不够细心以至无法阅读您代码中的注释int < Double
更改为int < int
,就会显着提高代码速度嗯...以什么方式?
简单吗? @Evgeniy Khyst的generateSequenceDoubleStream
看起来很简单。并且应该使用...但可能不会,因为接下来的两点
generateSequenceDoubleStream
不是!但是仍然可以用模式start + step*i
保存。而且start + step*i
模式很精确。只有BigDouble
和定点算术可以击败它。但是BigDouble
很慢,并且手动定点运算很繁琐,可能不适合您的数据。顺便说一句,在精度方面,您可以这样娱乐自己:https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.htmlList
?那可能是一个完全不同的故事。此代码对我来说很简单,并且足够快:
public static List<Double> genNoRoundDirectToDouble(double start, double end, double step) {
int len = (int)Math.ceil((end-start)/step) + 1;
var sequence = new ArrayList<Double>(len);
sequence.add(start);
for (int i=1 ; i < len ; ++i) sequence.add(start + step*i);
return sequence;
}
[如果您喜欢更优雅的方式(或者我们应该称之为惯用语),我个人将建议:
public static List<Double> gen_DoubleStream_presice(double start, double end, double step) { return IntStream.range(0, (int)Math.ceil((end-start)/step) + 1) .mapToDouble(i -> start + i * step) .boxed() .collect(Collectors.toList()); }
无论如何,可能的性能提升是:
尝试从
Double
切换到double
,如果确实需要它们,则可以根据测试判断再次切换回去,它可能会更快。 (但是,请不要相信我,请在您的环境中使用您的数据自己尝试一下。正如我说的那样-repl.it很容易成为基准测试)]
- 一个小魔术:
Math.round()
的单独循环...也许与数据局部性有关。我不建议这样做-结果非常不稳定。但这很有趣。double[] sequence = new double[len]; for (int i=1; i < len; ++i) sequence[i] = start + step*i; List<Double> list = new ArrayList<Double>(len); list.add(start); for (int i=1; i < len; ++i) list.add(Math.round(sequence[i])/mult); return list;
您绝对应该认为自己比较懒惰,可以按需生成数字,而无需存储在List
中
[如果您怀疑某事-测试它:-)我的回答是“是”,但是再次……不要相信我。测试一下。
所以,回到主要问题:还有更好的方法吗?当然是!但这取决于。
选择
Double
,甚至更多,请使用“接近”数量级的数字-不需要它们!签出相同的副本:https://repl.it/repls/RespectfulSufficientWorker-上一次测试表明会出现[[结果无差异,但是会降低速度。can减少求和错误