計算機中的浮點數在數軸上分布均勻嗎?

比如一個32位機的float浮點數 如果在數軸上把所有可表示的值表示出來 在數軸上是否是均勻的?


@vczh 的例子不夠誇張,IEEE754中,[0, 2)里的浮點數比[2, infty)里的浮點數多。

#include &
#include &
#include &

using namespace std;

// Count number of float in [l, r)
unsigned frange(float l, float r) {
assert(l &>= 0.0f r &>= 0.0f);
float f = l;
unsigned n = 0;
do {
f = nextafter(f, r);
n++;
} while (f != r);
return n;
}

float utof(unsigned x) {
union { unsigned u; float f; } u = { x };
return u.f;
}

int main(int argc, char* argv[]) {
cout &<&< "[0, 2): " &<&< frange(0.0f, 2.0f) &<&< endl; cout &<&< "[2, inf): " &<&< frange(2.0f, INFINITY) &<&< endl; cout &<&< "[0, MinNormal): " &<&< frange(0.0f, utof(0x00800000)) &<&< endl; cout &<&< "[MinNormal, 1.0): " &<&< frange(utof(0x00800000), 1.0f) &<&< endl; cout &<&< "[1.0, inf): " &<&< frange(1.0f, INFINITY) &<&< endl; } // [0, 2): 1073741824 // [2, inf): 1065353216 // [0, MinNormal): 8388608 // [MinNormal, 1.0): 1056964608 // [1.0, inf): 1073741824

--

更新:為了回答 @陳碩在評論中的問題,修改了一下程序,並畫出以下的表。


enewcommand{arraystretch}{1.5}
egin{tabular}{|c|c|}
hline
positive range  count \
hline
$[0.0, 	ext{normal}_	ext{min})$  $2^{23}$ \
$[	ext{normal}_	ext{min}, 1.0)$  $126 	imes 2^{23}$ \
$[1.0, infty)$  $128 	imes 2^{23}$ \
$infty$  1 \
NaN  $2^{23} - 1$\
hline
Total  $2^{31}$ \
hline
end{tabular}

可見,[1,infty)的個數是[0, 1)frac{128}{127}倍,原因是8-bit exponent有0xFF這特殊值,用於表示inf和NaN,所以exponent可表示非特殊值的是奇數,導至exponent的正負個數並非相等。


最近正好在對比浮點數據和整型數據分別適合處理什麼類型的計算,這是16bit = s(1bit).e(6 bits).f(9 bits)格式在範圍內的分布,縱軸使用的是log坐標,絕對值越小的數表示的越精確。


當然不是均勻的,1-2裡面的所有浮點數,就跟2-4裡面的所有浮點數一樣多


謝謝邀請。這個問題挺有意思的。

依據最常見的IEEE標準,把所有float32在數軸上標出來,會長什麼樣子呢?

首先,浮點數的表示範圍(拋去正無窮和負無窮)是有限的,所以被標出的數軸只是數軸中的一段。因此大尺度上肯定不是均勻分布的。

其次,由於浮點數的原理是科學計數法,所以(大體上講),表示的是所有有效數字恰好為一定數目的數,所以,在相同的數量級上是均勻的,但在不同的數量級上就不是了。

綜上,浮點數在數軸上的分布不是絕對均勻的,而是分段均勻的。

(另外,這個問題和操作系統內核並無關係。內核里一般是禁止使用浮點數的。)


如果你取對數作為橫坐標的話,那就是在每個整數區間之間都是均勻的。

整數區間之內是線性刻度均勻的。



我們可以把符號位忽略掉,只看正半軸。然後我們按2^{-127} 2^{-126}等等一直到2^{126}在數軸上畫點。這樣每兩個相鄰點之間所含的浮點數表示量是一樣的。

另外,在-2^{-127}2^{-127}之間只有零能表示出來。

也就是說,除去-2^{-127}2^{-127}這一段,越靠近零的地方是會分布的越密的。

參考ARM Information Center - 庫和浮點支持指南


@Milo Yip的解答很直觀。我來對結果做些理論解釋。

下圖是IEEE754單精度浮點數在內存中的表示法。綠色部分為指數部分,紅色部分為尾數部分。這個表示法的具體解讀可以參考float的精範圍是如何求出來的? - 坡下碎石的回答。

只考慮正數情況時,最高位的符號位為0。2.0f的表示方法如下圖。

這時指數部分為128,尾數為0。當指數部分小於128時,不管尾數是多少,這個值都小於2.0f。當所有位都為0時表示0.0f。因此0~29位均可取0或1,可以表示的0到2.0之間的浮點數為2^30 = 1073741824。

而正無限的表示法如下圖所示,指數部分為255,尾數有個隱藏位為1。

那麼指數在128到255之間,尾數任意取值,這樣就是IEEE754單精度浮點數可以表示的2到正無窮之間的數。一共有2^30 - 2 ^ 23 = 1065353216個。回到本題,我們可以從上面的表示法看出來,每個指數取值所能表示的數為2^23個,從0開始指數每增長1,其表示數的範圍就增長1倍,但可以表示的數是一樣多的。所以浮點數在數軸上的分布應該符合對數規律,即取對數後接近均勻分布。


浮點數的設計目的就是整數位小,需要較少bit表示時可以騰出更多bit表示小數位,所以必然不均勻。


不均勻。越靠近原點越密集,越遠離原點越稀疏。


這問題問的好專業我看了半天。

我要舉例子上解釋這個問題了哈前方低能。

首先套用某匿名答案,計算機中浮點數的「浮點」就是可以移動的小數點的位置。那麼我們來假設一個浮點數只有8byte而且點不佔位同時沒有負數來簡化理解這個問題。

好的讓我們設定這8byte分別是76543210,而點的位置就有可能為.7.6.5.4.3.2.1.0. 就是這個樣子。

在8byte的整數區間,我們可以表示最小0,最大255的整數,而這個時候我們的小數點是固定在8byte中的最後的。當我們的點數位於0後衛,數值為76543210. 咱滴浮點數,精度是1.

讓我們移動一下這個浮點數的點,讓我們的數值變為7654321.0 咱滴浮點數,精度是0.5,最大值127.5最小值0.

以此類推!

當我們吧數值變為.76543210的時候,咱滴浮點數,精度是1/2的次冪,也就是256分之1.最大值lim-&>1等於255/256,最小值0。

也就是說在我們的浮點數在點可能的9個位置上的精度分別為1、0.5、0.25、0.125、0.0625、0.03125、0.015625、0.0078125和0.00390625。

顯而易見,我們的浮點位分布為:

0-&>1 256個

1-&>2 128個

2-&>3 64個

4-&>5 32個

8-&>9 16個

16-&>17 8個

32-&>33 4個

64-&>65 2個

128-&>129 1個

函數繪圖分布:請腦補。

那麼我們來進行一下拓展,8byte拓展為64byte,同時加入一個符號位佔位,加入一個階符,加入一個階碼。在進行計算,原理是完全完全完全一樣的。

所以答案是不平均而且成指數墜落。


可以簡單理解為:整數部分越小,那麼小數部分能表示的就越多,數量自然越多,所以0-1的數量是最多的。


浮點數的「浮」的意思就是那個小數點可以動。有效數字就那麼多,整數部分長點兒,小數部分就短了。在數軸上取固定長度的一段兒,如果靠近0,那麼數比較小,整數部分比較短,小數部分比較長,這段兒包含的數的個數就多了。


貢獻點代碼,作為對 @Milo Yip答案的補充

#include &
#include &

using namespace std;

/*
* f &>= 0.0f;
*/
float nextFloat(float f) {
struct float_in_memory {
unsigned int tail : 23;
unsigned int exp : 8;
unsigned int sign : 1;
};

float_in_memory fim = *((struct float_in_memory*)f);
/*
* It"s same with:
* int i = *((int *)f);
* i++;
* f = *((float *)i);
*/
if (fim.tail == 0x7fffff) {
fim.tail = 0;
fim.exp++;
} else {
fim.tail++;
}

f = *((float *)fim);
return f;
}

int main() {
int n = 0;
float f = 0.0f;
while (f &< 2.0f) { f = nextFloat(f); n++; } cout &<&< "float in [0,2) is " &<&< n &<&< endl; n = 0; while (f &< INFINITY) { f = nextFloat(f); n++; } cout &<&< "float in [2, infinity) is " &<&< n &<&< endl; return 0; }

輸出:

float in [0,2) is 1073741824

float in [2, infinity) is 1065353216


如果均勻分布,又不止一個的話,就有無窮多個,顯然是不可能的啊


IEEE 754 標準已經很多人提過了。不再贅述。針對 64bits dobule 而言,1 bit 符號位(sign),11 bits 指數位(exponent),52 bits 尾數位(mantissa)。但其實我不喜歡說浮點數是小數點在浮動這個說法,感覺像是主動去動這個小數點。回到浮點數的定義 (1+f)2^e 里,所謂小數點浮動是 2^e 大數字相乘帶來的被動結果。

直接看浮點數的話,我覺得最方便的還是 MATLAB,可以直接用 format hex 展示浮點數 16 進位的樣子。介紹點有用的。

realmax,64位浮點數最大的數,是 7fefffffffffffff,約等於 1.7977e+308。

realmin,64位浮點數最小的數,是 0010000000000000,約等於 2.2251e-308。

精度問題的話,具體到不同區間 [2^e, 2^{e+1}],精度(增長步長)為 2^{e-t}。其中 t 是保存 mantissa 的 bits 數,這裡是 52。可以看出來數字越小精度越大,當 e &>= 53 的時候,步長就已經大於 1 了。所以會出現 2^{53} = 2^{53}+1 這種情況,體現到 MATLAB 里就是

&>&> 2^53 == 2^53 + 1
ans = 1
&>&> 2^52 == 2^52 + 1
ans = 0

最後還有個比較直觀的 MATLAB 的小程序去體現浮點數的精度,出自 NCM,floatgui.m。

參考資料:

Numerical Computing with MATLAB


僅[-1,1] 區間就佔了50%的有效浮點數值


同類型浮點數的「有效位數」是一樣多的

只允許有3位有效數字

比1.23大一點點的是1.24

比123000.0大一點點是124000.0

題主你說呢

哦,對了,上面的例子用的是十進位,請腦補成二進位版本例子


可以看csapp第2章關於浮點數的內容。


推薦閱讀:

Rust目前有比較靠譜的IDE嗎?
集線器和交換機的區別?
勸退偽化生和傳統工科並推崇CS是不是知乎上的一種政治正確?為什麼會這樣呢?
如果出現一種實用的新型計算機(未來計算機,比如基於憶阻器的),那麼需要學習新的編程語言么?
在軟體開發的職業領域裡,在什麼樣的情況下才會遇到 : 計算機編程藝術《The Art of Computer Programming》以及 演算法導論《Introduction to Algorithms》 中的知識呢?

TAG:計算機科學 | 浮點數 | 計算機組成原理 | IEEE754 |