在给定开始、结束和步骤的情况下生成 List<Double> 值序列的最佳方法?
Best way to generate a List<Double> sequence of values given start, end, and step?
我真的很惊讶我无法在这里找到这个问题的答案,虽然也许我只是使用了错误的搜索词或其他东西。我能找到的最接近的是 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
方法来处理小数步长可能导致明显浮点错误的情况。它确实需要更多的算法,所以在像我们这样对性能极其敏感的情况下,在不需要舍入时可以选择使用更简单的方法是很好的。我怀疑在大多数一般用例中,舍入开销可以忽略不计。
请注意,我有意排除了处理 "abnormal" 参数的逻辑,例如 Infinity
、NaN
、start
> 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]
是否已有提供此类功能的库?
如果不是,我的方法有什么问题吗?
有人对此有更好的方法吗?
就我个人而言,我会缩短 DoubleSequenceGenerator class 一些其他好东西,并且只使用一个 序列生成器 方法,包含使用任何所需精度或根本不使用精度的选项:
在下面的生成器方法中,如果没有向可选的 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]
可以使用 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]
}
在 Java 9 中添加了具有此签名(3 个参数)的方法 iterate。因此,对于 Java 8,代码看起来像
DoubleStream.iterate(start, d -> d + step)
.limit((int) (1 + (end - start) / step))
试试这个。
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()
Returns 这个 BigDecimal 的小数位数。如果为零或正数,则小数位数是小数点右边的位数。如果为负数,则将数字的未缩放值乘以 10 的缩放负数次方。例如,-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]
Is there an existing library that provides this kind of functionality already?
抱歉,我不知道,但从其他答案及其相对简单性来看 - 不,没有。不需要。嗯,差不多...
If not, are there any issues with my approach?
是也不是。您至少有一个错误,并且有一定的性能提升空间,但方法本身是正确的。
- 您的错误:舍入错误(只需将
while (mult*fraction < 1.0)
更改为 while (mult*fraction < 10.0)
即可解决)
- 所有其他人都没有达到
end
...好吧,也许他们只是不够细心,无法阅读您代码中的注释
- 其他的都比较慢。
- 只需将主循环中的条件从
int < Double
更改为 int < int
即可显着提高代码速度
Does anyone have a better approach to this?
嗯...以什么方式?
- 简单吗? @Evgeniy Khyst 的
generateSequenceDoubleStream
看起来很简单。并且应该使用...但也许不会,因为接下来的两点
- 准确吗?
generateSequenceDoubleStream
不是!但仍然可以用模式 start + step*i
保存。
start + step*i
模式是精确的。只有BigDouble
和定点运算能打败它。但是 BigDouble
s 很慢,手动定点运算很乏味,可能不适合您的数据。
顺便说一句,关于精度的问题,你可以自娱自乐:https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
速度...好吧,现在我们站稳了脚跟。
查看此回复 https://repl.it/repls/RespectfulSufficientWorker
我现在没有像样的测试台,所以我使用了repl.it...,这对于性能测试来说是完全不够的,但这不是重点。关键是——没有确定的答案。除了可能在你的情况下,你的问题并不完全清楚,你绝对不应该使用 BigDecimal (进一步阅读)。
我试过针对大投入进行游戏和优化。和你的原始代码,有一些小的变化 - 最快。但也许您需要大量的小 List
s?那么这可能是一个完全不同的故事。
这段代码对我来说很简单,而且速度足够快:
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
s
I suspect that in most general use cases the rounding overhead would be negligible.
如果您怀疑某些东西 - 测试它:-) 我的答案是 "Yes",但是再次......不要相信我。测试一下。
所以,回到主要问题:有没有更好的方法?
是的,当然!
但这取决于。
- 如果您需要非常大的数字和非常[=66=,请选择大小数]small 个数字。但是,如果您将它们转换回
Double
,甚至更多,则将其与 "close" 数量级的数字一起使用 - 不需要它们!检查相同的 repl:https://repl.it/repls/RespectfulSufficientWorker - 上次测试表明 结果没有差异 ,但挖掘速度有所下降。
- 根据您的数据属性、任务和环境进行一些微优化。
- 如果从 5-10% 的性能提升中获益不大,则更喜欢简短的代码。不要浪费你的时间
- 如果可以并且值得的话,也许使用定点运算。
除此之外,你还好。
PS。在 repl 中还有一个 Kahan Summation Formula 实现......只是为了好玩。 https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html#1346 它有效 - 你 可以 减少求和错误
我真的很惊讶我无法在这里找到这个问题的答案,虽然也许我只是使用了错误的搜索词或其他东西。我能找到的最接近的是 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
方法来处理小数步长可能导致明显浮点错误的情况。它确实需要更多的算法,所以在像我们这样对性能极其敏感的情况下,在不需要舍入时可以选择使用更简单的方法是很好的。我怀疑在大多数一般用例中,舍入开销可以忽略不计。
请注意,我有意排除了处理 "abnormal" 参数的逻辑,例如 Infinity
、NaN
、start
> 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]
是否已有提供此类功能的库?
如果不是,我的方法有什么问题吗?
有人对此有更好的方法吗?
就我个人而言,我会缩短 DoubleSequenceGenerator class 一些其他好东西,并且只使用一个 序列生成器 方法,包含使用任何所需精度或根本不使用精度的选项:
在下面的生成器方法中,如果没有向可选的 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]
可以使用 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]
}
在 Java 9 中添加了具有此签名(3 个参数)的方法 iterate。因此,对于 Java 8,代码看起来像
DoubleStream.iterate(start, d -> d + step)
.limit((int) (1 + (end - start) / step))
试试这个。
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()
Returns 这个 BigDecimal 的小数位数。如果为零或正数,则小数位数是小数点右边的位数。如果为负数,则将数字的未缩放值乘以 10 的缩放负数次方。例如,-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]
Is there an existing library that provides this kind of functionality already?
抱歉,我不知道,但从其他答案及其相对简单性来看 - 不,没有。不需要。嗯,差不多...
If not, are there any issues with my approach?
是也不是。您至少有一个错误,并且有一定的性能提升空间,但方法本身是正确的。
- 您的错误:舍入错误(只需将
while (mult*fraction < 1.0)
更改为while (mult*fraction < 10.0)
即可解决) - 所有其他人都没有达到
end
...好吧,也许他们只是不够细心,无法阅读您代码中的注释 - 其他的都比较慢。
- 只需将主循环中的条件从
int < Double
更改为int < int
即可显着提高代码速度
- 您的错误:舍入错误(只需将
Does anyone have a better approach to this?
嗯...以什么方式?
- 简单吗? @Evgeniy Khyst 的
generateSequenceDoubleStream
看起来很简单。并且应该使用...但也许不会,因为接下来的两点 - 准确吗?
generateSequenceDoubleStream
不是!但仍然可以用模式start + step*i
保存。start + step*i
模式是精确的。只有BigDouble
和定点运算能打败它。但是BigDouble
s 很慢,手动定点运算很乏味,可能不适合您的数据。 顺便说一句,关于精度的问题,你可以自娱自乐:https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html 速度...好吧,现在我们站稳了脚跟。 查看此回复 https://repl.it/repls/RespectfulSufficientWorker 我现在没有像样的测试台,所以我使用了repl.it...,这对于性能测试来说是完全不够的,但这不是重点。关键是——没有确定的答案。除了可能在你的情况下,你的问题并不完全清楚,你绝对不应该使用 BigDecimal (进一步阅读)。
我试过针对大投入进行游戏和优化。和你的原始代码,有一些小的变化 - 最快。但也许您需要大量的小
List
s?那么这可能是一个完全不同的故事。这段代码对我来说很简单,而且速度足够快:
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
s
- 简单吗? @Evgeniy Khyst 的
I suspect that in most general use cases the rounding overhead would be negligible.
如果您怀疑某些东西 - 测试它:-) 我的答案是 "Yes",但是再次......不要相信我。测试一下。
所以,回到主要问题:有没有更好的方法?
是的,当然!
但这取决于。
- 如果您需要非常大的数字和非常[=66=,请选择大小数]small 个数字。但是,如果您将它们转换回
Double
,甚至更多,则将其与 "close" 数量级的数字一起使用 - 不需要它们!检查相同的 repl:https://repl.it/repls/RespectfulSufficientWorker - 上次测试表明 结果没有差异 ,但挖掘速度有所下降。 - 根据您的数据属性、任务和环境进行一些微优化。
- 如果从 5-10% 的性能提升中获益不大,则更喜欢简短的代码。不要浪费你的时间
- 如果可以并且值得的话,也许使用定点运算。
除此之外,你还好。
PS。在 repl 中还有一个 Kahan Summation Formula 实现......只是为了好玩。 https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html#1346 它有效 - 你 可以 减少求和错误