Написание осмысленных модульных тестов для кода с неточностями с плавающей запятой (например, обнаружение коллизий)

Допустим, я пишу физический движок с обнаружением столкновений, используя поплавки. Один из методов может состоять в том, чтобы проверить, пересекаются или соприкасаются два физических объекта.

class PhysicsObject {

    Vector3f position;

    [...]

    public void isIntersecting(PhysicsObject otherObject) {
        boolean isTouchingOrIntersecting = [do calculations];
        return isTouchingOrIntersecting;
    }

}

Для самой симуляции точность с плавающей запятой (даже при использовании с плавающей запятой) достаточно хороша, потому что любые неточности не будут видны/заметны (и будут немедленно исправлены на следующем этапе симуляции).

Но как мне написать свои модульные тесты, особенно для пограничных случаев? Я могу написать тесты с двумя удаленными объектами и двумя явно пересекающимися объектами, но как насчет того, чтобы они точно соприкасались? В зависимости от значений с плавающей запятой метод может возвращать true или false в зависимости от любого внешнего условия (компилятор, архитектура и т. д.).

Или я должен сказать, что не важно, какой результат возвращает метод в этом граничном случае, и поэтому нет смысла писать модульный тест для этого случая?

Когда возвращаемое значение метода снова является числом с плавающей запятой, становится ясно, что я должен использовать относительные ошибки и эпсилоны. Но я использую эту проблему обнаружения столкновений в качестве примера для всего класса подобных проблем, когда плавающие точки преобразуются в какой-то "точный" (логический, целочисленный) результат, и как их тестировать (в контексте 3D-графики/физики). код).


person jhyot    schedule 24.04.2016    source источник


Ответы (1)


Арифметика IEEE не является стохастической. Он воспроизводится бит за битом. Так что, если он работает на одной машине и компиляторе, он будет работать и на других, с одной оговоркой. Значение FLT_EVAL_METHOD={0,1,2} даст разные результаты из-за различий в точности промежуточных вычислений. Вы можете использовать #ifdefs

#if FLT_EVAL_METHOD == 0
  EXPECT_EQ(answer0, result);
#elif FLT_EVAL_METHOD == 1
  EXPECT_EQ(answer1, result);
#else /* FLT_EVAL_METHOD == 2 */
  EXPECT_EQ(answer2, result);
#endif

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

Вы можете сделать то же самое для сравнений с плавающей запятой, но если вас интересует только приблизительное равенство, вы можете использовать что-то вроде EXPECT_DOUBLE_EQ(a,b) googletest, которое проверяет равенство в пределах 4 ULP (единиц в последней позиции). Это работает следующим образом: сначала интерпретируя (используя объединение) значение с плавающей запятой как целое число со знаком, преобразуя его в целое число с избытком XX (объединяя ±0), затем проверяя, является ли разница ‹= 4. Это автоматически производит относительную меру, которая превращается в абсолютную меру, близкую к нулю.

Убедитесь, что вы используете аналогичные виды вычислений, чтобы определить, есть ли и где есть ли пересечение. Фактически, напишите модульный тест, чтобы убедиться именно в этом, и проверьте его на границе, отрегулировав местоположение на ±1, ±2, ... ULP, чтобы убедиться, что они непротиворечивы. Если запрос пересечения показывает true, но вычисления местоположения расходятся, потому что вы не можете найти пересечение, вы можете закончить бесконечным циклом или сбоем.

person Reality Pixels    schedule 08.05.2016