for 循環為什麼不支持小數?


半夜終極大更新。。。。。

已附帶9.9在32位機器上的精確值

一句話版本:google IEEE754 有真相

詳細版本:

sunderbrace{k_0 k_1 cdots k_{n-1}}_{n bits}underbrace{d_0 d_1 cdots d_{m-1}}_{m bits}

s是符號位

n bits那一部分是指數位 E

m bits 那一部分是significand位 M

任何一個浮點數數學上的精確值是

(-1)^s*2^E*M

對於32bit和64bit而言

s都是1bit,而E和M長度部分是IEEE規定的。。。

32bit E長度為8bit

64bit E長度為11bit

下面來通過9.9的binary representation來算一下其精確值

利用這一段代碼(在小端機上!!)

#include&
using namespace std;
int main(){
float test{ 9.9f };
unsigned char *print{ (unsigned char *)test };
unsigned int mask{ 1 };
for (int i = sizeof(float) - 1; i &>= 0; --i){
unsigned int temp{ print[i] };
for (int i = 7; i &>= 0; --i)
cout &<&< ((temp (mask &<&< i)) &>&> i);
}
cout &<&< endl; return 0; }

獲得binary representation

underbrace{0}_{sign bit}    underbrace{10000010}_{exp bit}    underbrace{00111100110011001100110}_{significand bit}

sign bit是0 所以第一部分是1

exp bit是128+2-127, 注意這裡的-127是自帶的偏移位,所以第二部分的值是8

significand bit更tricky一點,這裡所有的位都是小數點後的位,而且還自帶1作為整數部分

所以這一部分是

1+2^{-3}+2^{-4}+2^{-5}+2^{-6}+2^{-9}+2^{-10}+2^{-13}+2^{-14}+2^{-17}+2^{-18}+2^{-21}+2^{-22}

算出來是

frac{5190451}{4194304}=1.2374999523162841796875

把以上三個部分乘起來就得到:

9.8999996185302734375

(喪心病狂的不等於9.9)

最後扯一點浮點數和資訊理論的關係

浮點數為什麼叫浮點數?因為上邊那一坨的精確值算出來小數點具體在哪兒是浮動的

浮點數說是可以表示10^38範圍(32bit 少年們可以算一下當家庭作業)但是有個根據資訊理論我們知道 32bit只能表達2^32個不同的值 所以回到樓主的問題來上說你隨手選一個實數剛好能被精確表達的概率是0(不是很小,是0,浮點數的集合測度是0,不管用多少位表達,不理解請回憶高數)

So 如果要想精確表達你要的數,請使用自己實現定點數類

BTW 浮點數至少弄炸過兩次飛船,還導致過一次美國本土被導彈轟炸,so,少年,古往今來的仁人志士與你同在


我認為這個問題跟浮點數運算過程中的截斷誤差是兩碼事,雖然這裡面的確出現了截斷誤差。

我們重新看一下程序:

#include &
int main(){
for(double i = 0;i &<= 9.9;i=i+0.1){ std::cout&<&

現在我們在腦海里建立一個運算過程:有一個實數0,它每次增加0.1,最後增加到不大於9.9的一個數為止。

接下來我們分析這個腦海中的過程與這段C++程序有什麼區別。

程序中出現了2個雙精度浮點值和1個整型值 。其中那個整型值是可以完美映射到實數0的。但是剩下的2個值卻不能映射到實數中的9.9和0.1。原因是在C++中浮點數字面量是採用有限位的10進位表示的,但是字面量的值本身卻是有限位的2進位小數表示的。雖然0.1這個實數在十進位中表示作一個有限小數,但是它在以二進位表示時是一個無限循環小數。9.9以二進位表示同樣是一個無限循環小數。

現在我們把初值導致的差異分析完畢了,再看一下計算過程中會產生的截斷誤差。

先看雙精度浮點數0.1的二進位表示是怎樣的:

{10_{2}}^{-4}  	imes {1.1001100110011001100110011001100110011001100110011010}_2

我們可以看出,浮點數0.1加上浮點數0.1這個計算過程是不造成損失的,因為浮點數0.1的最後一個有效位是0,因此有效位足夠存儲和數。我們把雙精度浮點數0.1稱為a

再看3個a相加的結果,我們記它為b

{{10}_2}^{-2}	imes 1.0011001100110011001100110011001100110011001100110011_2

可以看到ab相加要做到和不損失需要54個有效位,而雙精度浮點數只提供了53個有效位(包括隱含的1),所以這裡就產生誤差了。

浮點數與實數的差異不小,需要正確理解浮點數才能將它用於數學計算。


沒這個需求


此問題與for無關,與浮點數的計算精度有關。

樓主你要記住一件事,浮點數是不能判相等的,比如你要判斷someVar的值是不是等於0,要這樣寫

abs(someVar - 0.0) &< 1e-6

someVar &<= 9.9怎麼寫?

someVar &< 9.9 || abs(someVar - 9.9) &< 1e-6 someVar &< 9.9 + 1e-6

如此類推


你自己看看你用浮點數循環出來的彙編就會發現浮點數最好不要用於循環的控制

整數的循環控制很簡單,一個inc 一個cmp,一個jz或者jnz足矣 浮點就麻煩得多


除了浮點數判等誤差的原因之外,請思考下for的語義是什麼?

你今天吃了三頓飯,你會不會對人描述為:

第0.1頓: 早飯,油條豆漿

第0.2頓: 午飯,回鍋肉和米飯

第0.3頓: 晚飯,臊子面

這不符合人類計數的習慣。而for就是個循環計數器,蛋疼才會每次自增01.


浮點數在計算到最後一位的時候都會累積誤差,但是除非數值接近0,否則cout默認輸出都不會精確到那麼多。同樣的現象在excel裡面也可以實現


因為浮點數在內存中的表示問題

當i初始化為0時,i的真實值為0,所以以0.1為間隔自增,到9.9時再自增0.1,退出循環

但是當i被初始化為9.9時,i的值並不精確等於9.9,所以到了0.1的時候,自減仍舊大於0


顯然for只是條件循環語句,第二從句的表達式為真就繼續執行。跟小數還是大樹沒關係

————————————————

最後一次運算貌似是 i(此時值為0.1)減0.1等於0,但實際上i不等於0.1,而是0.1+(1.87905e-14),減去0.1之後等於1.87905e-14。這是個極小的數。

浮點數的存儲方式決定了部分運算會不精確。你可以這樣來理解:它的存儲位一部分用來存底數,一部分用來存指數。當指數那部分過大,就會擠佔一部分底數的存儲位置。不同底數和指數的數字之間的精度必然不同,因此運算可能會留下一些不幹凈的小數。

————————————————

@題主 用PHP跑了一下,有類似的結果:


浮點數比較不要用常規運算符,應該設定一個閾值epsilon去比較,具體原因請深究浮點數的儲存表示方式。


不明所以 上個圖供大家分析

---------------------------------------


這不是for的問題,是計算機精度問題。數字在計算機中是以二進位表示。如double a=0.2;doubleb=0.1;你可以試著列印一下a+b,並不等於0.3;但是如果a=0.15;b=0.15;a+b就等於0.3了

建議你看看《計算機組成原理》就明白了


第一張圖

真的沒看出來哪裡不對 不知道你問題在哪

第二張圖

浮點數是沒有那麼高的精確度的 理解一下精度的概念

想像一下 i初始化的時候 = 9.00000001 (某個比9大一點點的正數)

如果你懂C 把cout&<&printf ( "%.1f " , i ) ; 最後就不會出現那個奇怪數字了

第三張圖

你想像一下 i初始化的時候 = 0.00000001 (某個很小的正數) 最後就會輸出9.9了


推薦閱讀:

Scala以cascade的方式調用函數有什麼不妥嗎?
遊戲的數值系統的實現和演化
電腦語言「0,1」的蘊涵的數理邏輯知識 到底是什麼樣的?如何後天將其與思維模式結合?
極樂技術周報(第二十七期)
FizzBuzz

TAG:編程語言 | 編程 | C |