Я пытаюсь реализовать основные математические функции 2D-вектора для игры на Java. Они будут интенсивно использоваться в игре, поэтому я хочу, чтобы они были максимально быстрыми.

Я начал с целых чисел в качестве векторных координат, потому что игре не нужно ничего более точного для координат, но для всех вычислений мне все равно пришлось бы перейти на двойные векторы, чтобы получить четкий результат (например, пересечение двух линий).

При использовании удвоений возникают ошибки округления. Я мог бы просто игнорировать их и использовать что-то вроде

d1 - d2 <=  0.0001

Для сравнения значений, но я предполагаю, что при дальнейших расчетах ошибка может суммироваться, пока не станет значительной. Поэтому я подумал, что могу округлить их после каждой, возможно, неточной операции, но это привело к гораздо худшим результатам, предположительно потому, что программа также округляет неточные значения (например, 0.33333333... -> 0.3333300...).

Использование BigDecimal было бы слишком медленным.

Как лучше всего решить эту проблему?

2
Sinthorion 16 Окт 2015 в 18:23

2 ответа

Лучший ответ

Неточный метод

Когда вы используете числа, требующие точных вычислений, вы должны быть уверены, что вы не делаете что-то вроде: (и это то, что, похоже, вы делаете сейчас)

error accumulation

Это приведет к накоплению ошибок округления по мере продолжения процесса; давая вам чрезвычайно неточные данные в долгосрочной перспективе. В приведенном выше примере вы фактически округляете начальное float 4 раз, и с каждым разом оно становится все более и более неточным!


Точный метод

Лучший и более точный способ получения чисел - это сделать следующее: избегать накопления ошибок округления

Это поможет вам избежать накопления ошибок округления, потому что каждый расчет основан только на 1 преобразовании, и результаты этого преобразования не включаются в следующий расчет.

Наилучший метод атаки - начать с максимальной точности, которая необходима , затем преобразовать по мере необходимости, но оставить оригинал в целости и сохранности. Я предлагаю вам проследить за процессом из второй фотографии, которую я опубликовал.

Я начал с целых чисел в качестве векторных координат, потому что игре не нужно ничего более точного для координат, но для всех вычислений мне все равно пришлось бы перейти на двойные векторы, чтобы получить четкий результат (например, пересечение двух линий).

Важно отметить, что вам не следует пытаться выполнить какое-либо округление ваших значений, если это не окажет заметного влияния на ваш конечный результат; вы просто будете выполнять больше работы с небольшой прибылью или вообще без нее и даже можете столкнуться со снижением производительности, если будете делать это достаточно часто.

5
Community 20 Июн 2020 в 09:12

Это незначительное дополнение к предыдущему ответу. При преобразовании числа с плавающей запятой в целое число важно округлять, а не просто преобразовывать. В следующей программе d - это наибольшее значение двойной точности, которое строго меньше 1.0. Это могло легко возникнуть в результате вычисления, которое дало бы 1,0 в бесконечно точной арифметике вещественных чисел.

Простое приведение дает результат 0. Округление сначала дает результат 1.

public class Test {
  public static void main(String[] args) {
    double d = Math.nextDown(1.0);
    System.out.println(d);
    System.out.println((int)d);
    System.out.println((int)Math.round(d));
  }
}

Выход:

0.9999999999999999
0
1
2
Community 23 Май 2017 в 11:58