flaot 資料型別的一些坑(大數吃小數)

2021-10-06 04:20:42 字數 3969 閱讀 9334

引入

首先我們來看一段**,你認為它會輸出什麼呢?

#includeint main()

解析:邏輯上就是將1.0進行累加2千萬次。我們預計的結果應該是20000000。

但是結果卻如圖:

毫無疑問,這肯定和float資料型別有關,但是至於為什麼會出現這個問題,我們一起來分析。

float資料型別如何表示

我們一般都了解int資料型別是如何表示的:它是由32bit位組成,若是有符號int,最高位是符號位,低31位表示有效範圍。無符號int ,32位都是表示有效範圍(32位作業系統,本章中所有demo或話術都是基於32位作業系統)。

但是對float資料型別的表示,我相信大多數人都不太了解。於是今天我們一起來深入學習,了解在工作或學習中需要注意的事項。

float資料型別在記憶體中的格式如圖所示:

第一部分是符號位,用s表示,用來表示符號位。float型別和int型別不一樣,int型別可以通過unsigned修飾來表示有符號或無符號資料。float型別都是有符號的。

第二部分是8bit的指數字用e表示,我們用1 ~ 254對映到-126 ~ 127這254個有正有負的數上,因為浮點數不僅要表示很大的數,也要表示很小的數,因此,指數字也應該有負值。

第三部分是23位的有效位,用f表示。

用科學表示法,浮點數可以表示如下:

從該表示式中,我們無法表示資料0。因此我們就需要一些約定,來表示一些特殊的數。如圖:

當e=0,並且f=0時,浮點數就表示0。

例子我們一起以0.5為例進行分析:

因此,s=0,f=0,e=-1。而e是用1 ~ 254對映-126 ~ 127。因而,e在記憶體中的應該是126。即0.5在記憶體中的表示應為下圖:

精度損失問題

通過浮點數的資料格式我們知道,有效位是23位,因此對於有些數儲存在計算機中就會出現精度損失的情況。比如9.1。

9.1=1001.000110011(以0011迴圈)…轉換為二進位制科學表示法就是9.1=1.001000110011(以0011迴圈)…x 2

32^3

23。因此,s=0,e=3,f=001000110011(以0011迴圈)。由於f只有23bit,所以就會存在精度缺失。如圖:

至此,9.1儲存在記憶體中的二進位制為010000010 0010 0011001100110011 001,在轉換為十進位制就是9.09999942779541015625。

這就解釋了為什麼0.3+0.6=0.899999。因為0.3轉換為浮點數儲存到記憶體中後,不再是準確的0.3了。0.6也是如此。(有些平台demo列印出來的是0.900000,那是因為預設列印位數問題,可通過printf("%1.10f",sum)控制小數點位數。並且不同的編譯器精度缺失的處理方式不同,我在ubuntu 18的測試環境中,得到的值是大於0.9的)

大數吃小數

上面介紹了浮點數精度損失的問題,我們再來看一下大數吃小數的問題。還是直接來上demo:

實際輸出為:

從現象上看就是乙個很大的浮點數和乙個較小的浮點數之和,得到的還是較大的浮點數(大數吃小數)。原因是為何呢?我們一起**一下。

其實這就是浮點數加法計算的原理過程分析,核心就是先對齊再計算

例:0.5 + 0.125

分析:0.5 = (−1

)0(-1)^0

(−1)

0 x 1.0 x 2−1

2 ^2−

10.125 = (−1

)0(-1)^0

(−1)

0 x 1.0 x 2−3

2^2−

3由於0.5和0.125的指數字不相等(-1和-3)需要先對齊(統一為較大的指數字),即:

0.125 = (−1

)0(-1)^0

(−1)

0 x 0.01 x 2−1

2^2−

1 (指數字對齊,對應的有效位就要右移)

0.5 + 0.125 = (−1

)0(-1)^0

(−1)

0 x 1.0 x 2−1

2 ^2−

1 + (−1

)0(-1)^0

(−1)

0 x 0.01 x 2−1

2^2−

1 = (−1

)0(-1)^0

(−1)

0 x 1.01 x 2−1

2^2−

1 上述就是浮點數求和的過程。我相信,大家應該已經知道大數吃小數的原因了。由於有效位是23位,當較大數是較小數的 2

242^

224 (16777216‬)倍之多時,較小數為了將指數字對齊,有效位就會右移很多位,導致有效位中整數部分在23bit之後。科學表示式為:(−1

)0(-1)^0

(−1)

0 x 0.0 x 2

n2 ^

2n=0,這也就解釋了開篇的問題。

當浮點數1.0進行累加時,sum為16777216時,是1.0的2

242^

224倍,之後的累加就出現了大數吃小數。

輸出為:16777216思考

通過上面的分享,我們在使用浮點數時,要盡量避免兩個坑:精度缺失大數吃小數。以下是結合自己的經驗做的總結:

工作中儘量減少對浮點型資料的使用

曾經我們的研發總監明確要求我們編碼中不准使用浮點數,主要是因為我們的業務一般不會用到浮點數。我相信很多公司也要求盡可能少的使用浮點數。但是有些業務需求一定會用到小數,比如:商場中商品的**表,或者是銀行中賬戶金額。我們可以通過字串的方式儲存這些值(雖然有點浪費記憶體),就可以避免精度缺失的問題

面試中遇到的問題

曾經我在面試的時候,面試官讓我對下面的問題進行程式設計:

例:計算代數式 1+1

2\frac

21​+1

3\frac

31​+1

4\frac

41​+…+1

n\frac

n1​的值

我的解法如下:

#includeint main()

其實這不是正確的解,應該將for迴圈改為for(i=n;i>=1;i--)。這點你get到了嗎?

MySQL的一些資料型別

如果乙個float型資料轉成二進位制後的第32位之後都是0,那麼資料是準的 如果乙個float型資料轉成二進位制後的第32位之後不全為0,則資料就會存在誤差 float和double型別的區別和誤差 但是decimal型別是mysql官方唯一指定能精確儲存的型別,也是dba強烈推薦和金錢相關的型別都...

資料型別與資料類的一些感想

在長時間做電表集抄軟體的時候,習慣於將資料層層封裝,而且資料物件全部做成可以序列化的。只不過以前在存貯的時候,使用的還是關係型資料庫的思想,寫dal時候做資料分解比較痛苦。曾有心將序列化後的資料物件直接存貯,可又無法進行快速查詢了,所以此想法無解。現在接觸到的turbogears,zope等,都自己...

c 中一些常見的資料型別

學了也有一兩年的語言了,到現在連基本的資料型別的大小也不是很清楚,或者說是根本就不知道。雖然以前也都看過,但真容易忘。其實感覺好像也不能怪我,誰叫它不經常被用到,或是說不經常被我給用到。其實這也只是為自己找了乙個藉口罷了。直到今天,遇到memset 函式,才想徹底弄清楚一些基本的資料型別的大小,此處...