【发布时间】:2020-11-22 06:11:54
【问题描述】:
这是对此处找到的一个较旧问题的跟进:Chaining Function Calls 和用户Mooing Duck 为我提供了一个通过使用代理类和代理函数的答案。我已经设法为这个类模板,它似乎正在工作。我在float 和double 之间得到完全不同的结果...
以下是floats 和doubles 的类 和应用程序 的非模板版本:
只需将类、函数和代理函数中的所有floats替换为doubles...主要除了参数,程序不会改变。
#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;
浮点输出:
-4.37114e-08,1
双精度输出:
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}。我了解浮点运算的工作原理,并且为x 值生成的输出相对接近0,因此对于所有时态和目的,它们应该被视为0,我可以包括使用epsilon 检查函数来测试它是否足够接近 0 以将其直接设置为 0 这不是问题...
让我好奇的是,为什么 -4.3...e-8 代表 float 而 +6.1...e-17 代表 double?在 float 情况下,我得到负值,而对于 double 情况,我得到正值。在这两种情况下,是的,它们都非常小并且接近 0,这很好,但相反的迹象,这让我摸不着头脑?
我正在寻求澄清,以便更好地了解为什么这些值会以它们的方式生成......它来自类型转换还是由于正在使用的 trig 函数?还是两者兼而有之?只是试图查明迹象的分歧来自哪里......
我需要了解导致这种细微差异的原因,因为当精度优于足够好的估计时,这与我对此类的使用及其生成的输出有关。
编辑
在处理这些函数模板的实例化时,特别是对于旋转函数,我开始为我的向量对象测试<int> 类型...我开始遇到一些编译器错误...翻译和缩放函数是好吧,由于类似的原因loss of data、narrowing 和widening 转换等,我只遇到了旋转功能的问题...
我不得不将我的旋转功能的实现更改为:
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) )
};
}
在这里,无论Ty 的类型如何,我都强制角度始终为double。 rotate 函数仍期望其参数的类型与正在实例化的 vec2_t 对象的类型相同。问题在于正在创建并从计算中返回的 vec2_t 对象的初始化。我必须明确地将static_cast x 和y 坐标到Ty。现在,当我为vec2_t<int> 尝试上面的相同程序时,传入90 的旋转值,我得到的输出正好是0,1。
当我将vec2_t 实例化为double 或float 时,另一个有趣的事实是强制角度始终为double 并始终将计算值转换回Ty,我总是得到两种情况下的正面6.123...e-17 结果...这也应该允许我简化is_zero() 函数的设计,以测试这些值是否足够接近0 以将它们显式设置为0。
【问题讨论】:
-
由于模板正在工作,您可以删除代码的非模板版本。相反,最好添加几行用
double实例化的代码(而不仅仅是写评论)。 -
@cigien 我只是展示了在重构阶段将我的代码从非模板版本转换为模板版本的过程......我展示了两个示例以表明生成的值是正确的!跨度>
-
好的,没关系。另请注意,您正在进行缩小转换。这不会在 clang 中编译(并且 gcc 会发出警告)。
-
这正是“printf 调试”适用的问题。打印出中间值(如
a)和sin和cos的返回值,您就拥有了找出问题所在的所有信息。
标签: c++ casting output c++17 precision