Я на самом деле очень удивлен, что не смог найти ответ на этот вопрос здесь, хотя, возможно, я просто использую неправильные условия поиска или что-то в этом роде. Самое близкое, что я мог бы найти, это , но они спрашивают о генерации определенного диапазона double
s с определенным размером шага, и ответы рассматривают это как таковой. Мне нужно что-то, что будет генерировать числа с произвольным размером начала, конца и шага.
Я полагаю, что где-то уже должен быть какой-то метод, подобный этому, в библиотеке, но если это так, я не смог найти его легко (опять же, возможно, я просто использую неправильные условия поиска или что-то в этом роде). Итак, вот что я приготовил самостоятельно за последние несколько минут, чтобы сделать это:
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
> 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]
Существует ли уже существующая библиотека, которая уже обеспечивает такую функциональность?
Если нет, есть ли проблемы с моим подходом?
У кого-нибудь есть лучший подход к этому?
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
. Подобные ошибки дляIntStream.iterate
иStream.iterate
. Такжеnon-static method doubleValue() cannot be referenced from a static context
.Лично я бы немного укоротил класс DoubleSequenceGenerator для других полезностей и использовал бы только один метод генератора последовательностей, который содержит опцию для использования любой желаемой точности или вообще никакой точности:
В приведенном ниже методе генератора, если ничего не указано (или любое значение меньше 0) для необязательного параметра setPrecision, округление с десятичной точностью не выполняется. Если для значения точности задано 0, то числа округляются до ближайшего целого числа (т. Е. 89,674 округляется до 90,0). Если конкретное значение точности больше 0 то значения преобразуются в эту десятичную точность.
BigDecimal используется здесь для ... ну .... точности:
И в основном ():
И консоль отображает:
источник
val
каждой итерации, вы получаете аддитивные потери точности. Для очень больших последовательностей ошибка в последних нескольких числах может быть значительной. 2. Повторные звонкиBigDecimal.valueOf()
относительно дороги. Вы получите лучшую производительность (и точность), преобразовав входные данные вBigDecimal
s и используяBigDecimal
forval
. На самом деле, используяdouble
forval
, вы на самом деле не получаете никакой прецизионной выгоды от использования,BigDecimal
за исключением, возможно, округления.Попробуй это.
Вот,
Возвращает масштаб этого BigDecimal. Если ноль или положительный, шкала - это число цифр справа от десятичной точки. Если оно отрицательное, немасштабированное значение числа умножается на десять до степени отрицания шкалы. Например, шкала -3 означает, что немасштабированное значение умножается на 1000.
В основном ()
И вывод:
источник
Извините, я не знаю, но, судя по другим ответам и их относительной простоте - нет, нет. Нет нужды. Ну, почти...
Да и нет. У вас есть хотя бы одна ошибка, и есть место для повышения производительности, но сам подход правильный.
while (mult*fraction < 1.0)
наwhile (mult*fraction < 10.0)
и это должно исправить)end
... ну, может быть, они просто недостаточно внимательны, чтобы читать комментарии в вашем кодеint < Double
наint < int
заметно увеличит скорость вашего кодаХм ... Каким образом?
generateSequenceDoubleStream
Евгения Хиста выглядит довольно просто. И следует использовать ... но, возможно, нет, из-за следующих двух пунктовgenerateSequenceDoubleStream
не является! Но все же можно сохранить с рисункомstart + step*i
. Иstart + step*i
картина точная. ТолькоBigDouble
арифметика с фиксированной запятой может превзойти его. НоBigDouble
они медленные, и ручная арифметика с фиксированной запятой утомительна и может не подходить для ваших данных. Кстати, в вопросах точности вы можете развлечься этим: https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.htmlСкорость ... ну теперь мы на шатком основании. Посмотрите этот репл https://repl.it/repls/RespectfulSufficientWorker У меня сейчас нет приличного тестового стенда, поэтому я использовал repl.it ... который совершенно не подходит для тестирования производительности, но это не главное. Дело в том, что нет однозначного ответа. За исключением того, что, возможно, в вашем случае, который не совсем понятен из вашего вопроса, вам определенно не следует использовать BigDecimal (читайте дальше).
Я пытался играть и оптимизировать для больших входов. А ваш оригинальный код с некоторыми незначительными изменениями - самый быстрый. Но, может быть, вам нужно огромное количество маленьких
List
с? Тогда это может быть совершенно другая история.Этот код довольно простой на мой вкус и достаточно быстрый:
Если вы предпочитаете более элегантный способ (или мы должны назвать его идиоматическим), я бы лично предложил:
В любом случае, возможное повышение производительности:
Double
наdouble
, и если они вам действительно нужны, вы можете переключиться обратно, судя по тестам, это все еще может быть быстрее. (Но не верьте моему, попробуйте сами с вашими данными в вашей среде. Как я уже сказал - repl.it отстой для оценки)Немного волшебства: отдельный цикл для
Math.round()
... может быть, это как-то связано с локальностью данных. Я не рекомендую это - результат очень нестабилен. Но это весело.Вы определенно должны рассмотреть более ленивыми и генерировать номера по требованию без сохранения , то в
List
сЕсли вы что-то подозреваете - проверьте это :-) Мой ответ "Да", но опять же ... не верьте мне. Попробуй это.
Итак, вернемся к основному вопросу: есть ли лучший путь?
Ну конечно; естественно!
Но это зависит.
Double
, и более того, используйте это с числами «близкой» величины - в них нет необходимости! Оформить заказ тот же репл: https://repl.it/repls/RespectfulSufficientWorker - последний тест показывает, что не будет никакой разницы в результатах , но потеря скорости раскопок.Кроме этого, ты в порядке.
PS . В реплик есть также реализация формулы суммирования Кахана ... просто для удовольствия. https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html#1346 и это работает - вы можете уменьшить ошибки суммирования
источник