Это продолжение более старого вопроса, найденного здесь: Цепочка вызовов функций и пользователь Mooing Duck предоставил мне ответ, который работает с использованием функций Proxy Class и Proxy. . Мне удалось создать шаблон этого класса, и он, похоже, работает. Я получаю совершенно разные результаты между float
и double
...
Вот не шаблонные версии классов и приложения для floats
и doubles
:
Просто замените все float
s на double
s внутри классов , функций и прокси-функций ... Основная программа не изменится, за исключением аргументов .
#include <cmath>
#include <exception>
#include <iostream>
#include <utility>
namespace pipes {
const double PI = 4 * atan(1);
struct vec2 {
float x;
float y;
};
std::ostream& operator<<(std::ostream& out, vec2 v2) {
return out << v2.x << ',' << v2.y;
}
vec2 translate(vec2 in, float a) {
return vec2{ in.x + a, in.y + a };
}
vec2 rotate(vec2 in, float a) {
// convert a in degrees to radians:
a *= (float)(PI / 180.0);
return vec2{ in.x*cos(a) - in.y*sin(a),
in.x*sin(a) + in.y*cos(a) };
}
vec2 scale(vec2 in, float a) {
return vec2{ in.x*a, in.y*a };
}
// proxy class
template<class rhst, vec2(*f)(vec2, rhst)>
class vec2_op1 {
std::decay_t<rhst> rhs; // store the parameter until the call
public:
vec2_op1(rhst rhs_) : rhs(std::forward<rhst>(rhs_)) {}
vec2 operator()(vec2 lhs) { return f(lhs, std::forward<rhst>(rhs)); }
};
// proxy methods
vec2_op1<float, translate> translate(float a) { return { a }; }
vec2_op1<float, rotate> rotate(float a) { return { a }; }
vec2_op1<float, scale> scale(float a) { return { a }; }
// lhs is the object, rhs is the operation on the object
template<class rhst, vec2(*f)(vec2, rhst)>
vec2& operator|(vec2& lhs, vec2_op1<rhst, f>&& op) { return lhs = op(lhs); }
} // namespace pipes
int main() {
try {
pipes::vec2 a{ 1.0, 0.0 };
pipes::vec2 b = (a | pipes::rotate(90.0));
std::cout << b << '\n';
} catch (const std::exception& e) {
std::cerr << e.what() << "\n\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
Вывод для float:
-4.37114e-08,1
Вывод для double:
6.12323e-17,1
Вот шаблонная версия ...
#include <cmath>
#include <exception>
#include <iostream>
#include <utility>
namespace pipes {
const double PI = 4 * atan(1);
template<typename Ty>
struct vec2_t {
Ty x;
Ty y;
};
template<typename Ty>
std::ostream& operator<<(std::ostream& out, vec2_t<Ty> v2) {
return out << v2.x << ',' << v2.y;
}
template<typename Ty>
vec2_t<Ty> translate(vec2_t<Ty> in, Ty a) {
return vec2_t<Ty>{ in.x + a, in.y + a };
}
template<typename Ty>
vec2_t<Ty> rotate(vec2_t<Ty> in, Ty a) {
// convert a in degrees to radians:
a *= (Ty)(PI / 180.0);
return vec2_t<Ty>{ in.x*cos(a) - in.y*sin(a),
in.x*sin(a) + in.y*cos(a) };
}
template<typename Ty>
vec2_t<Ty> scale(vec2_t<Ty> in, Ty a) {
return vec2_t<Ty>{ in.x*a, in.y*a };
}
// proxy class
template<class rhst, typename Ty, vec2_t<Ty>(*f)(vec2_t<Ty>, rhst)>
class vec2_op1 {
std::decay_t<rhst> rhs; // store the parameter until the call
public:
vec2_op1(rhst rhs_) : rhs(std::forward<rhst>(rhs_)) {}
vec2_t<Ty> operator()(vec2_t<Ty> lhs) { return f(lhs, std::forward<rhst>(rhs)); }
};
// proxy methods
template<typename Ty>
vec2_op1<Ty, Ty, translate<Ty>> translate(Ty a) { return { a }; }
template<typename Ty>
vec2_op1<Ty, Ty, rotate<Ty>> rotate(Ty a) { return { a }; }
template<typename Ty>
vec2_op1<Ty, Ty, scale<Ty>> scale(Ty a) { return { a }; }
// overloaded | operator for chaining function calls to vec2_t objects
// lhs is the object, rhs is the operation on the object
template<class rhst, typename Ty, vec2_t<Ty>(*f)(vec2_t<Ty>, rhst)>
vec2_t<Ty>& operator|(vec2_t<Ty>& lhs, vec2_op1<rhst, Ty, f>&& op) { return lhs = op(lhs); }
} // namespace pipes
// for double just instantiate with double...
int main() {
try {
pipes::vec2_t<float> a{ 1.0f, 0.0f };
pipes::vec2_t<float> b = (a | pipes::rotate(90.0f));
std::cout << b << '\n';
} catch (const std::exception& e) {
std::cerr << e.what() << "\n\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Вывод для чисел с плавающей запятой:
-4.37114e-08,1
Вывод для удвоений:
6.12323e-17,1
Это говорит о том, что преобразование моего класса в шаблон класса, похоже, работает. Я понимаю, что может быть небольшая потеря точности из-за преобразования из double
в float
или расширения с float
до double
при кастинге, однако я не могу понять, почему существует такая разница в выходных значениях от одного к другому ...
Поворот точки или вектора {1,0}
на 90 градусов или PI / 2 радиан должен быть {0,1}
. Я понимаю, как работает арифметика с плавающей запятой c и что сгенерированный вывод для значений x
относительно близок к 0
, поэтому их следует рассматривать 0
для любого времени и целей, и я могу включить использование epsilon
функция проверки, чтобы проверить, достаточно ли оно близко к 0
, чтобы установить его прямо на 0
, что не является проблемой ...
Меня заинтриговало мое любопытство, почему это -4.3...e-8
для float и +6.1...e-17
для двойного? В случае с плавающей точкой я получаю отрицательные значения, а в случае двойного значения - положительные. В обоих случаях да, они очень маленькие и близки к 0, что нормально, но противоположные признаки, из-за которых я чешу затылок?
Я ищу ясности, чтобы лучше понять, почему эти значения принимаются генерируются такими, какие они есть ... Это происходит из-за преобразования типа или из-за используемой функции триггера? Или сочетание того и другого? Просто пытаюсь определить, откуда исходит расхождение знаков ...
Мне нужно знать, что вызывает эту тонкую разницу, поскольку она будет иметь отношение к моему использованию этого класса и его сгенерированных выходных данных, когда точность предпочтительнее, чем достаточно хорошие оценки.
Редактировать
При работе с экземплярами этих шаблонов функций, особенно для функции поворота, я начал test <int>
type для моих векторных объектов ... Я начал получать некоторые ошибки компилятора ... Функции перевода и масштабирования были в порядке, у меня была проблема только с функцией поворота по тем же причинам loss of data
, narrowing
и widening
преобразований и т.д. c ...
Мне пришлось изменить реализацию моей функции поворота на это:
template<typename Ty>
vec2_t<Ty> rotate(vec2_t<Ty> in, Ty a) {
// convert a in degrees to radians:
auto angle = (double)(a * (PI / 180.0));
return vec2_t<Ty>{ static_cast<Ty>( in.x*cos(angle) - in.y*sin(angle) ),
static_cast<Ty>( in.x*sin(angle) + in.y*cos(angle) )
};
}
Здесь я заставляю угол всегда быть double
независимо от типа Ty
. Функция поворота по-прежнему ожидает в качестве аргумента того же типа, что и тип объекта vec2_t
, экземпляр которого создается. Проблема заключалась в инициализации объекта vec2_t
, который создавался и возвращался в результате вычислений. Мне пришлось явно указать static_cast
координаты x
и y
на Ty
. Теперь, когда я пробую ту же программу для vec2_t<int>
, передавая значение поворота 90
, я получаю ровно 0,1
для своего вывода.
Еще один интересный факт, заставляя угол всегда быть double
и всегда возвращая вычисленные значения обратно к Ty
, когда я создаю свой vec2_t
как double
или float
, я всегда получаю положительный результат 6.123...e-17
для обоих случаев ... Это также должно позволить мне упростить дизайн функции is_zero()
, чтобы проверить, достаточно ли близки эти значения к 0
, чтобы явно установить их на 0
.