[英]Precision of floating-point data types in C++
為什么浮點數據類型的精度不與其大小成正比? 例如:
std::cout << sizeof(float) << "\n"; // this gives 4 on my machine "debian 64 bit" with "gcc 6.3.0"
std::cout << std::numeric_limits<float>::digits10 << "\n"; // gives 6
std::cout << sizeof(double) << "\n"; // gives 8
std::cout << std::numeric_limits<double>::digits10 << "\n"; // gives 15
std::cout << sizeof(long double) << "\n"; // gives 16
std::cout << std::numeric_limits<long double>::digits10 << "\n"; // gives 18
如您所見, double
的精度大約是float
精度的兩倍,這是有道理的,因為double
的大小是float
大小的兩倍。
但是double
和long double
就不一樣了, long double
double的大小是128位,是64位double
的兩倍,但是它的精度只多了三位數!!
我不知道浮點數是如何實現的,但從理性的角度來看,為了三位數的精度而多使用 64 位內存是否有意義?!
我四處搜索,但沒能找到一個簡單直接的答案。 如果有人能解釋為什么long double
的精度只比double
多三位數,你能解釋一下為什么這與double
和float
之間的情況不同嗎?
而且我還想知道如何在不定義我自己的數據類型的情況下獲得更好的精度,這顯然會以犧牲性能為代價?
“精度”並不是浮點值的全部。 它還與“幅度”有關(雖然不確定該術語是否正確:)? 表示值可以變成多大(或小)?
為此,嘗試打印每種類型的max_exponent
:
std::cout << "float: " << sizeof(float) << "\n";
std::cout << std::numeric_limits<float>::digits << "\n";
std::cout << std::numeric_limits<float>::max_exponent << "\n";
std::cout << "double: " << sizeof(double) << "\n";
std::cout << std::numeric_limits<double>::digits << "\n";
std::cout << std::numeric_limits<double>::max_exponent << "\n";
std::cout << "long double: " << sizeof(long double) << "\n";
std::cout << std::numeric_limits<long double>::digits << "\n";
std::cout << std::numeric_limits<long double>::max_exponent << "\n";
ideone上的輸出:
float: 4
24
128
double: 8
53
1024
long double: 16
64
16384
所以額外的位並不是全部用於表示更多的數字(精度)而是允許指數更大。 使用IEE 754 long double
中的措辭主要增加指數范圍而不是精度。
我上面的 ideone 示例顯示的格式(可能)顯示“x86 擴展精度格式” ,它為整數部分分配 1 位,為小數部分分配 63 位(總共 64 位)和 15 位(2^(15- 1) = 16384, 1 位用於指數的符號) 為指數。
請注意,C++ 標准只要求long double
至少與double
一樣精確,因此long double
可以是double
的同義詞,顯示的 x86 擴展精度格式(最有可能)或更好(AFAIK 僅 PowerPC 上的 GCC)。
而且我還想知道如何在不定義我自己的數據類型的情況下獲得更好的精度,這顯然會以犧牲性能為代價?
您需要自己編寫(當然是學習經驗,最好不要為生產代碼編寫)或使用庫,例如 GNU MPFR或Boost.Multiprecision 。
C++ 標准沒有為浮點類型設置固定要求,除了它們必須滿足的一些最低級別。
您正在使用的 C++ 實現可能針對 Intel 處理器。 除了常見的 IEEE-754 基本 32 位和 64 位二進制浮點格式外,Intel 還有一種 80 位格式。 您的 C++ 實現可能將其用於long double
。
Intel 的 80 位格式比 64 位double
精度格式多 11 位作為有效數字。 (它實際上使用 64,而double
格式使用 52,但其中一個保留用於顯式前導 1。)多 11 位意味着 2 11 = 2048 倍有效數值,大約多了三位十進制數字。
80 位格式(十字節)優先對齊到 16 字節的倍數,因此包含六個字節的填充以使long double
大小成為 16 字節的倍數。
您的問題中有很多不正確的假設
首先,在 C++ 中沒有關於類型大小的要求。 該標准只規定了每種類型的最低精度,並且......
...
double
類型至少提供與float
一樣多的精度,而long double
類型至少提供與double
一樣多的精度。float
類型的值集是double
類型值集的子集;double
類型的值集是long double
類型值集的子集。 浮點類型的值表示是實現定義的。http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
大多數現代實現將float
和double
映射到IEEE-754 單精度和雙精度格式,因為對它們的硬件支持是主流。 然而long double
沒有如此廣泛的支持,因為很少有人需要比 double 更高的精度,而且這些硬件的成本要高得多。 因此,一些平台將其映射為 IEEE-754 雙精度,即與double
相同。 如果底層硬件支持,其他一些將其映射到80 位 IEEE 754 擴展精度格式。 否則long double
將由double-double
算術或IEEE-754 四倍精度表示
此外,精度也不會與類型中的位數成線性比例關系。 很容易看出double
的精度是float
的兩倍多,范圍是 float 的 8 倍float
盡管存儲空間只有兩倍,因為它有 53 位有效位,而 float 有 24 位,指數位多 3 位。 類型也可以有陷阱表示或填充位,因此不同的類型可能有不同的范圍,即使它們具有相同的大小並且屬於相同的類別(整數或浮點數)
所以這里重要的是std::numeric_limits<long double>::digits
。 如果您打印它,您會看到long double
有 64 位有效位,僅比double
多 11 位。 現場觀看。 這意味着您的編譯器對long double
使用 80 位擴展精度,其余只是填充字節以保持對齊。 事實上gcc 有多種選項可以改變你的輸出:
-malign-double
和-mno-align-double
用於控制long double
的對齊方式-m96bit-long-double
和-m128bit-long-double
用於更改填充大小-mlong-double-64
、 -mlong-double-80
和-mlong-double-128
用於控制底層long double
實現通過更改選項,您將獲得以下long double
結果
-mlong-double-128
:大小 = 16,數字 10 = 33,數字 2 = 113-m96bit-long-double
:大小 = 12,digits10 = 18,digits2 = 64-mlong-double-64
:大小 = 8,數字 10 = 15,數字 2 = 53如果您禁用填充,您將獲得 size = 10,但由於未對齊,這將以性能為代價。 在編譯器資源管理器上查看更多演示
在 PowerPC 中,您也可以在更改浮點格式時看到相同的現象。 使用-mabi=ibmlongdouble
(double-double 算術,這是默認值),您將得到 (size, digits10, digits2) = (16, 31, 106) 但使用-mabi=ieeelongdouble
時,元組將變為 (16, 33, 113)
有關更多信息,您應該閱讀https://en.wikipedia.org/wiki/Long_double
而且我還想知道如何在不定義自己的數據類型的情況下獲得更高的精度
要搜索的關鍵字是arbitrary-precision arithmetic 。 您可以在List of arbitrary-precision arithmetic software中找到各種庫。 您可以在標簽bigint 、 biginteger或arbitrary-precision中找到更多信息
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.