[英]Subtle differences in output values (+/-) between float and doubles
這是對此處發現的一個較舊問題的跟進: Chaining Function Calls和用戶Mooing Duck為我提供了一個通過使用代理 Class 和代理功能的答案。 我已經成功地制作了這個 class 的模板,它似乎正在工作。 我在float
和double
之間得到完全不同的結果......
以下是floats
和doubles
的類和應用程序的非模板版本:
只需將類、函數和代理函數中的所有float
s 替換為double
s ... 除了arguments之外,主程序不會改變。
#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;
Output 用於浮動:
-4.37114e-08,1
Output 雙:
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;
}
用於浮動的 output:
-4.37114e-08,1
output 雙打:
6.12323e-17,1
這表明我的 class 到 class 模板的轉換似乎正在工作。 我知道由於在轉換時從double
轉換為float
或從float
擴大到double
可能會丟失一些精度,但是,我似乎無法理解為什么 output 值與一個有如此大的差異對另一個...
點或向量{1,0}
旋轉 90 度或 PI/2 弧度應為{0,1}
。 我了解浮點運算的工作原理,並且為x
值生成的 output 相對接近0
,因此對於所有時態和目的,它們都應被視為0
,我可以包括使用epsilon
檢查 function 以測試它是否足夠接近到0
直接將其設置為0
這不是問題...
讓我好奇的是,為什么浮點數是-4.3...e-8
而雙倍是+6.1...e-17
? 在 float 情況下,我得到負值,而對於 double 情況,我得到正值。 在這兩種情況下,是的,它們都非常小並且接近於 0,這很好,但是相反的跡象,這讓我摸不着頭腦?
我正在尋求清晰以更好地了解為什么這些值會以它們的方式生成......它來自類型轉換還是由於正在使用的觸發 function? 還是兩者兼而有之? 只是試圖查明跡象的分歧來自哪里......
我需要知道是什么導致了這種細微的差異,因為它與我對這個 class 及其生成的輸出的使用有關,當精度優於足夠好的估計時。
編輯
在使用這些 function 模板的實例化時,特別是對於旋轉 function,我開始為我的矢量對象測試<int>
類型......我開始遇到一些編譯器錯誤......翻譯和縮放功能很好,我由於loss of data
、 narrowing
和widening
轉換等類似原因,旋轉 function 僅存在問題...
我不得不將我的旋轉功能的實現更改為:
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
。 旋轉 function 仍然期望其參數的類型與正在實例化的vec2_t
object 的類型相同。 問題在於正在創建並從計算返回的vec2_t
object 的初始化。 我必須將x
和y
坐標顯式地static_cast
轉換為Ty
。 現在,當我為vec2_t<int>
嘗試上述相同的程序時,傳入90
的旋轉值,我得到的 output 正好是0,1
。
另一個有趣的事實是,通過強制角度始終為double
並始終將計算值轉換回Ty
,當我將vec2_t
實例化為double
或float
時,我總是得到正的6.123...e-17
結果案例...這也應該允許我簡化is_zero()
function 的設計,以測試這些值是否足夠接近0
以將它們顯式設置為0
。
TL;DR:無論符號如何,小數字都接近於零。 在這種情況下,你得到的數字“幾乎為零”。
我稱之為“標志痴迷”。 兩個非常小的數字是相似的,即使它們的符號不同。 在這里,您正在查看您執行的計算精度邊緣的數字。 考慮到它們的類型,它們都同樣“小”。 其他答案提示了錯誤的確切位置:)
你的問題是:
a *= (Ty)(PI / 180.0);
對於float
情況,計算結果為 1.570796371
對於double
情況,計算結果為 1.570796327
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.